Go-kit/log: The Composable Logging Solution for Go Microservices with OpenTelemetry Integration in 2025
In the increasingly complex landscape of distributed systems and microservices architectures, having a logging solution that integrates seamlessly with your entire ecosystem is crucial. Go-kit/log, a component of the broader Go-kit microservices toolkit, offers precisely this integration while maintaining the simplicity and flexibility that Go developers value. As OpenTelemetry has emerged as the industry standard for observability, Go-kit/log's composition-based design makes it particularly well-suited for integration with this framework. This introduction explores how Go-kit/log has evolved to meet the demands of modern service-oriented architectures in 2025, with a special focus on its OpenTelemetry capabilities.
What is Go-kit/log?
Go-kit/log is the logging component of Go-kit, a programming toolkit for building microservices in Go. Unlike standalone logging libraries, Go-kit/log was designed specifically to complement the other components within the Go-kit ecosystem, providing a coherent approach to logging within service-oriented architectures.
The philosophy behind Go-kit/log aligns with the broader Go-kit approach: favoring composition over inheritance, explicit dependency injection, and interfaces with focused responsibilities. This design makes Go-kit/log particularly suitable for complex, distributed systems where logging needs often extend beyond simple message output—especially when combined with OpenTelemetry's observability standards.
Core Features and Design Principles
1. Minimalist Interface-Based Design
At its core, Go-kit/log is built around a remarkably simple interface:
123type Logger interface {Log(keyvals ...interface{}) error}
This deceptively simple interface serves as the foundation for a powerful logging system built through composition. By accepting key-value pairs, it enforces structured logging as a default rather than an afterthought—perfectly aligning with OpenTelemetry's approach to structured telemetry data.
2. Composition-First Architecture
Go-kit/log embraces Go's composition-based approach to software design. Instead of providing a monolithic logger with numerous features, it offers small, focused components that can be composed together:
1234567891011// Create a logger that writes to stdout, adds timestamps,// and logs at the INFO level or abovelogger := level.NewFilter(log.With(timestamp.NewTimestampLogger(log.NewJSONLogger(os.Stdout),),"service", "api",),level.AllowInfo(),)
This composability is ideal for integrating OpenTelemetry components into the logging pipeline.
3. Context-Aware Logging
In microservices architectures, tracking requests across multiple services is essential. Go-kit/log provides mechanisms for context-aware logging:
12456// Attach logger to contextctx = kitlog.WithContext(ctx, logger)// Later, retrieve and use logger from contextcontextLogger := kitlog.FromContext(ctx)contextLogger.Log("msg", "processing request", "path", r.URL.Path)
This context awareness dovetails perfectly with OpenTelemetry's context propagation mechanism.
Integrating Go-kit/log with OpenTelemetry
In 2025, the integration between Go-kit/log and OpenTelemetry has become a best practice for organizations building observable microservices. Here's how this integration typically works:
1. Trace Context Propagation
One of the most powerful aspects of combining Go-kit/log with OpenTelemetry is the ability to automatically include trace identifiers in log entries:
1234567891011121314151617181920// Create an OpenTelemetry-aware loggerfunc otLogMiddleware(next endpoint.Endpoint) endpoint.Endpoint {return func(ctx context.Context, request interface{}) (interface{}, error) {logger := log.With(kitlog.FromContext(ctx))// Extract trace information from contextspan := trace.SpanFromContext(ctx)if span.SpanContext().IsValid() {logger = log.With(logger,"trace_id", span.SpanContext().TraceID().String(),"span_id", span.SpanContext().SpanID().String(),)}// Store enhanced logger in contextctx = kitlog.WithContext(ctx, logger)return next(ctx, request)}}
2. OpenTelemetry Log Exporter
Go-kit/log can be configured to send logs to OpenTelemetry collectors through a custom writer:
12346789101112131415161718192021222324252627type otelLogWriter struct {logExporter *otlp.Exporterresource *resource.Resource}func (w *otelLogWriter) Write(p []byte) (n int, err error) {// Parse JSON log entryvar logEntry map[string]interface{}if err := json.Unmarshal(p, &logEntry); err != nil {return 0, err}// Convert to OpenTelemetry log recordrecord := logs.NewLogRecord(logs.WithResource(w.resource),logs.WithBody(logEntry["msg"]),logs.WithAttributes(convertToAttributes(logEntry)),)// Export log recordctx := context.Background()if err := w.logExporter.ExportLogs(ctx, []logs.LogRecord{record}); err != nil {return 0, err}return len(p), nil}
3. Unified Middleware Chain
Go-kit's middleware-based architecture allows for elegant integration of logging, metrics, and tracing:
123456789101112// Create a service endpoint with OpenTelemetry and loggingendpoint := kitendpoint.Chain(// OpenTelemetry tracing middlewareotelkit.TraceEndpoint("MyOperation"),// Logging middleware with trace contextotLogMiddleware,// Metrics middlewarekitendpoint.Metrics(prometheus.NewCounterFrom(stdprometheus.CounterOpts{Name: "request_count",Help: "Number of requests received.",})),)(myServiceEndpoint)
This approach provides a unified observability solution where logs, metrics, and traces work together coherently.
A Complete Example: Go-kit/log with OpenTelemetry in 2025
Here's a more complete example showing how Go-kit/log and OpenTelemetry can be used together in a microservice:
13456789101112131415161718202122232425262728293031323334353637383940414243444546474849505152535455565759606162636465666768697071727374757677787980818283848586878889909192package mainimport ("context""net/http""os""github.com/go-kit/kit/log""github.com/go-kit/kit/log/level""github.com/go-kit/kit/transport/http/httptransport""go.opentelemetry.io/otel""go.opentelemetry.io/otel/exporters/otlp/otlptrace""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.7.0""go.opentelemetry.io/otel/trace")func main() {// Initialize loggerlogger := log.NewJSONLogger(os.Stdout)logger = log.With(logger, "ts", log.DefaultTimestampUTC, "caller", log.DefaultCaller)// Initialize OpenTelemetryshutdown := initOpenTelemetry("user-service")defer shutdown()// Create service and endpointssvc := userService{}getUserEndpoint := makeGetUserEndpoint(svc)// Add OpenTelemetry and logging middlewaregetUserEndpoint = kitendpoint.Chain(otelkit.TraceEndpoint("GetUser"),makeOTelLogMiddleware(logger),)(getUserEndpoint)// HTTP transportgetUserHandler := httptransport.NewServer(getUserEndpoint,decodeGetUserRequest,encodeResponse,httptransport.ServerBefore(// Extract trace context from HTTP headersfunc(ctx context.Context, r *http.Request) context.Context {propagator := otel.GetTextMapPropagator()return propagator.Extract(ctx, propagation.HeaderCarrier(r.Header))},),)// Start serverhttp.Handle("/users/", getUserHandler)level.Info(logger).Log("msg", "HTTP server starting", "addr", ":8080")http.ListenAndServe(":8080", nil)}// Middleware that adds trace information to logsfunc makeOTelLogMiddleware(logger log.Logger) endpoint.Middleware {return func(next endpoint.Endpoint) endpoint.Endpoint {return func(ctx context.Context, request interface{}) (interface{}, error) {span := trace.SpanFromContext(ctx)// Add trace context to loggerctxLogger := log.With(logger,"trace_id", span.SpanContext().TraceID().String(),"span_id", span.SpanContext().SpanID().String(),)// Log the requestlevel.Info(ctxLogger).Log("msg", "handling request","request_type", fmt.Sprintf("%T", request),)// Call the next endpoint with the logger in contextctx = log.WithContext(ctx, ctxLogger)resp, err := next(ctx, request)// Log the resultif err != nil {level.Error(ctxLogger).Log("msg", "error handling request","err", err,)}return resp, err}}}
Benefits of Go-kit/log with OpenTelemetry
The combination of Go-kit/log and OpenTelemetry provides several key advantages:
- Complete observability: Unified approach to logs, metrics, and traces
- Correlation: Automatic linking between logs and traces
- Standardization: Adherence to OpenTelemetry's vendor-neutral standards
- Composability: Clean integration with existing Go-kit middleware chains
- Context propagation: Consistent handling of request context across services
Analyzing OpenTelemetry Logs in Dash0
Logs can be directly routed into Dash0. Dash0 with OpenTelemetry provides the ability to filter, search, group, and triage within a simple user interface, with full keyboard support. Dash0 also gives full log context by showing trace context, the call and resource that created the log - including details like the Kubernetes Pod, server, and cloud environment.
Log AI also enhanced the logs with more semantical metadata and structure without any manual pattern declaration.
Conclusion: Go-kit/log in the OpenTelemetry Era
As distributed systems continue to grow in complexity in 2025, the combination of Go-kit/log and OpenTelemetry offers a powerful solution for comprehensive observability. Go-kit/log's composition-based design aligns perfectly with OpenTelemetry's modular architecture, allowing for clean integration between logging, metrics, and distributed tracing.
While Go-kit/log may not offer the raw performance of some specialized logging libraries, its strength lies in its seamless integration with the broader ecosystem of service middleware and observability tools. For teams building microservices with Go-kit, the combination of Go-kit/log and OpenTelemetry provides a cohesive observability strategy that enhances debugging, monitoring, and performance optimization across distributed environments.
As OpenTelemetry continues to evolve as the industry standard for observability, Go-kit/log's adaptable, interface-driven design ensures it will remain a relevant and powerful tool in the Go ecosystem for years to come.