Tu aplicación funciona, pero ¿estás seguro de que sabes lo que hay dentro?
\n\nImagina: lanzaste un microservicio en Go a producción. No falla, no da errores 500, pero los usuarios se quejan de que la página carga «como que lento». Revisas el monitoreo — CPU al 30%, memoria normal. ¿Qué hacer? Sin observabilidad en Go eres un capitán de barco sin mapa ni brújula.
\n\nObservabilidad no es solo «registros». Son tres pilares: trazado, métricas y registros. Juntos te dan un superpoder: entender lo que sucede en el sistema, incluso si no esperabas un problema. En este artículo analizaremos cada uno de estos aspectos con ejemplos de código en Go. No solo leerás teoría — obtendrás recetas listas que puedes implementar hoy mismo.
\n\n¿Por qué Go es el lenguaje ideal para la observabilidad?
\n\nGo fue creado para sistemas backend donde el rendimiento y la confiabilidad son importantes. La biblioteca estándar (net/http, database/sql) ya sabe entregar métricas y soportar contextos para trazado. Y el ecosistema de OpenTelemetry Go y Prometheus Go hace que implementar observabilidad sea simple y elegante.
Pero lo principal — en Go no hay «magia». Escribes código que se puede medir, trazar y registrar sin dolor. Veamos cómo se hace.
\n\nTrazado en Go: OpenTelemetry en acción
\n\nTrazado es la historia de una solicitud desde que entra al sistema hasta que sale. Muestra cuánto tiempo tomó cada paso: consulta a la BD, llamada a una API externa, procesamiento de datos. En Go, el trazado se basa en contextos (context.Context).
Configuración del SDK de OpenTelemetry
\n\nInstalemos los paquetes:
\ngo get go.opentelemetry.io/otel \\\n go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp \\\n go.opentelemetry.io/otel/sdk/trace\n\nAhora inicializamos el trazador:
\n\npackage main\n\nimport (\n "context"\n "log"\n "go.opentelemetry.io/otel"\n "go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp"\n "go.opentelemetry.io/otel/sdk/trace"\n)\n\nfunc initTracer() {\n exporter, err := otlptracehttp.New(context.Background())\n if err != nil {\n log.Fatal(err)\n }\n\n tp := trace.NewTracerProvider(\n trace.WithBatcher(exporter),\n trace.WithSampler(trace.AlwaysSample()),\n )\n otel.SetTracerProvider(tp)\n}\n\nCreando un span en el manejador HTTP
\n\nAsí se ve el trazado en código real:
\n\nfunc userHandler(w http.ResponseWriter, r *http.Request) {\n tracer := otel.Tracer("user-service")\n ctx, span := tracer.Start(r.Context(), "getUser")\n defer span.End()\n\n // Simulación de consulta a BD\n user, err := getUserFromDB(ctx, r.URL.Query().Get("id"))\n if err != nil {\n span.RecordError(err)\n span.SetStatus(codes.Error, err.Error())\n http.Error(w, err.Error(), 500)\n return\n }\n\n span.SetAttributes(\n attribute.String("user.id", user.ID),\n attribute.Int64("user.age", int64(user.Age)),\n )\n\n // Span anidado para llamada externa\n _, childSpan := tracer.Start(ctx, "callAnalyticsAPI")\n defer childSpan.End()\n\n callAnalyticsAPI(user)\n \n w.Write([]byte("OK"))\n}\n\nCada span contiene nombre, hora de inicio y fin, atributos y estado. En Jaeger o Grafana Tempo verás el mapa completo de la solicitud.
\n\nConsejo práctico
\nNo olvides pasar el contexto a través de toda la aplicación. En Go esto se hace automáticamente si usas context.Context en las firmas de funciones. OpenTelemetry Go tomará el contexto de la solicitud HTTP por sí mismo si usas middleware.
Métricas: Prometheus y exportación en Go
\n\nMétricas son números que hablan del estado del sistema: RPS, latencia, cantidad de errores, uso