Go: Nuevo sistema de logs con buffer circular

Fuentes: Building slogbox

El artículo de Alex Rios, "Construyendo slogbox", explora la implementación de un manejador de logs (slog.Handler) en Go que utiliza un buffer circular (ring buffer) para almacenar los últimos registros de logs. La motivación surge de la necesidad de tener acceso a logs recientes para fines de monitorización, depuración y 'black box recording', similar a la funcionalidad runtime/trace.FlightRecorder introducida en Go 1.25. En lugar de simplemente añadir registros a una slice y truncarla, lo que puede generar asignaciones de memoria innecesarias y presión en el recolector de basura (GC), slogbox utiliza una slice pre-alocada con aritmética de módulo para un manejo más eficiente. Esto evita asignaciones y copias de memoria en el 'hot path' de los logs.

La estructura recorder mantiene un buffer (buf), un puntero a la siguiente posición de escritura (head), un contador de registros (count), un contador global de registros (total) y un nivel de filtrado (flushOn). La función snapshotAll() permite obtener una copia de los registros en orden, manejando el 'wraparound' del buffer circular. Una decisión clave es almacenar los registros como objetos slog.Record sin formatearlos inmediatamente. Esto permite flexibilidad en el formato de salida (JSON, texto, etc.) en el momento de la lectura, evitando el formateo innecesario de registros que eventualmente se descartarán. Esto es especialmente importante en sistemas con una alta tasa de escritura de logs y una baja tasa de lectura.

Un problema de corrección sutil se aborda resolviendo los valores de los atributos de los logs de forma 'eager' (inmediata) en el momento de la escritura. Esto asegura que el valor del atributo refleje el estado del sistema en el momento en que se generó el log, y no en el momento en que se lee el log. Para la concurrencia, se utiliza un sync.RWMutex para permitir múltiples lectores simultáneos sin bloquear los escritores, optimizando el rendimiento para la ruta de escritura de alta frecuencia. El diseño prioriza la eficiencia de la escritura sobre la de la lectura, lo que es una compensación razonable dada la naturaleza de la aplicación. Finalmente, la API expone métodos para obtener snapshots de registros (raw, filtrados por nivel, iteradores) y para serializar los registros a diferentes formatos (JSON, escritor de I/O).