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  }