Benutzerdefinierte clientseitige Messwerte mit OpenTelemetry erfassen

In diesem Dokument wird beschrieben, wie Sie benutzerdefinierte Clientmesswerte mit OpenTelemetry erfassen. Benutzerdefinierte Clientmesswerte sind über die Clientbibliotheken für Java und Go verfügbar.

Mit benutzerdefinierten clientseitigen Messwerten können Sie die Ursache von Latenz in Ihrem System ermitteln. Weitere Informationen finden Sie unter Latenzpunkte in einer Spanner-Anfrage.

Spanner-Clientbibliotheken stellen auch Statistiken und Traces mithilfe des OpenTelemetry-Frameworks für die Beobachtbarkeit bereit. Weitere Informationen finden Sie unter Trace-Erfassung mit OpenTelemetry einrichten.

OpenTelemetry ist ein Open-Source-Framework und ‑Toolkit für Observability, mit dem Sie Telemetriedaten wie Traces, Messwerte und Logs erstellen und verwalten können.

Hinweise

Sie müssen das OpenTelemetry SDK mit den entsprechenden Optionen zum Exportieren Ihrer Telemetriedaten konfigurieren. Wir empfehlen die Verwendung des OTLP-Exporters (OpenTelemetry Protocol).

Wenn Sie benutzerdefinierte clientseitige Messwerte mit OpenTelemetry einrichten möchten, müssen Sie das OpenTelemetry SDK und den OTLP-Exporter konfigurieren:

  1. Fügen Sie Ihrer Anwendung mit dem folgenden Code die erforderlichen Abhängigkeiten hinzu:

    Java

    <dependencyManagement>
      <dependencies>
        <dependency>
          <groupId>com.google.cloud</groupId>
          <artifactId>libraries-bom</artifactId>
          <version>26.32.0</version>
          <type>pom</type>
          <scope>import</scope>
        </dependency>
        <dependency>
          <groupId>io.opentelemetry</groupId>
          <artifactId>opentelemetry-bom</artifactId>
          <version>1.35.0</version>
          <type>pom</type>
          <scope>import</scope>
        </dependency>
      </dependencies>
    </dependencyManagement>
    
    <dependencies>
      <dependency>
        <groupId>com.google.cloud</groupId>
        <artifactId>google-cloud-spanner</artifactId>
      </dependency>
      <dependency>
        <groupId>io.opentelemetry</groupId>
        <artifactId>opentelemetry-sdk</artifactId>
      </dependency>
      <dependency>
        <groupId>io.opentelemetry</groupId>
        <artifactId>opentelemetry-sdk-metrics</artifactId>
      </dependency>
      <dependency>
        <groupId>io.opentelemetry</groupId>
        <artifactId>opentelemetry-sdk-trace</artifactId>
      </dependency>
      <dependency>
        <groupId>io.opentelemetry</groupId>
        <artifactId>opentelemetry-exporter-otlp</artifactId>
      </dependency>
    </dependencies>

    Go

    go.opentelemetry.io/otel v1.34.0
    go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.28.0
    go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.28.0
    go.opentelemetry.io/otel/metric v1.34.0
    go.opentelemetry.io/otel/sdk v1.34.0
    go.opentelemetry.io/otel/sdk/metric v1.34.0
  2. Erstellen Sie ein OpenTelemetry-Objekt mit dem OTLP-Exporter und fügen Sie es mit SpannerOptions in Spanner ein:

    Java

    // Enable OpenTelemetry metrics and traces before Injecting OpenTelemetry
    SpannerOptions.enableOpenTelemetryMetrics();
    SpannerOptions.enableOpenTelemetryTraces();
    
    // Create a new meter provider
    SdkMeterProvider sdkMeterProvider = SdkMeterProvider.builder()
        // Use Otlp exporter or any other exporter of your choice.
        .registerMetricReader(
            PeriodicMetricReader.builder(OtlpGrpcMetricExporter.builder().build()).build())
        .build();
    
    // Create a new tracer provider
    SdkTracerProvider sdkTracerProvider = SdkTracerProvider.builder()
        // Use Otlp exporter or any other exporter of your choice.
        .addSpanProcessor(SimpleSpanProcessor.builder(OtlpGrpcSpanExporter
            .builder().build()).build())
            .build();
    
    // Configure OpenTelemetry object using Meter Provider and Tracer Provider
    OpenTelemetry openTelemetry = OpenTelemetrySdk.builder()
        .setMeterProvider(sdkMeterProvider)
        .setTracerProvider(sdkTracerProvider)
        .build();
    
    // Inject OpenTelemetry object via Spanner options or register as GlobalOpenTelemetry.
    SpannerOptions options = SpannerOptions.newBuilder()
        .setOpenTelemetry(openTelemetry)
        .build();
    Spanner spanner = options.getService();
    
    DatabaseClient dbClient = spanner
        .getDatabaseClient(DatabaseId.of(projectId, instanceId, databaseId));
    
    captureGfeMetric(dbClient);
    captureQueryStatsMetric(openTelemetry, dbClient);
    
    // Close the providers to free up the resources and export the data. */
    sdkMeterProvider.close();
    sdkTracerProvider.close();

    Go

    // Ensure that your Go runtime version is supported by the OpenTelemetry-Go compatibility policy before enabling OpenTelemetry instrumentation.
    // Refer to compatibility here https://github.com/googleapis/google-cloud-go/blob/main/debug.md#opentelemetry
    
    import (
    	"context"
    	"fmt"
    	"io"
    	"log"
    	"strconv"
    	"strings"
    
    	"cloud.google.com/go/spanner"
    	"go.opentelemetry.io/otel"
    	"go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc"
    	"go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc"
    	"go.opentelemetry.io/otel/metric"
    	sdkmetric "go.opentelemetry.io/otel/sdk/metric"
    	"go.opentelemetry.io/otel/sdk/resource"
    	sdktrace "go.opentelemetry.io/otel/sdk/trace"
    	semconv "go.opentelemetry.io/otel/semconv/v1.24.0"
    	"google.golang.org/api/iterator"
    )
    
    func enableOpenTelemetryMetricsAndTraces(w io.Writer, db string) error {
    	// db = `projects/<project>/instances/<instance-id>/database/<database-id>`
    	ctx := context.Background()
    
    	// Create a new resource to uniquely identify the application
    	res, err := newResource()
    	if err != nil {
    		log.Fatal(err)
    	}
    
    	// Enable OpenTelemetry traces by setting environment variable GOOGLE_API_GO_EXPERIMENTAL_TELEMETRY_PLATFORM_TRACING to the case-insensitive value "opentelemetry" before loading the client library.
    	// Enable OpenTelemetry metrics before injecting meter provider.
    	spanner.EnableOpenTelemetryMetrics()
    
    	// Create a new tracer provider
    	tracerProvider, err := getOtlpTracerProvider(ctx, res)
    	defer tracerProvider.ForceFlush(ctx)
    	if err != nil {
    		log.Fatal(err)
    	}
    	// Register tracer provider as global
    	otel.SetTracerProvider(tracerProvider)
    
    	// Create a new meter provider
    	meterProvider := getOtlpMeterProvider(ctx, res)
    	defer meterProvider.ForceFlush(ctx)
    
    	// Inject meter provider locally via ClientConfig when creating a spanner client or set globally via setMeterProvider.
    	client, err := spanner.NewClientWithConfig(ctx, db, spanner.ClientConfig{OpenTelemetryMeterProvider: meterProvider})
    	if err != nil {
    		return err
    	}
    	defer client.Close()
    	return nil
    }
    
    func getOtlpMeterProvider(ctx context.Context, res *resource.Resource) *sdkmetric.MeterProvider {
    	otlpExporter, err := otlpmetricgrpc.New(ctx)
    	if err != nil {
    		log.Fatal(err)
    	}
    	meterProvider := sdkmetric.NewMeterProvider(
    		sdkmetric.WithResource(res),
    		sdkmetric.WithReader(sdkmetric.NewPeriodicReader(otlpExporter)),
    	)
    	return meterProvider
    }
    
    func getOtlpTracerProvider(ctx context.Context, res *resource.Resource) (*sdktrace.TracerProvider, error) {
    	traceExporter, err := otlptracegrpc.New(ctx)
    	if err != nil {
    		return nil, err
    	}
    
    	tracerProvider := sdktrace.NewTracerProvider(
    		sdktrace.WithResource(res),
    		sdktrace.WithBatcher(traceExporter),
    		sdktrace.WithSampler(sdktrace.AlwaysSample()),
    	)
    
    	return tracerProvider, nil
    }
    
    func newResource() (*resource.Resource, error) {
    	return resource.Merge(resource.Default(),
    		resource.NewWithAttributes(semconv.SchemaURL,
    			semconv.ServiceName("otlp-service"),
    			semconv.ServiceVersion("0.1.0"),
    		))
    }
    

