הגדרה של איסוף נתוני מעקב באמצעות OpenTelemetry

במאמר הזה מוסבר איך להגדיר מעקב בצד הלקוח ומעקב מקצה לקצה באמצעות OpenTelemetry. כדי להצטרף למעקב מקצה לקצה, צריך קודם להגדיר מעקב בצד הלקוח. מידע נוסף זמין במאמר סקירה כללית על איסוף נתוני מעקב.

לפני שמתחילים

  • כדי לוודא שלחשבון השירות שבו האפליקציה משתמשת יש את ההרשאות שנדרשות להגדרת איסוף נתוני מעקב, צריך לבקש מהאדמין להקצות לחשבון השירות שבו האפליקציה משתמשת בפרויקט את תפקיד ה-IAM‏ Cloud Trace Agent (roles/cloudtrace.agent).

  • מוודאים ש-Cloud Trace API מופעל בפרויקט. מידע נוסף על הפעלת ממשקי API זמין במאמר הפעלת ממשקי API.

הגדרת מעקב בצד הלקוח

כדי להגדיר מעקב בצד הלקוח, צריך לייצא את נתוני המעקב. אפשר לייצא עקבות ל-collector או ישירות ל-backend של כלי observability. אפשר להגדיר מעקב באמצעות ממשקי OpenTelemetry API.

ייצוא עקבות אל מאסף באמצעות ממשקי OpenTelemetry API

כדי לייצא עקבות אל Collector באמצעות ממשקי OpenTelemetry API, צריך להגדיר את OpenTelemetry SDK ואת OLTP exporter:

  1. מוסיפים את יחסי התלות הנדרשים לאפליקציה באמצעות הקוד הבא:

    Java

    <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-trace</artifactId>
    </dependency>
    <dependency>
      <groupId>io.opentelemetry</groupId>
      <artifactId>opentelemetry-exporter-otlp</artifactId>
    </dependency>

    Go

    go.opentelemetry.io/otel v1.28.0
    go.opentelemetry.io/otel/sdk v1.28.0
    go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.28.0

    Node.js

    "@opentelemetry/exporter-trace-otlp-grpc": "^0.57.0",
    "@opentelemetry/sdk-trace-base": "^1.26.0",
    "@opentelemetry/sdk-trace-node": "^1.26.0",

    Python

    pip install opentelemetry-api opentelemetry-sdk
    pip install opentelemetry-exporter-otlp

  2. מגדירים את האובייקט OpenTelemetry ומפעילים את המעקב.

    Java

    Resource resource = Resource
        .getDefault().merge(Resource.builder().put("service.name", "My App").build());
    
    OtlpGrpcSpanExporter otlpGrpcSpanExporter =
        OtlpGrpcSpanExporter
            .builder()
            .setEndpoint(otlpEndpoint) // Replace with your OTLP endpoint
            .build();
    
    // Using a batch span processor
    // You can use `.setScheduleDelay()`, `.setExporterTimeout()`,
    // `.setMaxQueueSize`(), and `.setMaxExportBatchSize()` to further customize.
    BatchSpanProcessor otlpGrpcSpanProcessor =
        BatchSpanProcessor.builder(otlpGrpcSpanExporter).build();
    
    // Create a new tracer provider
    sdkTracerProvider = SdkTracerProvider.builder()
        // Use Otlp exporter or any other exporter of your choice.
        .addSpanProcessor(otlpGrpcSpanProcessor)
        .setResource(resource)
        .setSampler(Sampler.traceIdRatioBased(0.1))
        .build();
    
    // Export to a collector that is expecting OTLP using gRPC.
    OpenTelemetry openTelemetry = OpenTelemetrySdk.builder()
        .setTracerProvider(sdkTracerProvider).build();
    
    // Enable OpenTelemetry traces before Injecting OpenTelemetry
    SpannerOptions.enableOpenTelemetryTraces();
    
    // Inject OpenTelemetry object via Spanner options or register as GlobalOpenTelemetry.
    SpannerOptions options = SpannerOptions.newBuilder()
        .setOpenTelemetry(openTelemetry)
        .build();
    Spanner spanner = options.getService();

    Go

    
    // Ensure that your Go runtime version is supported by the OpenTelemetry-Go
    // compatibility policy before enabling OpenTelemetry instrumentation.
    
    // 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.
    
    ctx := context.Background()
    
    // Create a new resource to uniquely identify the application
    res, err := resource.Merge(resource.Default(),
    	resource.NewWithAttributes(semconv.SchemaURL,
    		semconv.ServiceName("My App"),
    		semconv.ServiceVersion("0.1.0"),
    	))
    if err != nil {
    	log.Fatal(err)
    }
    
    // Create a new OTLP exporter.
    defaultOtlpEndpoint := "http://localhost:4317" // Replace with the endpoint on which OTLP collector is running
    traceExporter, err := otlptracegrpc.New(ctx, otlptracegrpc.WithEndpoint(defaultOtlpEndpoint))
    if err != nil {
    	log.Fatal(err)
    }
    
    // Create a new tracer provider
    tracerProvider := sdktrace.NewTracerProvider(
    	sdktrace.WithResource(res),
    	sdktrace.WithBatcher(traceExporter),
    	sdktrace.WithSampler(sdktrace.TraceIDRatioBased(0.1)),
    )
    
    // Register tracer provider as global
    otel.SetTracerProvider(tracerProvider)

    Node.js

    const {NodeTracerProvider} = require('@opentelemetry/sdk-trace-node');
    const {
      OTLPTraceExporter,
    } = require('@opentelemetry/exporter-trace-otlp-grpc');
    const {
      BatchSpanProcessor,
      TraceIdRatioBasedSampler,
    } = require('@opentelemetry/sdk-trace-base');
    const {Resource} = require('@opentelemetry/resources');
    const {Spanner} = require('@google-cloud/spanner');
    
    // Define a Resource with service metadata
    const resource = new Resource({
      'service.name': 'my-service',
      'service.version': '1.0.0',
    });
    
    // Create an OTLP gRPC trace exporter
    const traceExporter = new OTLPTraceExporter({
      url: 'http://localhost:4317', // Default OTLP gRPC endpoint
    });
    
    // Create a provider with a custom sampler
    const provider = new NodeTracerProvider({
      sampler: new TraceIdRatioBasedSampler(1.0), // Sample 100% of traces
      resource,
      spanProcessors: [new BatchSpanProcessor(traceExporter)],
    });
    
    // Uncomment following line to register tracerProvider globally or pass it in Spanner object
    // provider.register();
    
    // Create the Cloud Spanner Client.
    const spanner = new Spanner({
      projectId: projectId,
      observabilityOptions: {
        tracerProvider: provider,
        enableExtendedTracing: true,
        enableEndToEndTracing: true,
      },
    });
    

    Python

    # Setup OpenTelemetry, trace and OTLP exporter.
    tracer_provider = TracerProvider(sampler=ALWAYS_ON)
    otlp_exporter = OTLPSpanExporter(endpoint="http://localhost:4317")
    tracer_provider.add_span_processor(BatchSpanProcessor(otlp_exporter))
    
    # Setup the Cloud Spanner Client.
    spanner_client = spanner.Client(
        project_id,
        observability_options=dict(tracer_provider=tracer_provider, enable_extended_tracing=True, enable_end_to_end_tracing=True),
    )

ייצוא ישירות אל קצה עורפי של יכולת צפייה באמצעות OpenTelemetry APIs

כדי להגדיר את ספריות הלקוח של Spanner כך שייצאו ישירות טווחי מעקב ל-Cloud Trace או ל-backend של ספק אחר של שירותי ניראות (observability), פועלים לפי השלבים הבאים:

  1. מוסיפים את יחסי התלות הנדרשים לאפליקציה באמצעות הקוד הבא:

    Java

    <dependency>
    <groupId>com.google.cloud</groupId>
    <artifactId>google-cloud-spanner</artifactId>
    </dependency>
    <dependency>
    <groupId>io.opentelemetry</groupId>
    <artifactId>opentelemetry-api</artifactId>
    </dependency>
    <dependency>
    <groupId>io.opentelemetry</groupId>
    <artifactId>opentelemetry-sdk</artifactId>
    </dependency>
    <dependency>
    <groupId>io.opentelemetry</groupId>
    <artifactId>opentelemetry-sdk-common</artifactId>
    </dependency>
    <dependency>
    <groupId>io.opentelemetry</groupId>
    <artifactId>opentelemetry-sdk-trace</artifactId>
    </dependency>
    <dependency>
    <groupId>com.google.cloud.opentelemetry</groupId>
    <artifactId>exporter-trace</artifactId>
    <version>0.30.0</version>
    </dependency>

    Go

    go.opentelemetry.io/otel v1.28.0
    go.opentelemetry.io/otel/sdk v1.28.0
    github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/trace v1.24.1

    Node.js

    "@google-cloud/opentelemetry-cloud-trace-exporter": "^2.4.1",
    "@opentelemetry/sdk-trace-base": "^1.26.0",
    "@opentelemetry/sdk-trace-node": "^1.26.0",

    Python

    pip install opentelemetry-api opentelemetry-sdk
    pip install opentelemetry-exporter-gcp-trace

  2. מגדירים את האובייקט OpenTelemetry ומפעילים את המעקב.

    Java

    Resource resource = Resource
        .getDefault().merge(Resource.builder().put("service.name", "My App").build());
    
    SpanExporter traceExporter = TraceExporter.createWithConfiguration(
        TraceConfiguration.builder().setProjectId(projectId).build()
    );
    
    // Using a batch span processor
    // You can use `.setScheduleDelay()`, `.setExporterTimeout()`,
    // `.setMaxQueueSize`(), and `.setMaxExportBatchSize()` to further customize.
    BatchSpanProcessor otlpGrpcSpanProcessor =
        BatchSpanProcessor.builder(traceExporter).build();
    
    // Create a new tracer provider
    sdkTracerProvider = SdkTracerProvider.builder()
        // Use Otlp exporter or any other exporter of your choice.
        .addSpanProcessor(otlpGrpcSpanProcessor)
        .setResource(resource)
        .setSampler(Sampler.traceIdRatioBased(0.1))
        .build();
    
    // Export to a collector that is expecting OTLP using gRPC.
    OpenTelemetry openTelemetry = OpenTelemetrySdk.builder()
        .setTracerProvider(sdkTracerProvider).build();
    
    // Enable OpenTelemetry traces before Injecting OpenTelemetry
    SpannerOptions.enableOpenTelemetryTraces();
    
    // Inject OpenTelemetry object via Spanner options or register it as global object.
    // To register as the global OpenTelemetry object,
    // use "OpenTelemetrySdk.builder()....buildAndRegisterGlobal()".
    SpannerOptions options = SpannerOptions.newBuilder()
        .setOpenTelemetry(openTelemetry)
        .build();
    Spanner spanner = options.getService();

    Go

    
    // Ensure that your Go runtime version is supported by the OpenTelemetry-Go
    // compatibility policy before enabling OpenTelemetry instrumentation.
    
    // 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.
    
    // Create a new resource to uniquely identify the application
    res, err := resource.Merge(resource.Default(),
    	resource.NewWithAttributes(semconv.SchemaURL,
    		semconv.ServiceName("My App"),
    		semconv.ServiceVersion("0.1.0"),
    	))
    if err != nil {
    	log.Fatal(err)
    }
    
    // Create a new cloud trace exporter
    exporter, err := traceExporter.New(traceExporter.WithProjectID(projectID))
    if err != nil {
    	log.Fatal(err)
    }
    
    // Create a new tracer provider
    tracerProvider := sdktrace.NewTracerProvider(
    	sdktrace.WithResource(res),
    	sdktrace.WithBatcher(exporter),
    	sdktrace.WithSampler(sdktrace.TraceIDRatioBased(0.1)),
    )
    
    // Register tracer provider as global
    otel.SetTracerProvider(tracerProvider)

    Node.js

    
    const {NodeTracerProvider} = require('@opentelemetry/sdk-trace-node');
    const {
      TraceExporter,
    } = require('@google-cloud/opentelemetry-cloud-trace-exporter');
    const {
      BatchSpanProcessor,
      TraceIdRatioBasedSampler,
    } = require('@opentelemetry/sdk-trace-base');
    const {Spanner} = require('@google-cloud/spanner');
    
    const traceExporter = new TraceExporter({projectId: projectId});
    
    // Create a provider with a custom sampler
    const provider = new NodeTracerProvider({
      sampler: new TraceIdRatioBasedSampler(1.0), // Sample 100% of traces
      spanProcessors: [new BatchSpanProcessor(traceExporter)],
    });
    
    // Uncomment following line to register tracerProvider globally or pass it in Spanner object
    // provider.register();
    
    // Set global propagator to propogate the trace context for end to end tracing.
    const {propagation} = require('@opentelemetry/api');
    const {W3CTraceContextPropagator} = require('@opentelemetry/core');
    propagation.setGlobalPropagator(new W3CTraceContextPropagator());
    
    const spanner = new Spanner({
      projectId: projectId,
      observabilityOptions: {
        tracerProvider: provider,
        // Enable extended tracing to allow your SQL statements to be annotated.
        enableExtendedTracing: true,
        // Enable end to end tracing.
        enableEndToEndTracing: true,
      },
    });
    

    Python

    # Setup OpenTelemetry, trace and Cloud Trace exporter.
    tracer_provider = TracerProvider(sampler=ALWAYS_ON)
    trace_exporter = CloudTraceSpanExporter(project_id=project_id)
    tracer_provider.add_span_processor(BatchSpanProcessor(trace_exporter))
    
    # Setup the Cloud Spanner Client.
    spanner_client = spanner.Client(
        project_id,
        observability_options=dict(tracer_provider=tracer_provider, enable_extended_tracing=True, enable_end_to_end_tracing=True),
    )
    

הגדרת מעקב מקצה לקצה

בקטע הזה מוסבר איך להגדיר מעקב מקצה לקצה בספריות לקוח של Spanner:

  1. מוסיפים את יחסי התלות הנדרשים לאפליקציה באמצעות הקוד הבא:

    Java

    התלות הקיימת במעקב בצד הלקוח מספיקה להגדרת מעקב מקצה לקצה. לא צריך להוסיף תלות כלשהי.

    Go

    בנוסף ליחסי התלות שנדרשים למעקב בצד הלקוח, נדרש גם יחס התלות הבא:

    go.opentelemetry.io/otel/propagation v1.28.0

    Node.js

    התלות הקיימת במעקב בצד הלקוח מספיקה להגדרת מעקב מקצה לקצה. לא צריך להוסיף תלות כלשהי.

    Python

    התלות הקיימת במעקב בצד הלקוח מספיקה להגדרת מעקב מקצה לקצה. לא צריך להוסיף תלות כלשהי.

  2. מצטרפים למעקב מקצה לקצה.

    Java

    SpannerOptions options = SpannerOptions.newBuilder()
      .setOpenTelemetry(openTelemetry)
      .setEnableEndToEndTracing(/* enableEndtoEndTracing= */ true)
      .build();

    Go

    כדי להפעיל את האפשרות הזו, משתמשים באפשרות EnableEndToEndTracing בהגדרות הלקוח.

    client, _ := spanner.NewClientWithConfig(ctx, "projects/test-project/instances/test-instance/databases/test-db", spanner.ClientConfig{
    SessionPoolConfig: spanner.DefaultSessionPoolConfig,
    EnableEndToEndTracing:      true,
    }, clientOptions...)

    Node.js

    const spanner = new Spanner({
    projectId: projectId,
    observabilityOptions: {
    tracerProvider: openTelemetryTracerProvider,
    enableEndToEndTracing: true,
    }
    })

    Python

    observability_options = dict(
    tracer_provider=tracer_provider,
    enable_end_to_end_tracing=True,
    )
    spanner = spanner.Client(project_id, observability_options=observability_options)

  3. מגדירים את ההפצה של הקשר המעקב ב-OpenTelemetry.

    Java

    OpenTelemetry openTelemetry = OpenTelemetrySdk.builder()
      .setTracerProvider(sdkTracerProvider)
      .setPropagators(ContextPropagators.create(W3CTraceContextPropagator.getInstance()))
      .buildAndRegisterGlobal();

    Go

    // Register the TraceContext propagator globally.
    otel.SetTextMapPropagator(propagation.TraceContext{})

    Node.js

    const {propagation} = require('@opentelemetry/api');
    const {W3CTraceContextPropagator} = require('@opentelemetry/core');
    propagation.setGlobalPropagator(new W3CTraceContextPropagator());

    Python

    from opentelemetry.propagate import set_global_textmap
    from opentelemetry.trace.propagation.tracecontext import TraceContextTextMapPropagator
    set_global_textmap(TraceContextTextMapPropagator())

