Traces und Messwerte mit Go generieren

In diesem Dokument wird beschrieben, wie Sie eine Go-Anwendung ändern, um Trace- und Messwertdaten mit dem Open-Source-Framework OpenTelemetry zu erfassen und strukturierte JSON-Logs in das Standard-Out-Format zu schreiben. Dieses Dokument enthält auch Informationen zu einer Beispiel-App, die Sie installieren und ausführen können. Die Anwendung ist so konfiguriert, dass Messwerte, Traces und Logs generiert werden.

Weitere Informationen zur Instrumentierung finden Sie in den folgenden Dokumenten:

Kontext

Context von OpenTelemetry ist ein Mechanismus zur Ausführung von Ausführungsbereichen. innerhalb eines Prozesses in allen APIs. Eine wichtige Verwendung des Kontexts besteht darin, die aktuelle aktive Span zu übergeben, damit sie geändert oder beim Erstellen als übergeordnetes Element für neue Spans referenziert werden kann. Zusammenfassung:

  • Der Kontext bezieht sich auf den Mechanismus, mit dem Werte auf Ausführungsebene, einschließlich der aktuellen aktiven Spanne, über APIs innerhalb eines Prozesses weitergegeben werden.

  • Span-Kontext ist ein unveränderliches Objekt in jedem Span, enthält die Trace-ID, die Span-ID sowie Flags und Status für den Trace.

  • Die Weitergabe ist der Mechanismus, mit dem der Kontext zwischen Diensten und Prozessen übertragen wird.

Der Bereich context.Context der Go-Standardbibliothek enthält ebenfalls über API-Grenzen hinweg nutzen. Normalerweise empfangen Handlerfunktionen auf einem Server eine eingehende Context und leiten sie über die Aufrufabfolge an alle Clients weiter, die ausgehende Anfragen stellen.

Die Standardbibliothek context.Context von Go wird als Implementierung von OpenTelemetry Context in Go.

Hinweise

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

Enable the APIs

Anwendung für die Erfassung von Traces, Messwerten und Logs instrumentieren

Führen Sie die folgenden Schritte aus, um Ihre Anwendung so zu instrumentieren, dass Trace- und Messwertdaten erfasst und strukturiertes JSON in die Standardausgabe geschrieben wird, wie in den folgenden Abschnitten dieses Dokuments beschrieben:

  1. Hauptfunktion konfigurieren
  2. OpenTelemetry konfigurieren
  3. Strukturiertes Logging konfigurieren
  4. Dem HTTP-Server Instrumentierung hinzufügen
  5. Trace-Spans mit Logs und Messwerten verknüpfen
  6. Dem HTTP-Client Instrumentierung hinzufügen
  7. Strukturierte Logs schreiben

Hauptfunktion konfigurieren

Um die Anwendung für das Schreiben strukturierter Logs, das Erfassen von Messwerten und mit OpenTelemetry zu verfolgen, aktualisieren Sie die Funktion main auf Konfigurieren Sie das strukturierte Go-Logging-Paket slog und konfigurieren Sie OpenTelemetry.

Das folgende Codebeispiel zeigt eine main-Funktion, die zwei Hilfsfunktionen aufruft: setupLogging() und setupOpenTelemetry(). Diese Helfer das Logging-Paket und OpenTelemetry konfigurieren.

Wenn Sie sich das vollständige Beispiel ansehen möchten, klicken Sie auf  Mehr und wählen Sie dann Auf GitHub ansehen aus.

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)
	}
}

Nachdem Sie das Logging-Paket konfiguriert haben, müssen Sie die Go-Context an den Logging-Client übergeben, um Ihre Protokolle mit Ihren Trace-Daten zu verknüpfen. Weitere Informationen siehe Abschnitt Strukturierte Logs schreiben Dokument.

OpenTelemetry konfigurieren

Wenn Sie Traces und Messwerte mit dem OTLP-Protokoll erfassen und exportieren möchten, konfigurieren Sie die globalen Instanzen TracerProvider und MeterProvider. Das folgende Codebeispiel veranschaulicht die setupOpenTelemetry-Funktion. Dieser wird über die Funktion main aufgerufen:

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
}

Im vorherigen Codebeispiel wird die globale TextMapPropagator so konfiguriert, dass das Format W3C Trace Context zur Weitergabe von Trace-Kontext verwendet wird. Diese Konfiguration gewährleistet, dass Spans die richtige hierarchische Beziehung in einem Trace haben.

Damit alle ausstehenden Telemetriedaten geleert und Verbindungen ordnungsgemäß geschlossen werden, gibt die setupOpenTelemetry-Funktion eine Funktion namens shutdown zurück, die diese Aktionen ausführt.

Strukturiertes Logging konfigurieren

So fügen Sie die Trace-Informationen in die geschriebenen JSON-formatierten Logs ein: Konfigurieren Sie das strukturierte Go-Logging-Paket slog für die Standardausgabe. Das folgende Codebeispiel veranschaulicht die setupLogging-Funktion. Dieser wird über die Funktion main aufgerufen:

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))
}