GFE-Latenz erfassen

Die GFE-Latenz (Google Front End) ist die Dauer in Millisekunden zwischen dem Zeitpunkt, an dem das Google-Netzwerk einen Remoteprozeduraufruf vom Client empfängt, und dem Zeitpunkt, an dem das GFE das erste Byte der Antwort empfängt.

Sie können die GFE-Latenz mit dem folgenden Code erfassen:

Java

static void captureGfeMetric(DatabaseClient dbClient) {
  // GFE_latency and other Spanner metrics are automatically collected
  // when OpenTelemetry metrics are enabled.

  try (ResultSet resultSet =
      dbClient
          .singleUse() // Execute a single read or query against Cloud Spanner.
          .executeQuery(Statement.of("SELECT SingerId, AlbumId, AlbumTitle FROM Albums"))) {
    while (resultSet.next()) {
      System.out.printf(
          "%d %d %s", resultSet.getLong(0), resultSet.getLong(1), resultSet.getString(2));
    }
  }
}

Go

// GFE_Latency and other Spanner metrics are automatically collected
// when OpenTelemetry metrics are enabled.
func captureGFELatencyMetric(ctx context.Context, client spanner.Client) error {
	stmt := spanner.Statement{SQL: `SELECT SingerId, AlbumId, AlbumTitle FROM Albums`}
	iter := client.Single().Query(ctx, stmt)
	defer iter.Stop()
	for {
		row, err := iter.Next()
		if err == iterator.Done {
			return nil
		}
		if err != nil {
			return err
		}
		var singerID, albumID int64
		var albumTitle string
		if err := row.Columns(&singerID, &albumID, &albumTitle); err != nil {
			return err
		}
	}
}

