github.com/telepresenceio/telepresence/v2@v2.20.0-pro.6.0.20240517030216-236ea954e789/pkg/tracing/tracer.go (about) 1 package tracing 2 3 import ( 4 "context" 5 "fmt" 6 "net/http" 7 "strings" 8 "time" 9 10 "go.opentelemetry.io/otel" 11 "go.opentelemetry.io/otel/attribute" 12 "go.opentelemetry.io/otel/exporters/otlp/otlptrace" 13 "go.opentelemetry.io/otel/propagation" 14 "go.opentelemetry.io/otel/sdk/resource" 15 tracesdk "go.opentelemetry.io/otel/sdk/trace" 16 semconv "go.opentelemetry.io/otel/semconv/v1.10.0" 17 "go.opentelemetry.io/otel/trace/noop" 18 "google.golang.org/grpc" 19 "google.golang.org/protobuf/types/known/emptypb" 20 21 "github.com/datawire/dlib/dhttp" 22 "github.com/datawire/dlib/dlog" 23 "github.com/telepresenceio/telepresence/rpc/v2/common" 24 ) 25 26 func setupTracer(ctx context.Context, componentName string, client otlptrace.Client, extraAttributes ...attribute.KeyValue) (*tracesdk.TracerProvider, error) { 27 exp, err := otlptrace.New(ctx, client) 28 if err != nil { 29 return nil, err 30 } 31 r, err := resource.New(ctx, 32 // We use these instead of resource.WithProcess() because the ProcessOwner detector 33 // can break when running as a user without a username (e.g. UID 1000) 34 resource.WithProcessCommandArgs(), 35 resource.WithProcessExecutableName(), 36 resource.WithProcessExecutablePath(), 37 resource.WithProcessPID(), 38 resource.WithProcessRuntimeDescription(), 39 resource.WithProcessRuntimeName(), 40 resource.WithProcessRuntimeVersion(), 41 resource.WithContainer(), 42 resource.WithHost(), 43 resource.WithOS(), 44 resource.WithAttributes(semconv.ServiceNameKey.String(componentName)), 45 resource.WithAttributes(extraAttributes...), 46 resource.WithTelemetrySDK(), 47 ) 48 if err != nil { 49 return nil, err 50 } 51 tp := tracesdk.NewTracerProvider( 52 // Always be sure to batch in production. 53 tracesdk.WithBatcher(exp), 54 tracesdk.WithSampler(tracesdk.AlwaysSample()), 55 // Record information about this application in a Resource. 56 tracesdk.WithResource(r), 57 ) 58 otel.SetTextMapPropagator(propagation.NewCompositeTextMapPropagator(propagation.TraceContext{}, propagation.Baggage{})) 59 otel.SetTracerProvider(tp) 60 return tp, nil 61 } 62 63 type TraceServer struct { 64 common.UnimplementedTracingServer 65 shim *otlpShim 66 tp *tracesdk.TracerProvider 67 } 68 69 func NewTraceServer(ctx context.Context, componentName string, extraAttributes ...attribute.KeyValue) (*TraceServer, error) { 70 client := &otlpShim{} 71 tp, err := setupTracer(ctx, componentName, client, extraAttributes...) 72 if err != nil { 73 return nil, err 74 } 75 76 return &TraceServer{ 77 tp: tp, 78 shim: client, 79 }, nil 80 } 81 82 func (ts *TraceServer) Shutdown(ctx context.Context) { 83 ctx, cancel := context.WithTimeout(ctx, time.Second*5) 84 defer cancel() 85 if err := ts.tp.Shutdown(ctx); err != nil { 86 dlog.Error(ctx, "error shutting down tracer: ", err) 87 } 88 otel.SetTracerProvider(noop.NewTracerProvider()) 89 } 90 91 func (ts *TraceServer) ServeGrpc(ctx context.Context, port uint16) error { 92 opts := []grpc.ServerOption{} 93 grpcHandler := grpc.NewServer(opts...) 94 sc := &dhttp.ServerConfig{ 95 Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 96 if r.ProtoMajor == 2 && strings.HasPrefix(r.Header.Get("Content-Type"), "application/grpc") { 97 grpcHandler.ServeHTTP(w, r) 98 } else { 99 w.WriteHeader(http.StatusNotFound) 100 } 101 }), 102 } 103 104 common.RegisterTracingServer(grpcHandler, ts) 105 106 return sc.ListenAndServe(ctx, fmt.Sprintf("0.0.0.0:%d", port)) 107 } 108 109 func (ts *TraceServer) DumpTraces(ctx context.Context, _ *emptypb.Empty) (*common.Trace, error) { 110 err := ts.tp.ForceFlush(ctx) 111 if err != nil { 112 return nil, fmt.Errorf("failed to force flush tracer: %w", err) 113 } 114 b := ts.shim.dumpTraces() 115 return &common.Trace{ 116 TraceData: b, 117 }, nil 118 }