Im vorherigen Code wird die Funktion handlerWithSpanContext aufgerufen, die Informationen aus der Context-Instanz extrahiert und diese Informationen als Attribute einem Log hinzufügt. Mit diesen Attributen können Sie dann ein Log mit einem Trace korrelieren:

  • logging.googleapis.com/trace: Der Ressourcenname des Trace, das mit dem Logeintrag verknüpft ist.
  • logging.googleapis.com/spanId: Die Span-ID mit dem Trace, das dem Logeintrag zugeordnet ist.
  • logging.googleapis.com/trace_sampled: Der Wert dieses Felds muss true oder false sein.

Weitere Informationen zu diesen Feldern finden Sie in der LogEntry-Struktur.

func handlerWithSpanContext(handler slog.Handler) *spanContextLogHandler {
	return &spanContextLogHandler{Handler: handler}
}

// spanContextLogHandler is an 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 golang 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
}

Instrumentierung zum HTTP-Server hinzufügen

Verwenden Sie OpenTelemetry, um den vom HTTP-Server verarbeiteten Anfragen Trace- und Messwertinstrumentierung hinzuzufügen. Im folgenden Beispiel wird der otelhttp-Handler verwendet. für die Weitergabe von Kontext sowie für die Trace- und Messwertinstrumentierung:

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)
}

Im vorherigen Code verwendet der otelhttp-Handler die globalen Instanzen TracerProvider, MeterProvider und TextMapPropagator. Die Die setupOpenTelemetry-Funktion konfiguriert diese Instanzen.

Trace-Spans mit Logs und Messwerten verknüpfen

Um Server- und Client-Spans zu verknüpfen und Messwerte und Logs zu verknüpfen, übergeben Sie den Rufen Sie die Context-Instanz zur HTTP-Anfrage auf und wenn Sie Logs schreiben. Das folgende Beispiel zeigt einen Routen-Handler, der die Go-Context-Instanz extrahiert und an den Logging-Dienst und die callSingle-Funktion weitergibt, die eine ausgehende HTTP-Anfrage stellt:

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")
}

Im vorherigen Code ruft der Funktionsaufruf r.Context() das Go-Context aus der HTTP-Anfrage.

Fügen Sie dem HTTP-Client Instrumentierung hinzu

Um den Trace-Kontext in ausgehende HTTP-Anfragen einzufügen und Trace- und Messwertinstrumentierung die Funktion otelhttp.Get aufrufen. Im folgenden Beispiel führt die Funktion callSingle diese Aktion aus:

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()
}

Im vorherigen Code verwendet der otelhttp-Handler die globalen Instanzen TracerProvider, MeterProvider und TextMapPropagator. Die Die setupOpenTelemetry-Funktion konfiguriert diese Instanzen.

Strukturierte Logs schreiben

Wenn Sie strukturierte Logs schreiben möchten, die mit einem Trace verknüpft sind, verwenden Sie das strukturierte Logging von Go Paket slog und übergeben Sie die Go Context-Instanz an den Listener. Die Go-Instanz Context ist erforderlich, wenn Sie ein Log mit einem Span verknüpfen möchten. Die folgende Anweisung zeigt beispielsweise, wie die Methode InfoContext für slog aufgerufen wird und wie das Feld subRequests der JSON-Instanz hinzugefügt wird:

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

Beispielanwendung ausführen, die für die Erfassung von Telemetriedaten konfiguriert ist

Die Beispielanwendung verwendet anbieterneutrale Formate, einschließlich JSON für Logs und OTLP für Messwerte und Traces. In diesem Beispiel wird die OpenTelemetry-Collector verwendet, die mit Google-Exportern konfiguriert wurde, um die Telemetrie an Google Cloud weiterzuleiten. Der Lastgenerator in der Anwendung gibt Anfragen an die Routen der Anwendung aus.

Anwendung herunterladen und bereitstellen

So führen Sie das Beispiel aus:

  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. Klonen Sie das Repository:

    git clone https://github.com/GoogleCloudPlatform/golang-samples
    
  3. Rufen Sie das OpenTelemetry-Verzeichnis auf:

    cd golang-samples/opentelemetry/instrumentation
    
  4. Erstellen Sie das Beispiel und führen Sie es aus:

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

    Wenn Sie Cloud Shell nicht ausführen, führen Sie die Anwendung mit der Umgebungsvariable GOOGLE_APPLICATION_CREDENTIALS aus, die auf eine Datei mit Anmeldedaten verweist. Standardanmeldedaten für Anwendungen stellt eine Datei mit Anmeldedaten unter $HOME/.config/gcloud/application_default_credentials.json bereit.

    # 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
    

Messwerte ansehen

