Observability with OpenTelemetry
GoApp is instrumented with OpenTelemetry (OTel) to provide comprehensive observability into the application's performance through traces and metrics. This allows developers to monitor, debug, and understand the application's behavior in real-time.
The entire observability stack is configured and managed within the internal/pkg/apm
package.
Overview
The apm
package initializes and configures the necessary OpenTelemetry providers and exporters. It sets up:
- Tracer Provider: For distributed tracing.
- Meter Provider: For collecting metrics.
These providers are then used to create middleware and instrument specific parts of the application, like the HTTP server and the database client.
Distributed Tracing
Distributed tracing allows you to follow a request as it travels through different parts of the application and even across different services. In GoApp, tracing is implemented in two key areas:
1. HTTP Server Middleware
The HTTP server uses the go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp
package to create a middleware. This middleware automatically starts a new trace span for each incoming HTTP request, measures its duration, and captures relevant attributes like the HTTP method, path, and status code.
Configuration can be seen in cmd/server/http/http.go
:
// cmd/server/http/http.go
otelopts := []otelhttp.Option{
// Exclude health checks from traces
otelhttp.WithFilter(func(req *http.Request) bool {
return !strings.HasPrefix(req.URL.Path, "/-/")
}),
// Format span names to reduce cardinality
otelhttp.WithSpanNameFormatter(func(operation string, r *http.Request) string {
wctx := webgo.Context(r)
if wctx == nil {
return r.URL.Path
}
return wctx.Route.Pattern
}),
}
apmMw := apm.NewHTTPMiddleware(otelopts...)
router.Use(func(w http.ResponseWriter, r *http.Request, hf http.HandlerFunc) {
apmMw(hf).ServeHTTP(w, r)
})
2. PostgreSQL Driver Instrumentation
Database queries are also traced using the github.com/exaring/otelpgx
library. It wraps the pgx
database driver to create a child span for every SQL query executed. This allows you to see exactly how much time is spent in the database for any given request.
Configuration is in internal/pkg/postgres/postgres.go
:
// internal/pkg/postgres/postgres.go
pgxconfig.Tracer = otelpgx.NewTracer(
otelpgx.WithTracerProvider(apm.Global().GetTracerProvider()),
)
Metrics
The application collects metrics and exposes them for scraping by a Prometheus server.
Prometheus Exporter
The internal/pkg/apm/prometheus.go
file sets up a Prometheus exporter. This exporter starts a separate HTTP server (by default on port 9090) and exposes a /metrics
endpoint.
// internal/pkg/apm/prometheus.go
func prometheusScraper(opts *Options) {
mux := http.NewServeMux()
mux.Handle("/-/metrics", promhttp.Handler())
server := &http.Server{
Handler: mux,
Addr: fmt.Sprintf(":%d", opts.PrometheusScrapePort),
}
// ... server starts listening
}
Metrics collected include standard ones from the otelhttp
middleware (e.g., request counts, latency histograms) and any custom metrics defined within the application.
Configuration
The observability features are configured via the apm.Options
struct, which is populated from the main application config.
PrometheusScrapePort
: The port for the Prometheus/metrics
endpoint (e.g.,9090
).TracesSampleRate
: A value between 0.0 and 100.0 to control the percentage of traces that are sampled and exported.CollectorURL
: The endpoint of an OpenTelemetry Collector where traces should be sent (e.g.,http://otel-collector:4318
).UseStdOut
: Iftrue
, traces and metrics are printed to standard output instead of being exported, which is useful for local debugging.