Ejemplo de instrumentación de Go

En este documento se describe cómo modificar una aplicación Go para recoger datos de trazas y métricas mediante el framework de código abierto OpenTelemetry, así como escribir registros JSON estructurados en la salida estándar. Este documento también proporciona información sobre una aplicación de ejemplo que puedes instalar y ejecutar. La aplicación está configurada para generar métricas, trazas y registros.

Para obtener más información sobre la instrumentación, consulta los siguientes documentos:

Acerca del contexto

El contexto de OpenTelemetry es un mecanismo para transferir valores de ámbito de ejecución entre APIs dentro de un proceso. Un uso importante del contexto es llevar el intervalo activo actual para que se pueda modificar o hacer referencia a él como el elemento superior de cualquier intervalo nuevo cuando se cree. En resumen:

  • Contexto hace referencia al mecanismo para propagar valores de ámbito de ejecución, incluido el intervalo activo actual, entre APIs de un proceso.

  • Contexto de intervalo es un objeto inmutable de cada intervalo que incluye el ID de la traza, el ID del intervalo, las marcas y el estado de la traza.

  • La propagación es el mecanismo que mueve el contexto entre servicios y procesos.

La biblioteca estándar de Go context.Context también transfiere valores de ámbito entre límites de API. Normalmente, las funciones de controlador de un servidor reciben un Context entrante y lo transfieren a través de la cadena de llamadas a los clientes que realizan solicitudes salientes.

La biblioteca estándar de Go context.Context se usa como implementación de OpenTelemetry Context en Go.

