Logging estructurado en Go: de fmt.Println a logs context‑aware con integración automática en APM

Logging estructurado en Go: Desde fmt.Println a context‑aware logs con integración automática en APM

Introducción

En los proyectos Go es habitual iniciar con fmt.Println para imprimir valores de depuración. Sin embargo, cuando una aplicación crece, esos mensajes simples se convierten en un obstáculo para la observabilidad. En este guía para principiantes vamos a transformar esa práctica básica en un sistema de logging estructurado que incluye contexto (trace ID, usuario, etc.) y se envía automáticamente a un APM (Application Performance Monitoring) como Lescopr. Cada paso está explicado sin asumir conocimientos previos.


1. Conceptos básicos

1.1 ¿Qué es logging estructurado?

El logging estructurado consiste en registrar datos en un formato legible por máquinas (JSON, protobuf) donde cada campo tiene un nombre definido. Esto permite filtrar, buscar y correlacionar eventos sin necesidad de parsear texto libre.

1.2 ¿Qué aporta el contexto?

El contexto agrega información adicional a cada registro: trace_id, span_id, user_id, request_path. Con estos datos, un APM puede enlazar logs con trazas distribuidas y ofrecer vistas unificadas.

1.3 ¿Qué es un APM?

Un APM monitoriza métricas de rendimiento (latencia, throughput) y errores en tiempo real. Cuando los logs incluyen IDs de trazas, el APM puede mostrar la cadena completa de llamadas que provocó un fallo.


2. Preparar el proyecto Go

2.1 Crear un módulo nuevo

mkdir ejemplo-logging && cd ejemplo-logging
go mod init ejemplo-logging

2.2 Añadir dependencias de logging y tracing

go get go.uber.org/zap
go get go.opentelemetry.io/otel

zap es una librería de logging rápido y estructurado; otel provee la API de tracing.


3. Reemplazar fmt.Println por zap

3.1 Inicializar un logger zap

import (
    "go.uber.org/zap"
)

func NewLogger() *zap.Logger {
    cfg := zap.NewProductionConfig()
    cfg.Encoding = "json" // formato estructurado
    logger, _ := cfg.Build()
    return logger
}

3.2 Uso básico

func main() {
    logger := NewLogger()
    defer logger.Sync()
    logger.Info("Aplicación iniciada", zap.String("version", "1.0.0"))
}

En lugar de fmt.Println("Aplicación iniciada") ahora emitimos un registro JSON con campo version.


4. Enriquecer logs con contexto de tracing

4.1 Configurar un tracer OpenTelemetry

import (
    "go.opentelemetry.io/otel"
    "go.opentelemetry.io/otel/trace"
    "go.opentelemetry.io/otel/sdk/trace"
)

func InitTracer() trace.TracerProvider {
    tp := trace.NewTracerProvider()
    otel.SetTracerProvider(tp)
    return tp
}

4.2 Propagar trace_id en los logs

func LogWithContext(ctx context.Context, logger *zap.Logger, msg string) {
    span := trace.SpanFromContext(ctx)
    logger.Info(msg,
        zap.String("trace_id", span.SpanContext().TraceID().String()),
        zap.String("span_id", span.SpanContext().SpanID().String()),
    )
}

4.3 Ejemplo completo

func handler(w http.ResponseWriter, r *http.Request) {
    ctx, span := otel.Tracer("example").Start(r.Context(), "handler")
    defer span.End()

    logger := NewLogger()
    LogWithContext(ctx, logger, "Solicitud recibida")
    // ... lógica del handler
    logger.Info("Respuesta enviada", zap.Int("status", 200))
}

Ahora cada log contiene trace_id y span_id, listos para ser recogidos por el APM.


5. Integración automática con Lescopr APM

5.1 Crear una cuenta y obtener el token

Regístrate en Lescopr, genera un API token y copia la URL del endpoint de ingestión.

5.2 Añadir el exportador OpenTelemetry para Lescopr

import (
    "go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp"
)

func InitLescoprExporter() {
    exporter, _ := otlptracehttp.New(context.Background(),
        otlptracehttp.WithEndpoint("ingest.lescopr.com"),
        otlptracehttp.WithHeaders(map[string]string{"Authorization": "Bearer <TU_TOKEN>"}),
    )
    tp := trace.NewTracerProvider(trace.WithBatcher(exporter))
    otel.SetTracerProvider(tp)
}

Al iniciar la aplicación, el tracer enviará trazas y logs directamente al backend de Lescopr.

5.3 Ver los logs en el dashboard

  1. Accede a la consola de Lescopr.
  2. Selecciona tu proyecto.
  3. Navega a Logs → Structured.
  4. Filtra por trace_id para ver la cadena completa de eventos.

6. Buenas prácticas y optimizaciones

  • Nivel de registro: Usa Info para eventos normales, Warn para situaciones anómalas y Error para fallos.
  • Muestreo: Configura el sampler de OpenTelemetry para reducir la carga en entornos de alta frecuencia.
  • Rotación de logs: Aunque los logs se envían a Lescopr, conserva archivos locales con política de retención de 7‑15 días para debugging offline.
  • Enriquecimiento adicional: Añade campos como environment, service_version y host_ip para facilitar la segmentación.

7. Paso a paso resumido

  1. Inicializa un proyecto Go y añade zap y otel.
  2. Configura zap en modo JSON.
  3. Crea un tracer OpenTelemetry.
  4. Envuelve cada petición con Start/End y pasa el context a los logs.
  5. Configura el exportador OTLP de Lescopr con tu token.
  6. Despliega y verifica los logs en el dashboard.

Conclusión

Pasar de fmt.Println a logs estructurados con contexto y envío automático a un APM transforma la capacidad de diagnóstico de cualquier servicio Go. La observabilidad pasa de ser reactiva a proactiva, reduciendo el MTTR y facilitando el cumplimiento de SLA.

Para profundizar, la documentación de Lescopr detalla la implementación paso a paso.