package tracing // todo, review: // https://github.com/ttys3/tracing-go/blob/main/tracing.go#L136 import ( "context" "crypto/tls" "crypto/x509" "os" "go.ntppool.org/common/logger" "go.ntppool.org/common/version" "go.opentelemetry.io/otel" "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/exporters/otlp/otlptrace" "go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp" "go.opentelemetry.io/otel/propagation" "go.opentelemetry.io/otel/sdk/resource" otelsdktrace "go.opentelemetry.io/otel/sdk/trace" semconv "go.opentelemetry.io/otel/semconv/v1.24.0" "go.opentelemetry.io/otel/trace" ) // https://github.com/open-telemetry/opentelemetry-go/blob/main/exporters/otlp/otlptrace/otlptracehttp/example_test.go type TpShutdownFunc func(ctx context.Context) error func Tracer() trace.Tracer { traceProvider := otel.GetTracerProvider() return traceProvider.Tracer("ntppool-tracer") } func Start(ctx context.Context, spanName string, opts ...trace.SpanStartOption) (context.Context, trace.Span) { return Tracer().Start(ctx, spanName, opts...) } type GetClientCertificate func(*tls.CertificateRequestInfo) (*tls.Certificate, error) type TracerConfig struct { ServiceName string Environment string Endpoint string EndpointURL string CertificateProvider GetClientCertificate RootCAs *x509.CertPool } var emptyTpShutdownFunc = func(_ context.Context) error { return nil } func InitTracer(ctx context.Context, cfg *TracerConfig) (TpShutdownFunc, error) { log := logger.Setup() // exporter, err := srv.newStdoutExporter(os.Stdout) var err error var exporter otelsdktrace.SpanExporter if otlpEndPoint := os.Getenv("OTEL_EXPORTER_OTLP_ENDPOINT"); len(otlpEndPoint) > 0 || len(cfg.Endpoint) > 0 { exporter, err = newOLTPExporter(ctx, cfg) } if err != nil { return emptyTpShutdownFunc, err } if exporter == nil { log.Warn("tracing not configured") return emptyTpShutdownFunc, nil } resource, err := newResource(cfg) if err != nil { return nil, err } tp := otelsdktrace.NewTracerProvider( otelsdktrace.WithSampler(otelsdktrace.AlwaysSample()), otelsdktrace.WithBatcher(exporter), otelsdktrace.WithResource(resource), ) otel.SetTracerProvider(tp) otel.SetTextMapPropagator( propagation.NewCompositeTextMapPropagator( propagation.TraceContext{}, // W3C Trace Context format; https://www.w3.org/TR/trace-context/ propagation.Baggage{}, ), ) return tp.Shutdown, nil } func newOLTPExporter(ctx context.Context, cfg *TracerConfig) (otelsdktrace.SpanExporter, error) { log := logger.Setup() opts := []otlptracehttp.Option{ otlptracehttp.WithCompression(otlptracehttp.GzipCompression), } if cfg.CertificateProvider != nil { log.InfoContext(ctx, "setting up cert provider") opts = append(opts, otlptracehttp.WithTLSClientConfig(&tls.Config{ GetClientCertificate: cfg.CertificateProvider, RootCAs: cfg.RootCAs, })) } if len(cfg.Endpoint) > 0 { opts = append(opts, otlptracehttp.WithEndpoint(cfg.Endpoint)) } if len(cfg.EndpointURL) > 0 { opts = append(opts, otlptracehttp.WithEndpointURL(cfg.EndpointURL)) } client := otlptracehttp.NewClient(opts...) exporter, err := otlptrace.New(ctx, client) if err != nil { log.ErrorContext(ctx, "creating OTLP trace exporter", "err", err) } return exporter, err } // func (srv *Server) newStdoutExporter(w io.Writer) (sdktrace.SpanExporter, error) { // return stdouttrace.New( // stdouttrace.WithWriter(w), // // Use human-readable output. // stdouttrace.WithPrettyPrint(), // // Do not print timestamps for the demo. // stdouttrace.WithoutTimestamps(), // ) // } // newResource returns a resource describing this application. func newResource(cfg *TracerConfig) (*resource.Resource, error) { log := logger.Setup() defaultResource := resource.Default() log.Debug("default semconv", "url", defaultResource.SchemaURL()) newResource := resource.NewWithAttributes( semconv.SchemaURL, semconv.ServiceNameKey.String(cfg.ServiceName), semconv.ServiceVersionKey.String(version.Version()), attribute.String("environment", cfg.Environment), ) log.Debug("new resource semconv", "url", newResource.SchemaURL()) r, err := resource.Merge( defaultResource, newResource, ) if err != nil { log.Error("could not setup otel resource", "err", err, "default", defaultResource.SchemaURL(), "local", newResource.SchemaURL(), ) } return r, nil }