feat(epic1): complete OpenTelemetry integration and add verification documentation

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.
This commit is contained in:
2025-11-05 18:20:15 +01:00
parent 30320304f6
commit fde01bfc73
13 changed files with 873 additions and 54 deletions

View File

@@ -13,10 +13,12 @@ import (
"git.dcentral.systems/toolz/goplt/internal/infra/database"
loggerimpl "git.dcentral.systems/toolz/goplt/internal/logger"
"git.dcentral.systems/toolz/goplt/internal/metrics"
"git.dcentral.systems/toolz/goplt/internal/observability"
"git.dcentral.systems/toolz/goplt/internal/server"
"git.dcentral.systems/toolz/goplt/pkg/config"
"git.dcentral.systems/toolz/goplt/pkg/errorbus"
"git.dcentral.systems/toolz/goplt/pkg/logger"
"go.opentelemetry.io/otel/trace"
"go.uber.org/fx"
)
@@ -156,6 +158,54 @@ func ProvideMetrics() fx.Option {
})
}
// ProvideTracer creates an FX option that provides the OpenTelemetry tracer.
func ProvideTracer() fx.Option {
return fx.Provide(func(cfg config.ConfigProvider, lc fx.Lifecycle) (trace.TracerProvider, error) {
enabled := cfg.GetBool("tracing.enabled")
if !enabled {
// Return no-op tracer
return trace.NewNoopTracerProvider(), nil
}
serviceName := cfg.GetString("tracing.service_name")
if serviceName == "" {
serviceName = "platform"
}
serviceVersion := cfg.GetString("tracing.service_version")
if serviceVersion == "" {
serviceVersion = "1.0.0"
}
env := cfg.GetString("environment")
if env == "" {
env = "development"
}
otlpEndpoint := cfg.GetString("tracing.otlp_endpoint")
tp, err := observability.InitTracer(context.Background(), observability.Config{
Enabled: enabled,
ServiceName: serviceName,
ServiceVersion: serviceVersion,
Environment: env,
OTLPEndpoint: otlpEndpoint,
})
if err != nil {
return nil, fmt.Errorf("failed to initialize tracer: %w", err)
}
// Register lifecycle hook to shutdown tracer
lc.Append(fx.Hook{
OnStop: func(ctx context.Context) error {
return observability.ShutdownTracer(ctx, tp)
},
})
return tp, nil
})
}
// ProvideHTTPServer creates an FX option that provides the HTTP server.
func ProvideHTTPServer() fx.Option {
return fx.Provide(func(
@@ -164,9 +214,10 @@ func ProvideHTTPServer() fx.Option {
healthRegistry *health.Registry,
metricsRegistry *metrics.Metrics,
errorBus errorbus.ErrorPublisher,
tracer trace.TracerProvider,
lc fx.Lifecycle,
) (*server.Server, error) {
srv, err := server.NewServer(cfg, log, healthRegistry, metricsRegistry, errorBus)
srv, err := server.NewServer(cfg, log, healthRegistry, metricsRegistry, errorBus, tracer)
if err != nil {
return nil, fmt.Errorf("failed to create HTTP server: %w", err)
}
@@ -194,7 +245,7 @@ func ProvideHTTPServer() fx.Option {
}
// CoreModule returns an FX option that provides all core services.
// This includes configuration, logging, database, error bus, health checks, metrics, and HTTP server.
// This includes configuration, logging, database, error bus, health checks, metrics, tracing, and HTTP server.
func CoreModule() fx.Option {
return fx.Options(
ProvideConfig(),
@@ -203,6 +254,7 @@ func CoreModule() fx.Option {
ProvideErrorBus(),
ProvideHealthRegistry(),
ProvideMetrics(),
ProvideTracer(),
ProvideHTTPServer(),
)
}