Creación de un Sistema de Logs en Go para Observabilidad

Clase 27 de 30Curso de Ciberseguridad para Desarrollo Web

Resumen

¿Cómo crear tu primer sistema de logs?

Los logs son cruciales para la observabilidad de sistemas, ayudando a identificar y analizar qué sucede en ellos. Herramientas como New Relic, Datadog, Kibana y CloudWatch facilitan esta tarea. Pero la verdadera magia comienza cuando creamos nuestro propio sistema de logs. Los logs deben ser consistentes, claros, encontrables y accionables.

¿Cómo deben ser los logs ideales?

  • Consistentes: Uniformidad en todo el sistema para fácil comprensión.
  • Claros: Mensajes precisos que no generen confusiones.
  • Encontrables: Facilidad para su búsqueda y análisis.
  • Accionables: Capaces de desencadenar acciones concretas.

Veamos cómo implementar un sistema de logs desde el código.

Implementación de un sistema de logs en Go

Para empezar, dentro de nuestro proyecto, crearemos una carpeta llamada Logger con un archivo logger.go, siguiendo estos pasos:

Estructura básica del Logger

  1. Definición de enum para niveles de log:
package logger

type LogLevel string

const (
    Fatal   LogLevel = "FATAL"
    Error   LogLevel = "ERROR"
    Warning LogLevel = "WARNING"
    Info    LogLevel = "INFO"
)
  1. Creación de la estructura Logger:
type Logger struct {
    ServiceName string
}

func NewLogger(serviceName string) Logger {
    return Logger{ServiceName: serviceName}
}

Aquí, la estructura incluye el parámetro ServiceName para identificar el origen del log.

Método básico para el logging

Implementamos un método que loguee en formato JSON, convirtiéndose en una base escalable y filtrable:

func (l Logger) Log(ctx context.Context, eventName string, logLevel LogLevel, attributes map[string]interface{}) {
    logMap := map[string]interface{}{
        "service":   l.ServiceName,
        "event":     eventName,
        "level":     logLevel,
        "attributes": attributes,
    }
    logJSON, _ := json.Marshal(logMap)
    fmt.Println(string(logJSON))
}

Añade métodos para diferentes niveles de error

Crear métodos específicos para cada nivel de log usando el método Log previamente definido:

func (l Logger) Info(ctx context.Context, eventName string, attributes map[string]interface{}) {
    l.Log(ctx, eventName, Info, attributes)
}

func (l Logger) Warning(ctx context.Context, eventName string, attributes map[string]interface{}) {
    l.Log(ctx, eventName, Warning, attributes)
}

func (l Logger) Error(ctx context.Context, eventName string, err error, attributes map[string]interface{}) {
    attributes["error"] = err.Error()
    l.Log(ctx, eventName, Error, attributes)
}

func (l Logger) Fatal(ctx context.Context, eventName string, attributes map[string]interface{}) {
    l.Log(ctx, eventName, Fatal, attributes)
}

Refactoriza tu Lambda para usar el sistema de logs

En el archivo de nuestra lambda, handleGitHubNotifications, inicializamos el objeto Logger antes de ejecutar cualquier lógica:

logger := NewLogger("handleGitHubWebhook")
attributes := make(map[string]interface{})

Sustituimos los métodos fmt.Print por el uso del Logger:

attributes["webhook"] = "hitCommit"
logger.Info(context.Background(), "validGitHubWebhookReceived", attributes)

Así, implementamos el sistema de logging dentro de una Lambda usando el despliegue de Terraform para asegurarnos de que los cambios se reflejan correctamente.

Visualización de logs en AWS CloudWatch

Una vez realizada la implementación, podemos verificar los logs en AWS CloudWatch accediendo a monitor, donde nuestros logs se reflejan en formato JSON por cada evento. Esto facilita la lectura y gestión de la información.

Con este conocimiento, estás preparado para extender el sistema de logs y mejorar la observabilidad de tus servicios. Continúa explorando y aprendiendo sobre cómo optimizar tus sistemas para alcanzar el máximo potencial. ¡El mundo de la observabilidad te espera!