ResolveSpec/pkg/tracing/tracing.go
Hein b741958895
Some checks failed
Tests / Run Tests (1.23.x) (push) Has been cancelled
Tests / Run Tests (1.24.x) (push) Has been cancelled
Tests / Lint Code (push) Has been cancelled
Tests / Build (push) Has been cancelled
Code sanity fixes, added middlewares
2025-12-08 08:28:43 +02:00

147 lines
3.9 KiB
Go

package tracing
import (
"context"
"fmt"
"net/http"
"go.opentelemetry.io/otel"
"go.opentelemetry.io/otel/attribute"
"go.opentelemetry.io/otel/exporters/otlp/otlptrace"
"go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc"
"go.opentelemetry.io/otel/propagation"
"go.opentelemetry.io/otel/sdk/resource"
sdktrace "go.opentelemetry.io/otel/sdk/trace"
semconv "go.opentelemetry.io/otel/semconv/v1.4.0"
"go.opentelemetry.io/otel/trace"
)
var tracer trace.Tracer
// Config holds tracing configuration
type Config struct {
ServiceName string
ServiceVersion string
Endpoint string // OTLP endpoint (e.g., "localhost:4317")
Enabled bool
}
// InitTracer initializes the OpenTelemetry tracer
func InitTracer(config Config) (func(context.Context) error, error) {
if !config.Enabled {
// Return no-op shutdown function
return func(context.Context) error { return nil }, nil
}
ctx := context.Background()
// Create OTLP exporter
client := otlptracegrpc.NewClient(
otlptracegrpc.WithEndpoint(config.Endpoint),
otlptracegrpc.WithInsecure(), // Use WithTLSCredentials in production
)
exporter, err := otlptrace.New(ctx, client)
if err != nil {
return nil, fmt.Errorf("failed to create OTLP exporter: %w", err)
}
// Create resource
res, err := resource.New(ctx,
resource.WithAttributes(
semconv.ServiceNameKey.String(config.ServiceName),
semconv.ServiceVersionKey.String(config.ServiceVersion),
),
)
if err != nil {
return nil, fmt.Errorf("failed to create resource: %w", err)
}
// Create trace provider
tp := sdktrace.NewTracerProvider(
sdktrace.WithBatcher(exporter),
sdktrace.WithResource(res),
sdktrace.WithSampler(sdktrace.AlwaysSample()),
)
// Set global trace provider
otel.SetTracerProvider(tp)
// Set global propagator
otel.SetTextMapPropagator(propagation.NewCompositeTextMapPropagator(
propagation.TraceContext{},
propagation.Baggage{},
))
// Get tracer
tracer = tp.Tracer(config.ServiceName)
// Return shutdown function
return tp.Shutdown, nil
}
// Middleware returns an HTTP middleware that creates spans for requests
func Middleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if tracer == nil {
next.ServeHTTP(w, r)
return
}
// Extract context from request headers
ctx := otel.GetTextMapPropagator().Extract(r.Context(), propagation.HeaderCarrier(r.Header))
// Start span
ctx, span := tracer.Start(ctx, r.Method+" "+r.URL.Path,
trace.WithSpanKind(trace.SpanKindServer),
trace.WithAttributes(
semconv.HTTPMethodKey.String(r.Method),
semconv.HTTPURLKey.String(r.URL.String()),
semconv.HTTPTargetKey.String(r.URL.Path),
semconv.HTTPSchemeKey.String(r.URL.Scheme),
semconv.NetHostNameKey.String(r.Host),
),
)
defer span.End()
// Create new request with updated context
r = r.WithContext(ctx)
next.ServeHTTP(w, r)
})
}
// StartSpan starts a new span with the given name
func StartSpan(ctx context.Context, name string, attrs ...attribute.KeyValue) (context.Context, trace.Span) {
if tracer == nil {
return ctx, trace.SpanFromContext(ctx)
}
return tracer.Start(ctx, name, trace.WithAttributes(attrs...))
}
// SpanFromContext returns the current span from the context
func SpanFromContext(ctx context.Context) trace.Span {
return trace.SpanFromContext(ctx)
}
// AddEvent adds an event to the current span
func AddEvent(ctx context.Context, name string, attrs ...attribute.KeyValue) {
span := trace.SpanFromContext(ctx)
span.AddEvent(name, trace.WithAttributes(attrs...))
}
// SetAttributes sets attributes on the current span
func SetAttributes(ctx context.Context, attrs ...attribute.KeyValue) {
span := trace.SpanFromContext(ctx)
span.SetAttributes(attrs...)
}
// RecordError records an error on the current span
func RecordError(ctx context.Context, err error) {
if err == nil {
return
}
span := trace.SpanFromContext(ctx)
span.RecordError(err)
}