Die OpenTelemetry-Instrumentierung in der Beispielanwendung generiert Prometheus-Messwerte, die Sie mit dem Metrics Explorer aufrufen können:

  • Prometheus/http_server_duration/histogram zeichnet die Dauer von Serveranfragen auf und speichert die Ergebnisse in einem Histogramm.

  • Prometheus/http_server_request_content_length_total/counter zeichnet die Länge des Anfrageinhalts für die HTTP-Routen /multi und /single auf. Die Messungen für diesen Messwert sind kumulativ. Das bedeutet, dass jeder Wert die Summe seit Beginn der Werterfassung darstellt.

  • Prometheus/http_server_response_content_length_total/counter zeichnet auf Länge des Antwortinhalts für die HTTP-Routen /multi und /single Die Messwerte für diesen Messwert sind kumulativ.

So rufen Sie die von der Beispielanwendung generierten Messwerte auf:
  1. Rufen Sie in der Google Cloud Console die Seite Metrics Explorer auf.

    Zum Metrics Explorer

    Wenn Sie diese Seite über die Suchleiste suchen, wählen Sie das Ergebnis aus, dessen Zwischenüberschrift Monitoring ist.

  2. Maximieren Sie im Element Messwert das Menü Messwert auswählen, geben Sie http_server in die Filterleiste ein und wählen Sie dann über die Untermenüs einen bestimmten Ressourcentyp und Messwert aus:
    1. Wählen Sie im Menü Aktive Ressourcen die Option Prometheus-Ziel aus.
    2. Wählen Sie im Menü Aktive Messwertkategorien die Option Http aus.
    3. Wählen Sie im Menü Aktive Messwerte einen Messwert aus.
    4. Klicken Sie auf Anwenden.
  3. Konfigurieren Sie, wie die Daten angezeigt werden.

    Wenn die Messungen für einen Messwert kumulativ sind, normalisiert Metrics Explorer die gemessenen Daten automatisch nach dem Ausrichtungszeitraum. Dadurch wird im Diagramm eine Rate angezeigt. Weitere Informationen finden Sie unter Arten, Typen und Umwandlungen.

    Wenn ganzzahlige oder doppelte Werte gemessen werden, z. B. mit den beiden counter-Messwerten, summiert der Metrics Explorer automatisch alle Zeitachsen. Wenn Sie die Daten für die HTTP-Routen /multi und /single aufrufen möchten, legen Sie im ersten Menü des Eintrags Aggregation die Option Keine fest.

    Weitere Informationen zum Konfigurieren eines Diagramms finden Sie unter Messwerte bei Verwendung von Metrics Explorer auswählen.

Traces ansehen

So rufen Sie Ihre Trace-Daten auf:

  1. Rufen Sie in der Google Cloud Console die Seite Trace Explorer auf:

    Zum Trace Explorer

    Sie können diese Seite auch über die Suchleiste finden.

  2. Wählen Sie im Streudiagramm einen Trace mit dem URI /multi aus.
  3. Wählen Sie im Gantt-Diagramm im Bereich Trace-Details den Span mit der Bezeichnung /multi aus.

    Ein Steuerfeld mit Informationen zur HTTP-Anfrage wird geöffnet. Zu diesen Details gehören die Methode, der Statuscode, die Anzahl der Byte und der User-Agent des Aufrufers.

  4. Wählen Sie den Tab Logs und Ereignisse aus, um die mit diesem Trace verknüpften Logs aufzurufen.

    Auf dem Tab werden einzelne Logs angezeigt. Maximieren Sie den Logeintrag, um die Details anzusehen. Sie können auch auf Logs ansehen klicken und das Log mit dem Log-Explorer aufrufen.

Weitere Informationen zur Verwendung von Cloud Trace-Explorer finden Sie unter Traces suchen und untersuchen.

Logs ansehen

Im Log-Explorer können Sie Ihre Logs prüfen und sich auch die zugehörigen Traces ansehen, sofern vorhanden.

  1. Rufen Sie in der Google Cloud Console die Seite Log-Explorer auf.

    Zum Log-Explorer

    Wenn Sie diese Seite über die Suchleiste suchen, wählen Sie das Ergebnis aus, dessen Zwischenüberschrift Monitoring ist.

  2. Suchen Sie ein Log mit der Beschreibung handle /multi request.

    Erweitern Sie den Logeintrag, um die Details des Logs aufzurufen. Im Feld jsonPayload befindet sich ein Eintrag mit der Bezeichnung subRequests. Dieser Eintrag wurde durch eine Anweisung in der handleMulti-Funktion hinzugefügt.

  3. Klicken Sie auf Traces für einen Logeintrag mit der Nachricht "handle /multi request" und wählen Sie dann Trace-Details anzeigen aus.

    Der Bereich Trace-Details wird geöffnet und zeigt den ausgewählten Trace an.

Weitere Informationen zur Verwendung des Log-Explorers finden Sie unter Logs mit dem Log-Explorer ansehen.

Nächste Schritte