Im Beispielcode wird der String spanner/gfe_latency an den Messwertnamen angehängt, wenn er in Cloud Monitoring exportiert wird. Sie können in Cloud Monitoring mit dem angehängten String nach diesem Messwert suchen.

Latenz der Cloud Spanner API-Anfrage erfassen

Die Latenz der Cloud Spanner API-Anfrage ist die Zeit in Sekunden zwischen dem ersten Byte der Clientanfrage, die das Cloud Spanner API-Frontend empfängt, und dem letzten Byte der Antwort, die das Cloud Spanner API-Frontend sendet.

Dieser Latenzmesswert ist als Teil von Cloud Monitoring-Messwerten verfügbar.

Umlauflatenz des Clients erfassen

Die Client-Umlauflatenz ist die Dauer in Millisekunden zwischen dem ersten Byte der Cloud Spanner API-Anfrage, die der Client an die Datenbank sendet (über das GFE und das Cloud Spanner API-Frontend), und dem letzten Byte der Antwort, die der Client von der Datenbank empfängt.

Der Messwert für die Roundtrip-Latenz des Spanner-Clients wird von OpenTelemetry nicht unterstützt. Stattdessen können Sie sich den clientseitigen Messwert für die Vorgangslatenz ansehen. Weitere Informationen finden Sie unter Beschreibungen clientseitiger Messwerte.

Sie können den Messwert auch mit OpenCensus und einer Bridge instrumentieren und die Daten zu OpenTelemetry migrieren.

Abfragelatenz erfassen

Die Abfragelatenz ist die Dauer in Millisekunden, die zum Ausführen von SQL-Abfragen in der Spanner-Datenbank benötigt wird.

Sie können die Abfragelatenz mit dem folgenden Code erfassen:

Java

