How to Configure Distributed Tracing with OpenTelemetry in Fabrica-Kit
TLDR: Fabrica-Kit provides a built-in OpenTelemetry integration through trace.Init() that configures an OTLP HTTP exporter, plus specialized helpers for Redis (traceredis) and PostgreSQL (tracepg) to create an end-to-end tracing pipeline with automatic log correlation.
The go-pantheon/fabrica-kit repository offers a lightweight, opinionated OpenTelemetry integration that enables distributed tracing across your entire service stack. By configuring the global tracer provider and instrumenting external dependencies, you can capture full request flows from HTTP handlers through database queries and cache operations. This guide covers the exact implementation details found in the source code to help you configure distributed tracing with OpenTelemetry in fabrica-kit.
Initialize the Global Tracer Provider
All tracing functionality in Fabrica-Kit centers around the global tracer provider defined in trace/trace.go. The trace.Init() function creates an OTLP HTTP exporter, configures resource attributes, and registers the provider with otel.SetTracerProvider().
package main
import (
"log"
"github.com/go-pantheon/fabrica-kit/trace"
)
func main() {
// Endpoint for the OTLP collector (e.g., Jaeger, Tempo, or SaaS backend)
// Format: host:port without scheme or trailing slashes
const collectorURL = "otel-collector.example.com:4318"
// Initialize with service name, profile, and colour attributes
if err := trace.Init(collectorURL, "order-service", "prod", "blue"); err != nil {
log.Fatalf("failed to init tracing: %v", err)
}
// Subsequent OpenTelemetry calls now use this configured provider
}
Once initialized, every context.Context created by your application carries trace IDs. The provider remains active for the process lifetime, pushing spans to the configured collector endpoint.
Instrument External Services
Fabrica-Kit ships dedicated instrumentation packages for Redis and PostgreSQL that automatically wrap clients with OpenTelemetry span creation.
Redis Instrumentation
The trace/traceredis/redis.go file exposes WithTracing(), which wraps any redis.UniversalClient with the redisotel instrumentation library. This creates automatic spans for each Redis command (e.g., redis.get, redis.set).
import (
"context"
"github.com/go-pantheon/fabrica-kit/trace/traceredis"
"github.com/redis/go-redis/v9"
)
func newRedisClient() redis.UniversalClient {
rdb := redis.NewUniversalClient(&redis.UniversalOptions{
Addrs: []string{"redis:6379"},
})
// Enable automatic span creation for all Redis commands
if err := traceredis.WithTracing(rdb, nil); err != nil {
panic(err)
}
return rdb
}
For connection-level observability, use StartConnectionSpan() or StartClusterConnectionSpan() to manually create spans around connection establishment events.
func connectToShard(ctx context.Context, addr string) {
ctx, span := traceredis.StartConnectionSpan(ctx, "connect", addr, 0)
defer span.End()
// Connection logic here emits a distinct "connect" span
}
PostgreSQL Instrumentation
The trace/tracepg/postgresql.go file provides NewDB(), which constructs a pgx connection pool instrumented with otelpgx. Every query execution generates a span (db.postgresql.query) with standard attributes like db.system and db.statement.
import (
"context"
pgtrace "github.com/go-pantheon/fabrica-kit/trace/tracepg"
"github.com/go-pantheon/fabrica-util/data/db/pg"
)
func newDB(ctx context.Context) (*pg.DB, func()) {
cfg := pg.Config{
DSN: "postgres://user:pass@pg:5432/orders?sslmode=disable",
DBName: "orders",
}
// Build PostgreSQL-specific tracing configuration
pgCfg := pgtrace.DefaultPostgreSQLConfig(cfg)
// Optional: include query parameters in spans for debugging
pgCfg.IncludeQueryParameters = true
db, cleanup, err := pgtrace.NewDB(ctx, pgCfg)
if err != nil {
panic(err)
}
return db, cleanup
}
The IncludeQueryParameters option allows sensitive query values to be attached as span attributes, useful for local debugging but typically disabled in production.
Correlate Logs with Trace IDs
Distributed traces become actionable when joined with application logs. The xlog/log.go implementation automatically extracts the current trace ID from any context.Context using tracing.TraceID() and injects it into structured log output.
import "github.com/go-pantheon/fabrica-kit/xlog"
func handler(w http.ResponseWriter, r *http.Request) {
// Pass the request context to inherit the trace ID
logger := xlog.NewLogger(r.Context())
logger.Info("processing order", "order_id", "12345")
// Output includes: {"msg":"processing order","trace":"abc123...","order_id":"12345"}
}
Because the context propagates through Redis and PostgreSQL calls, log entries emitted from within those operations carry the same root trace ID, enabling end-to-end correlation across services.
Summary
trace.Init()intrace/trace.goconfigures the global OTLP exporter and service attributes once at startup.traceredis.WithTracing()wraps Redis clients to emit automatic command spans, whileStartConnectionSpan()handles manual connection tracing.tracepg.NewDB()creates instrumented PostgreSQL pools with optional query parameter logging viaIncludeQueryParameters.xlog.NewLogger()extractstracing.TraceID()from context to correlate every log line with its distributed trace.
Frequently Asked Questions
What OpenTelemetry protocol does fabrica-kit use for trace export?
The trace.Init() function in trace/trace.go configures an OTLP HTTP exporter that pushes spans to the collector URL you specify (default port 4318). This follows the standard OpenTelemetry Protocol specification and is compatible with Jaeger, Grafana Tempo, Datadog, and other OTLP-compliant backends.
Can I include raw SQL parameters in my PostgreSQL trace spans?
Yes. When configuring the PostgreSQL tracer in trace/tracepg/postgresql.go, set pgCfg.IncludeQueryParameters = true before passing the config to NewDB(). This attaches query arguments as span attributes, though you should disable this in production to prevent leaking sensitive data.
How do I create custom spans for Redis connection events?
Use traceredis.StartConnectionSpan() or traceredis.StartClusterConnectionSpan() from trace/traceredis/redis.go. These functions accept a context, operation name, and address, returning a decorated context and span that you must close with defer span.End().
Does fabrica-kit automatically propagate trace context through all log outputs?
Yes. When you use xlog.NewLogger(ctx) as shown in xlog/log.go, the logger automatically calls tracing.TraceID() to extract the current OpenTelemetry trace identifier from the context. Every log entry emitted through that logger includes a trace field, enabling seamless correlation between logs and traces without manual instrumentation.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →