devops/otel-trace-exemplar/recursive/main.go (175 lines of code) (raw):

// Copyright 2024 Google LLC // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package main import ( "context" "fmt" "log" "math/rand" "net/http" "os" "strconv" "time" "go.opentelemetry.io/contrib/detectors/gcp" "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp" "go.opentelemetry.io/otel" "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc" "go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc" "go.opentelemetry.io/otel/metric" "go.opentelemetry.io/otel/propagation" 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.4.0" ) const ( spanInterval = 50 * time.Millisecond genInterval = 10 * time.Second ) var ( OTLPEndpoint = "localhost:4317" ServiceName = "default-service" histogram metric.Int64Histogram ) // newResource returns a OpenTelemetry resource object of the service func newResource(name, version, env string) (*resource.Resource, error) { return resource.New( context.Background(), // Using resource detector changes the target monitored resource name from // generic node to generic task. resource.WithDetectors(gcp.NewDetector()), resource.WithTelemetrySDK(), resource.WithAttributes( semconv.ServiceNameKey.String(name), semconv.ServiceVersionKey.String(version), semconv.DeploymentEnvironmentKey.String(env), ), ) } func initTracer() (*sdktrace.TracerProvider, error) { e := os.Getenv("OTLP_ENDPOINT") if e == "" { e = OTLPEndpoint } // OTLP exporter config for Collector (using default config) exporter, err := otlptracegrpc.New( context.Background(), otlptracegrpc.WithEndpoint(e), otlptracegrpc.WithInsecure(), ) if err != nil { return nil, err } sname := os.Getenv("SERVICE_NAME") if sname == "" { sname = ServiceName } res, err := newResource(sname, "1.0.0", "demo") if err != nil { log.Printf("failed to create resource: %v", err) return nil, err } tp := sdktrace.NewTracerProvider( sdktrace.WithSampler(sdktrace.AlwaysSample()), sdktrace.WithBatcher(exporter), sdktrace.WithResource(res), ) otel.SetTracerProvider(tp) otel.SetTextMapPropagator(propagation.TraceContext{}) return tp, nil } func initMeter() (*sdkmetric.MeterProvider, error) { e := os.Getenv("OTLP_ENDPOINT") if e == "" { e = OTLPEndpoint } exporter, err := otlpmetricgrpc.New( context.Background(), otlpmetricgrpc.WithEndpoint(e), otlpmetricgrpc.WithInsecure(), ) if err != nil { return nil, err } sname := os.Getenv("SERVICE_NAME") if sname == "" { sname = ServiceName } res, err := newResource(sname, "1.0.0", "demo") if err != nil { return nil, err } mp := sdkmetric.NewMeterProvider( sdkmetric.WithReader(sdkmetric.NewPeriodicReader( exporter, sdkmetric.WithInterval(1*time.Minute), )), sdkmetric.WithResource(res), ) otel.SetMeterProvider(mp) return mp, nil } func main() { log.Print("starting tracer provider") tp, err := initTracer() if err != nil { log.Fatalf("failed to start OpenTelemetry Trace config: %v", err) } mp, err := initMeter() if err != nil { log.Fatalf("failed to start OpenTelemetry Meter config: %v", err) } defer func() { if err := tp.Shutdown(context.Background()); err != nil { log.Fatalf("failed to shutdown TraceProvider: %v", err) } if err := mp.Shutdown(context.Background()); err != nil { log.Fatalf("failed to shutdown MeterProvider: %v", err) } }() log.Print("launch telemetry data generator") meter := mp.Meter("demo") histogram, err = meter.Int64Histogram( "exemplar/root.latency", metric.WithDescription("latency of the root handler"), metric.WithUnit("ms"), ) if err != nil { log.Fatalf("failed to initialize histogram: %v", err) } http.HandleFunc("/_hello", func(w http.ResponseWriter, _ *http.Request) { w.WriteHeader(http.StatusOK) w.Write([]byte("ok")) }) otelHandler := otelhttp.NewHandler(http.HandlerFunc(root), "client.handler") http.Handle("/root", otelHandler) if err := http.ListenAndServe(":8080", nil); err != nil { log.Fatalf("failed to start server: %v", err) } } func root(w http.ResponseWriter, r *http.Request) { ctx := r.Context() q := r.URL.Query() n := rand.Intn(10) + 1 nstr := q.Get("n") if nstr != "" { n, _ = strconv.Atoi(nstr) } ctx, span := otel.Tracer("handler").Start(ctx, "sample.root") start := time.Now() foo(ctx, n) duration := time.Since(start) histogram.Record( ctx, int64(duration.Milliseconds()), ) span.End() fmt.Fprintf(w, "run %vth fib", n) } func foo(ctx context.Context, n int) { if n == 0 { return } ctx, span := otel.Tracer("handler").Start(ctx, "sample.foo") span.SetAttributes(attribute.Key("iteration").Int(n)) time.Sleep(spanInterval) span.AddEvent(fmt.Sprintf("mid-interval-%v", n)) time.Sleep(spanInterval) foo(ctx, n-1) span.End() }