Story 1.6: OpenTelemetry Distributed Tracing - Implemented tracer initialization with stdout (dev) and OTLP (prod) exporters - Added HTTP request instrumentation via Gin middleware - Integrated trace ID correlation in structured logs - Added tracing configuration to config files - Registered tracer provider in DI container Documentation and Setup: - Created Docker Compose setup for PostgreSQL database - Added comprehensive Epic 1 summary with verification instructions - Added Epic 0 summary with verification instructions - Linked summaries in documentation index and epic READMEs - Included detailed database testing instructions - Added Docker Compose commands and troubleshooting guide All Epic 1 stories (1.1-1.6) are now complete. Story 1.7 depends on Epic 2.
95 lines
2.5 KiB
Go
95 lines
2.5 KiB
Go
package observability
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"os"
|
|
|
|
"go.opentelemetry.io/otel"
|
|
"go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp"
|
|
"go.opentelemetry.io/otel/exporters/stdout/stdouttrace"
|
|
"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.26.0"
|
|
"go.opentelemetry.io/otel/trace"
|
|
)
|
|
|
|
// Config holds OpenTelemetry configuration.
|
|
type Config struct {
|
|
Enabled bool
|
|
ServiceName string
|
|
ServiceVersion string
|
|
Environment string
|
|
OTLPEndpoint string
|
|
}
|
|
|
|
// InitTracer initializes OpenTelemetry tracing.
|
|
func InitTracer(ctx context.Context, cfg Config) (trace.TracerProvider, error) {
|
|
if !cfg.Enabled {
|
|
// Return a no-op tracer provider
|
|
return trace.NewNoopTracerProvider(), nil
|
|
}
|
|
|
|
// Create resource with service information
|
|
res, err := resource.New(ctx,
|
|
resource.WithAttributes(
|
|
semconv.ServiceNameKey.String(cfg.ServiceName),
|
|
semconv.ServiceVersionKey.String(cfg.ServiceVersion),
|
|
semconv.DeploymentEnvironmentKey.String(cfg.Environment),
|
|
),
|
|
)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to create resource: %w", err)
|
|
}
|
|
|
|
var exporter sdktrace.SpanExporter
|
|
|
|
if cfg.Environment == "production" && cfg.OTLPEndpoint != "" {
|
|
// Production: export to OTLP collector
|
|
exporter, err = otlptracehttp.New(ctx,
|
|
otlptracehttp.WithEndpoint(cfg.OTLPEndpoint),
|
|
otlptracehttp.WithInsecure(), // Use WithTLSClientConfig for secure connections
|
|
)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to create OTLP exporter: %w", err)
|
|
}
|
|
} else {
|
|
// Development: export to stdout
|
|
exporter, err = stdouttrace.New(
|
|
stdouttrace.WithPrettyPrint(),
|
|
stdouttrace.WithWriter(os.Stdout),
|
|
)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to create stdout exporter: %w", err)
|
|
}
|
|
}
|
|
|
|
// Create tracer provider
|
|
tp := sdktrace.NewTracerProvider(
|
|
sdktrace.WithBatcher(exporter),
|
|
sdktrace.WithResource(res),
|
|
sdktrace.WithSampler(sdktrace.AlwaysSample()), // Sample all traces in dev, can be adjusted for prod
|
|
)
|
|
|
|
// Set global tracer provider
|
|
otel.SetTracerProvider(tp)
|
|
|
|
// Set global propagator for trace context
|
|
otel.SetTextMapPropagator(propagation.NewCompositeTextMapPropagator(
|
|
propagation.TraceContext{},
|
|
propagation.Baggage{},
|
|
))
|
|
|
|
return tp, nil
|
|
}
|
|
|
|
// ShutdownTracer gracefully shuts down the tracer provider.
|
|
func ShutdownTracer(ctx context.Context, tp trace.TracerProvider) error {
|
|
if ttp, ok := tp.(*sdktrace.TracerProvider); ok {
|
|
return ttp.Shutdown(ctx)
|
|
}
|
|
return nil
|
|
}
|
|
|