static void captureQueryStatsMetric(OpenTelemetry openTelemetry, DatabaseClient dbClient) {
  // Register query stats metric.
  // This should be done once before start recording the data.
  Meter meter = openTelemetry.getMeter("cloud.google.com/java");
  DoubleHistogram queryStatsMetricLatencies =
      meter
          .histogramBuilder("spanner/query_stats_elapsed")
          .setDescription("The execution of the query")
          .setUnit("ms")
          .build();

  // Capture query stats metric data.
  try (ResultSet resultSet = dbClient.singleUse()
      .analyzeQuery(Statement.of("SELECT SingerId, AlbumId, AlbumTitle FROM Albums"),
          QueryAnalyzeMode.PROFILE)) {

    while (resultSet.next()) {
      System.out.printf(
          "%d %d %s", resultSet.getLong(0), resultSet.getLong(1), resultSet.getString(2));
    }

    String value = resultSet.getStats().getQueryStats()
        .getFieldsOrDefault("elapsed_time", Value.newBuilder().setStringValue("0 msecs").build())
        .getStringValue();
    double elapsedTime = value.contains("msecs")
        ? Double.parseDouble(value.replaceAll(" msecs", ""))
        : Double.parseDouble(value.replaceAll(" secs", "")) * 1000;
    queryStatsMetricLatencies.record(elapsedTime);
  }
}

Go

func captureQueryStatsMetric(ctx context.Context, mp metric.MeterProvider, client spanner.Client) error {
	meter := mp.Meter(spanner.OtInstrumentationScope)
	// Register query stats metric with OpenTelemetry to record the data.
	// This should be done once before start recording the data.
	queryStats, err := meter.Float64Histogram(
		"spanner/query_stats_elapsed",
		metric.WithDescription("The execution of the query"),
		metric.WithUnit("ms"),
		metric.WithExplicitBucketBoundaries(0.0, 0.01, 0.05, 0.1, 0.3, 0.6, 0.8, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 8.0, 10.0, 13.0,
			16.0, 20.0, 25.0, 30.0, 40.0, 50.0, 65.0, 80.0, 100.0, 130.0, 160.0, 200.0, 250.0,
			300.0, 400.0, 500.0, 650.0, 800.0, 1000.0, 2000.0, 5000.0, 10000.0, 20000.0, 50000.0,
			100000.0),
	)
	if err != nil {
		fmt.Print(err)
	}

	stmt := spanner.Statement{SQL: `SELECT SingerId, AlbumId, AlbumTitle FROM Albums`}
	iter := client.Single().QueryWithStats(ctx, stmt)
	defer iter.Stop()
	for {
		row, err := iter.Next()
		if err == iterator.Done {
			// Record query execution time with OpenTelemetry.
			elapasedTime := iter.QueryStats["elapsed_time"].(string)
			elapasedTimeMs, err := strconv.ParseFloat(strings.TrimSuffix(elapasedTime, " msecs"), 64)
			if err != nil {
				return err
			}
			queryStats.Record(ctx, elapasedTimeMs)
			return nil
		}
		if err != nil {
			return err
		}
		var singerID, albumID int64
		var albumTitle string
		if err := row.Columns(&singerID, &albumID, &albumTitle); err != nil {
			return err
		}
	}
}

Im Beispielcode wird der String spanner/query_stats_elapsed an den Messwertnamen angehängt, wenn er in Cloud Monitoring exportiert wird. Sie können in Cloud Monitoring mit dem angehängten String nach diesem Messwert suchen.

Messwerte im Metrics Explorer aufrufen

  1. Rufen Sie in der Google Cloud Console die Seite „Metrics Explorer“ auf.

    Zum Metrics Explorer

  2. Wählen Sie Ihr Projekt aus.

  3. Klicken Sie auf Messwert auswählen.

  4. Suchen Sie mit den folgenden Strings nach Latenzmesswerten:

    • roundtrip_latency: für den Messwert für die Client-Roundtrip-Latenz.
    • spanner/gfe_latency: für den GFE-Latenzmesswert.
    • spanner/query_stats_elapsed: für den Messwert für die Abfragelatenz.
  5. Wählen Sie den Messwert aus und klicken Sie auf Übernehmen.

Weitere Informationen zum Gruppieren oder Aggregieren von Messwerten finden Sie unter Abfragen mit Menüs erstellen.

Nächste Schritte