מאפיינים של מעקב מקצה לקצה

המידע שמופיע בנתוני מעקב מקצה לקצה:

שם המאפיין תיאור
service.name ערך המאפיין הוא תמיד spanner_api_frontend.
cloud.region Google Cloud האזור בענן של חזית ה-API של Spanner שמשרת את בקשת האפליקציה שלכם.
gcp.spanner.server.query.fingerprint ערך המאפיין הוא טביעת האצבע של השאילתה. כדי לנפות באג בשאילתה הזו, אפשר לעיין בעמודה TEXT_FINGERPRINT בטבלאות של נתוני השאילתות.
gcp.spanner.server.paxos.participantcount מספר המשתתפים בעסקה. מידע נוסף זמין במאמר Life of Spanner Reads & Writes.
gcp.spanner.isolation_level ערך המאפיין הוא רמת הבידוד של הטרנזקציה. הערכים האפשריים הם SERIALIZABLE ו-REPEATABLE_READ. מידע נוסף זמין במאמר סקירה כללית על רמות בידוד.

תיעוד עקבות לדוגמה

בעקבות מקצה לקצה אפשר לראות את הפרטים הבאים:

  • זמן האחזור בין האפליקציה שלכם לבין Spanner. אתם יכולים לחשב את זמן האחזור ברשת כדי לבדוק אם יש בעיות ברשת.
  • האזור ב-Cloud שבו פועל ה-frontend של Spanner API, שממנו מוגשות הבקשות של האפליקציה. אפשר להשתמש בזה כדי לבדוק קריאות חוצות אזורים בין האפליקציה לבין Spanner.

בדוגמה הבאה, הבקשה של האפליקציה מטופלת על ידי הקצה הקדמי של ה-API של Spanner באזור us-west1, וזמן האחזור ברשת הוא 8.542 אלפיות השנייה (55.47 אלפיות השנייה – 46.928 אלפיות השנייה).

צפייה במעקב מקצה לקצה.

המאמרים הבאים