Antes de empezar

  1. Sign in to your Google Cloud account. If you're new to Google Cloud, create an account to evaluate how our products perform in real-world scenarios. New customers also get $300 in free credits to run, test, and deploy workloads.
  2. Install the Google Cloud CLI.

  3. Si utilizas un proveedor de identidades (IdP) externo, primero debes iniciar sesión en la CLI de gcloud con tu identidad federada.

  4. Para inicializar gcloud CLI, ejecuta el siguiente comando:

    gcloud init
  5. Create or select a Google Cloud project.

    • Create a Google Cloud project:

      gcloud projects create PROJECT_ID

      Replace PROJECT_ID with a name for the Google Cloud project you are creating.

    • Select the Google Cloud project that you created:

      gcloud config set project PROJECT_ID

      Replace PROJECT_ID with your Google Cloud project name.

  6. Verify that billing is enabled for your Google Cloud project.

  7. Enable the Cloud Logging, Cloud Monitoring, and Cloud Trace APIs:

    gcloud services enable logging.googleapis.com monitoring.googleapis.com cloudtrace.googleapis.com
  8. Install the Google Cloud CLI.

  9. Si utilizas un proveedor de identidades (IdP) externo, primero debes iniciar sesión en la CLI de gcloud con tu identidad federada.

  10. Para inicializar gcloud CLI, ejecuta el siguiente comando:

    gcloud init
  11. Create or select a Google Cloud project.

    • Create a Google Cloud project:

      gcloud projects create PROJECT_ID

      Replace PROJECT_ID with a name for the Google Cloud project you are creating.

    • Select the Google Cloud project that you created:

      gcloud config set project PROJECT_ID

      Replace PROJECT_ID with your Google Cloud project name.

  12. Verify that billing is enabled for your Google Cloud project.

  13. Enable the Cloud Logging, Cloud Monitoring, and Cloud Trace APIs:

    gcloud services enable logging.googleapis.com monitoring.googleapis.com cloudtrace.googleapis.com
  14. Instrumentar tu aplicación para recoger trazas, métricas y registros

    Para instrumentar tu aplicación de forma que recoja datos de trazas y métricas, y para escribir JSON estructurado en la salida estándar, sigue los pasos que se describen en las secciones posteriores de este documento:

    1. Configurar la función principal
    2. Configurar OpenTelemetry
    3. Configurar el almacenamiento de registros estructurado
    4. Añadir instrumentación al servidor HTTP
    5. Vincular intervalos de traza con registros y métricas
    6. Añadir instrumentación al cliente HTTP
    7. Escribir registros estructurados

    Configurar la función principal

    Para configurar la aplicación de forma que escriba registros estructurados y recoja métricas y datos de trazas mediante OpenTelemetry, actualiza la función main para configurar el paquete de registro estructurado de Go, slog, y OpenTelemetry.

    En el siguiente ejemplo de código se muestra una función main que llama a dos funciones auxiliares, setupLogging() y setupOpenTelemetry(). Estas funciones auxiliares configuran el paquete de registro y OpenTelemetry.

    Para ver el ejemplo completo, haz clic en Más y, a continuación, selecciona Ver en GitHub.

    func main() {
    	ctx := context.Background()
    
    	// Setup logging
    	setupLogging()
    
    	// Setup metrics, tracing, and context propagation
    	shutdown, err := setupOpenTelemetry(ctx)
    	if err != nil {
    		slog.ErrorContext(ctx, "error setting up OpenTelemetry", slog.Any("error", err))
    		os.Exit(1)
    	}
    
    	// Run the http server, and shutdown and flush telemetry after it exits.
    	slog.InfoContext(ctx, "server starting...")
    	if err = errors.Join(runServer(), shutdown(ctx)); err != nil {
    		slog.ErrorContext(ctx, "server exited with error", slog.Any("error", err))
    		os.Exit(1)
    	}
    }
    

    Después de configurar el paquete de registro, para vincular los registros a los datos de la traza, debes pasar el Context de Go al registrador. Para obtener más información, consulta la sección Escribir registros estructurados de este documento.

    Configurar OpenTelemetry

    Para recoger y exportar trazas y métricas mediante el protocolo OTLP, configure las instancias globales TracerProvider y MeterProvider. En el siguiente ejemplo de código se muestra la función setupOpenTelemetry, a la que se llama desde la función main:

    func setupOpenTelemetry(ctx context.Context) (shutdown func(context.Context) error, err error) {
    	var shutdownFuncs []func(context.Context) error
    
    	// shutdown combines shutdown functions from multiple OpenTelemetry
    	// components into a single function.
    	shutdown = func(ctx context.Context) error {
    		var err error
    		for _, fn := range shutdownFuncs {
    			err = errors.Join(err, fn(ctx))
    		}
    		shutdownFuncs = nil
    		return err
    	}
    
    	// Configure Context Propagation to use the default W3C traceparent format
    	otel.SetTextMapPropagator(autoprop.NewTextMapPropagator())
    
    	// Configure Trace Export to send spans as OTLP
    	texporter, err := autoexport.NewSpanExporter(ctx)
    	if err != nil {
    		err = errors.Join(err, shutdown(ctx))
    		return
    	}
    	tp := trace.NewTracerProvider(trace.WithBatcher(texporter))
    	shutdownFuncs = append(shutdownFuncs, tp.Shutdown)
    	otel.SetTracerProvider(tp)
    
    	// Configure Metric Export to send metrics as OTLP
    	mreader, err := autoexport.NewMetricReader(ctx)
    	if err != nil {
    		err = errors.Join(err, shutdown(ctx))
    		return
    	}
    	mp := metric.NewMeterProvider(
    		metric.WithReader(mreader),
    	)
    	shutdownFuncs = append(shutdownFuncs, mp.Shutdown)
    	otel.SetMeterProvider(mp)
    
    	return shutdown, nil
    }
    

    El ejemplo de código anterior configura el TextMapPropagator global para usar el formato Contexto de seguimiento de W3C para propagar el contexto de seguimiento. Esta configuración asegura que los intervalos tengan la relación correcta entre elementos principales y secundarios en una traza.

    Para asegurarse de que se vacía toda la telemetría pendiente y de que las conexiones se cierran correctamente, la función setupOpenTelemetry devuelve una función llamada shutdown, que realiza esas acciones.

    Configurar el almacenamiento de registros estructurado

    Para incluir la información de la traza en los registros con formato JSON que se escriben en la salida estándar, configura el paquete de registro estructurado de Go, slog. En el siguiente ejemplo de código se muestra la función setupLogging, a la que se llama desde la función main:

    func setupLogging() {
    	// Use json as our base logging format.
    	jsonHandler := slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{ReplaceAttr: replacer})
    	// Add span context attributes when Context is passed to logging calls.
    	instrumentedHandler := handlerWithSpanContext(jsonHandler)
    	// Set this handler as the global slog handler.
    	slog.SetDefault(slog.New(instrumentedHandler))
    }
    

    El código anterior llama a la función handlerWithSpanContext, que extrae información de la instancia Context y la añade como atributos a un registro. Estos atributos se pueden usar para correlacionar un registro con un rastreo:

    • logging.googleapis.com/trace: nombre de recurso de la traza asociada a la entrada de registro.
    • logging.googleapis.com/spanId: el ID del intervalo de la traza asociada a la entrada de registro.
    • logging.googleapis.com/trace_sampled: el valor de este campo debe ser true o false.

    Para obtener más información sobre estos campos, consulta la LogEntry estructura.

    func handlerWithSpanContext(handler slog.Handler) *spanContextLogHandler {
    	return &spanContextLogHandler{Handler: handler}
    }
    
    // spanContextLogHandler is a slog.Handler which adds attributes from the
    // span context.
    type spanContextLogHandler struct {
    	slog.Handler
    }
    
    // Handle overrides slog.Handler's Handle method. This adds attributes from the
    // span context to the slog.Record.
    func (t *spanContextLogHandler) Handle(ctx context.Context, record slog.Record) error {
    	// Get the SpanContext from the context.
    	if s := trace.SpanContextFromContext(ctx); s.IsValid() {
    		// Add trace context attributes following Cloud Logging structured log format described
    		// in https://cloud.google.com/logging/docs/structured-logging#special-payload-fields
    		record.AddAttrs(
    			slog.Any("logging.googleapis.com/trace", s.TraceID()),
    		)
    		record.AddAttrs(
    			slog.Any("logging.googleapis.com/spanId", s.SpanID()),
    		)
    		record.AddAttrs(
    			slog.Bool("logging.googleapis.com/trace_sampled", s.TraceFlags().IsSampled()),
    		)
    	}
    	return t.Handler.Handle(ctx, record)
    }
    
    func replacer(groups []string, a slog.Attr) slog.Attr {
    	// Rename attribute keys to match Cloud Logging structured log format
    	switch a.Key {
    	case slog.LevelKey:
    		a.Key = "severity"
    		// Map slog.Level string values to Cloud Logging LogSeverity
    		// https://cloud.google.com/logging/docs/reference/v2/rest/v2/LogEntry#LogSeverity
    		if level := a.Value.Any().(slog.Level); level == slog.LevelWarn {
    			a.Value = slog.StringValue("WARNING")
    		}
    	case slog.TimeKey:
    		a.Key = "timestamp"
    	case slog.MessageKey:
    		a.Key = "message"
    	}
    	return a
    }
    

    Añadir instrumentación al servidor HTTP

    Para añadir instrumentación de trazas y métricas a las solicitudes gestionadas por el servidor HTTP, usa OpenTelemetry. En el siguiente ejemplo se usa el controlador otelhttp para propagar el contexto y para la instrumentación de métricas y de seguimiento:

    func runServer() error {
    	handleHTTP("/single", handleSingle)
    	handleHTTP("/multi", handleMulti)
    
    	return http.ListenAndServe(":8080", nil)
    }
    
    // handleHTTP handles the http HandlerFunc on the specified route, and uses
    // otelhttp for context propagation, trace instrumentation, and metric
    // instrumentation.
    func handleHTTP(route string, handleFn http.HandlerFunc) {
    	instrumentedHandler := otelhttp.NewHandler(otelhttp.WithRouteTag(route, handleFn), route)
    
    	http.Handle(route, instrumentedHandler)
    }
    

    En el código anterior, el controlador otelhttp usa las instancias globales TracerProvider, MeterProvider y TextMapPropagator. La función setupOpenTelemetry configura estas instancias.

    Vincular intervalos de traza con registros y métricas

    Para vincular los intervalos de servidor y de cliente, y para asociar métricas y registros, pasa la instancia de Go Context a la solicitud HTTP y cuando escribas registros. En el siguiente ejemplo se muestra un controlador de ruta que extrae la instancia de Go Context y la transfiere al registrador y a la función callSingle, que realiza una solicitud HTTP saliente:

    func handleMulti(w http.ResponseWriter, r *http.Request) {
    	subRequests := 3 + rand.Intn(4)
    	// Write a structured log with the request context, which allows the log to
    	// be linked with the trace for this request.
    	slog.InfoContext(r.Context(), "handle /multi request", slog.Int("subRequests", subRequests))
    
    	err := computeSubrequests(r, subRequests)
    	if err != nil {
    		http.Error(w, err.Error(), http.StatusBadGateway)
    		return
    	}
    
    	fmt.Fprintln(w, "ok")
    }
    

    En el código anterior, la llamada a la función r.Context() obtiene el Context de Go de la solicitud HTTP.

    Añadir instrumentación al cliente HTTP

    Para insertar el contexto de la traza en las solicitudes HTTP salientes y añadir la instrumentación de la traza y las métricas, llama a la función otelhttp.Get. En el siguiente ejemplo, la función callSingle realiza esta acción:

    func callSingle(ctx context.Context) error {
    	// otelhttp.Get makes an http GET request, just like net/http.Get.
    	// In addition, it records a span, records metrics, and propagates context.
    	res, err := otelhttp.Get(ctx, "http://localhost:8080/single")
    	if err != nil {
    		return err
    	}
    
    	return res.Body.Close()
    }
    

    En el código anterior, el controlador otelhttp usa las instancias globales TracerProvider, MeterProvider y TextMapPropagator. La función setupOpenTelemetry configura estas instancias.

    Escribir registros estructurados

    Para escribir registros estructurados que se vinculen a un rastreo, usa el paquete de registro estructurado de Go, slog, y pasa la instancia de Go Context al registrador. La instancia de Go Context es obligatoria si quieres vincular un registro a un intervalo. Por ejemplo, en la siguiente instrucción se muestra cómo llamar al método InfoContext de slog y cómo añadir el campo subRequests a la instancia JSON:

    slog.InfoContext(r.Context(), "handle /multi request", slog.Int("subRequests", subRequests))
    

    Ejecutar una aplicación de ejemplo configurada para recoger telemetría

    La aplicación de ejemplo usa formatos independientes del proveedor, como JSON para los registros y OTLP para las métricas y los rastreos. Para enrutar la telemetría a Google Cloud, este ejemplo usa OpenTelemetry Collector configurado con exportadores de Google. El generador de carga de la aplicación envía solicitudes a las rutas de la aplicación.

    Descargar y desplegar la aplicación

    Para ejecutar la muestra, haz lo siguiente:

    1. In the Google Cloud console, activate Cloud Shell.

      Activate Cloud Shell

      At the bottom of the Google Cloud console, a Cloud Shell session starts and displays a command-line prompt. Cloud Shell is a shell environment with the Google Cloud CLI already installed and with values already set for your current project. It can take a few seconds for the session to initialize.

    2. Clona el repositorio:

      git clone https://github.com/GoogleCloudPlatform/golang-samples
      
    3. Ve al directorio de OpenTelemetry:

      cd golang-samples/opentelemetry/instrumentation
      
    4. Compila y ejecuta el ejemplo:

      docker compose up --abort-on-container-exit
      

      Si no estás ejecutando la aplicación en Cloud Shell, hazlo con la variable de entorno GOOGLE_APPLICATION_CREDENTIALS que apunte a un archivo de credenciales. Las credenciales de aplicación predeterminadas proporcionan un archivo de credenciales en $HOME/.config/gcloud/application_default_credentials.json.

      # Set environment variables
      export GOOGLE_CLOUD_PROJECT="PROJECT_ID"
      export GOOGLE_APPLICATION_CREDENTIALS="$HOME/.config/gcloud/application_default_credentials.json"
      export USERID="$(id -u)"
      
      # Run
      docker compose -f docker-compose.yaml -f docker-compose.creds.yaml up --abort-on-container-exit
      
    5. Ver tus métricas

      La instrumentación de OpenTelemetry en la aplicación de ejemplo genera métricas de Prometheus que puedes ver con el explorador de métricas:

      • Prometheus/http_server_duration/histogram Registra la duración de las solicitudes del servidor y almacena los resultados en un histograma.

      • Prometheus/http_server_request_content_length_total/counter registra la longitud del contenido de la solicitud de las rutas HTTP /multi y /single. Las mediciones de esta métrica son acumulativas, lo que significa que cada valor representa el total desde que se empezó a recoger valores.

      • Prometheus/http_server_response_content_length_total/counter registra la longitud del contenido de la respuesta de las rutas HTTP /multi y /single. Las mediciones de esta métrica son acumulativas.

      Para ver las métricas generadas por la aplicación de ejemplo, haz lo siguiente:
      1. En la Google Cloud consola, ve a la página  Explorador de métricas:

        Ve al explorador de métricas.

        Si usas la barra de búsqueda para encontrar esta página, selecciona el resultado cuya sección sea Monitorización.

      2. En la barra de herramientas de la Google Cloud consola, selecciona tu Google Cloud proyecto. En las configuraciones de App Hub, selecciona el proyecto host de App Hub o el proyecto de gestión de la carpeta habilitada para aplicaciones.
      3. En el elemento Métrica, despliega el menú Seleccionar una métrica, introduce http_server en la barra de filtros y, a continuación, usa los submenús para seleccionar un tipo de recurso y una métrica específicos:
        1. En el menú Recursos activos, selecciona Destino de Prometheus.
        2. En el menú Categorías de métricas activas, selecciona Http.
        3. En el menú Métricas activas, seleccione una métrica.
        4. Haz clic en Aplicar.
      4. Configure cómo se ven los datos.

        Cuando las mediciones de una métrica son acumulativas, Explorador de métricas normaliza automáticamente los datos medidos por el periodo de alineación, lo que hace que el gráfico muestre una tasa. Para obtener más información, consulta Tipos, clases y conversiones.

        Cuando se miden valores enteros o dobles, como con las dos métricas counter, el explorador de métricas suma automáticamente todas las series temporales. Para ver los datos de las rutas HTTP /multi y /single, defina el primer menú de la entrada Agregación como Ninguna.

        Para obtener más información sobre cómo configurar un gráfico, consulta el artículo Seleccionar métricas al utilizar el explorador de métricas.

      Ver tus trazas

      Los datos de la traza pueden tardar varios minutos en estar disponibles. Por ejemplo, cuando tu proyecto recibe datos de traza, es posible que Google Cloud Observability tenga que crear una base de datos para almacenar esos datos. La creación de la base de datos puede tardar unos minutos. Durante ese periodo, no se podrá ver ningún dato de seguimiento.

      Para ver los datos de la traza, haz lo siguiente:

      1. En la Google Cloud consola, ve a la página Explorador de trazas:

        Ir a Explorador de trazas

        También puedes encontrar esta página mediante la barra de búsqueda.

      2. En la sección de la tabla de la página, seleccione una fila con el nombre del intervalo /multi.
      3. En el gráfico de Gantt del panel Detalles del rastreo, selecciona el intervalo etiquetado como /multi.

        Se abrirá un panel con información sobre la solicitud HTTP. Estos detalles incluyen el método, el código de estado, el número de bytes y el agente de usuario de la persona que llama.

      4. Para ver los registros asociados a este rastreo, selecciona la pestaña Registros y eventos.

        En la pestaña se muestran los registros individuales. Para ver los detalles de la entrada de registro, despliégala. También puede hacer clic en Ver registros y consultar el registro con el Explorador de registros.

      Para obtener más información sobre cómo usar el explorador de Cloud Trace, consulta Buscar y explorar trazas.

      Consultar los registros

      En Explorador de registros, puedes inspeccionar tus registros y ver las trazas asociadas, si las hay.

      1. En la Google Cloud consola, ve a la página Explorador de registros:

        Ve al Explorador de registros.

        Si usas la barra de búsqueda para encontrar esta página, selecciona el resultado cuya sección sea Registro.

      2. Busca un registro con la descripción handle /multi request.

        Para ver los detalles del registro, despliega la entrada. En el campo jsonPayload, hay una entrada llamada subRequests. Esta entrada se ha añadido mediante una instrucción de la función handleMulti.

      3. En una entrada de registro con el mensaje "handle /multi request", haz clic en Trazas y, a continuación, selecciona Ver detalles de la traza.

        Se abre el panel Detalles de la traza, donde se muestra la traza seleccionada.

        Los datos de registro pueden estar disponibles varios minutos antes que los datos de la traza. Si se produce un error al ver los datos de seguimiento, ya sea buscando un seguimiento por ID o siguiendo los pasos de esta tarea, espera uno o dos minutos y vuelve a intentarlo.

      Para obtener más información sobre cómo usar el Explorador de registros, consulta el artículo Ver registros con el Explorador de registros.

      Siguientes pasos