OpenTelemetry Go is the official Go implementation of OpenTelemetry, providing a comprehensive set of APIs and SDKs for distributed tracing, metrics collection, and logging. It enables developers to instrument their Go applications to collect, process, and export telemetry data to observability platforms.
Package Name: go.opentelemetry.io/otel
Package Type: Library
Latest Version: v1.38.0
Installation:
# Core API
go get go.opentelemetry.io/otel@v1.38.0
# Trace API
go get go.opentelemetry.io/otel/trace@v1.38.0
# Metric API
go get go.opentelemetry.io/otel/metric@v1.38.0
# Log API
go get go.opentelemetry.io/otel/log@v1.38.0
# SDK
go get go.opentelemetry.io/otel/sdk@v1.38.0
# OTLP Exporters
go get go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp@v1.38.0
go get go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp@v1.38.0
go get go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp@v1.38.0OpenTelemetry Go consists of multiple modules organized into three main categories:
| Signal | Status |
|---|---|
| Traces | Stable |
| Metrics | Stable |
| Logs | Beta |
OpenTelemetry Go supports the current and previous two major Go releases (Go 1.23, 1.24, 1.25 as of v1.38.0).
import (
"go.opentelemetry.io/otel"
"go.opentelemetry.io/otel/attribute"
"go.opentelemetry.io/otel/trace"
"go.opentelemetry.io/otel/metric"
"go.opentelemetry.io/otel/log"
"go.opentelemetry.io/otel/propagation"
"go.opentelemetry.io/otel/codes"
// For internal logging configuration
"github.com/go-logr/logr"
)package main
import (
"context"
"log"
"time"
"go.opentelemetry.io/otel"
"go.opentelemetry.io/otel/attribute"
"go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp"
"go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp"
"go.opentelemetry.io/otel/sdk/metric"
"go.opentelemetry.io/otel/sdk/resource"
sdktrace "go.opentelemetry.io/otel/sdk/trace"
semconv "go.opentelemetry.io/otel/semconv/v1.37.0"
)
func main() {
ctx := context.Background()
// Create resource with service information
res, err := resource.New(ctx,
resource.WithAttributes(
semconv.ServiceName("my-service"),
semconv.ServiceVersion("1.0.0"),
),
)
if err != nil {
log.Fatal(err)
}
// Setup trace exporter and provider
traceExporter, err := otlptracehttp.New(ctx)
if err != nil {
log.Fatal(err)
}
tracerProvider := sdktrace.NewTracerProvider(
sdktrace.WithBatcher(traceExporter),
sdktrace.WithResource(res),
)
otel.SetTracerProvider(tracerProvider)
defer tracerProvider.Shutdown(ctx)
// Setup metric exporter and provider
metricExporter, err := otlpmetrichttp.New(ctx)
if err != nil {
log.Fatal(err)
}
meterProvider := metric.NewMeterProvider(
metric.WithReader(metric.NewPeriodicReader(metricExporter)),
metric.WithResource(res),
)
otel.SetMeterProvider(meterProvider)
defer meterProvider.Shutdown(ctx)
// Instrument your application
tracer := otel.Tracer("my-service")
meter := otel.Meter("my-service")
// Create a counter metric
counter, err := meter.Int64Counter(
"api.requests",
metric.WithDescription("Number of API requests"),
metric.WithUnit("{call}"),
)
if err != nil {
log.Fatal(err)
}
// Create a span and record metrics
ctx, span := tracer.Start(ctx, "operation")
defer span.End()
// Add attributes to span
span.SetAttributes(
attribute.String("user.id", "12345"),
attribute.String("operation", "process"),
)
// Record metric
counter.Add(ctx, 1, metric.WithAttributes(
attribute.String("method", "GET"),
attribute.String("endpoint", "/api/users"),
))
// Simulate work
time.Sleep(100 * time.Millisecond)
}The otel package provides global accessors for setting and retrieving telemetry providers:
// Tracer Provider
func SetTracerProvider(tp trace.TracerProvider)
func GetTracerProvider() trace.TracerProvider
func Tracer(name string, opts ...trace.TracerOption) trace.Tracer
// Meter Provider
func SetMeterProvider(mp metric.MeterProvider)
func GetMeterProvider() metric.MeterProvider
func Meter(name string, opts ...metric.MeterOption) metric.Meter
// Text Map Propagator
func SetTextMapPropagator(propagator propagation.TextMapPropagator)
func GetTextMapPropagator() propagation.TextMapPropagator
// Error Handler
func SetErrorHandler(h ErrorHandler)
func GetErrorHandler() ErrorHandler
func Handle(err error)
// Version
func Version() string
// Internal Logging
func SetLogger(logger logr.Logger)OpenTelemetry Go provides comprehensive instrumentation capabilities across three signals:
Create and manage distributed traces to track requests across services.
Key Features:
Quick Example:
import (
"context"
"go.opentelemetry.io/otel"
)
func performWork(ctx context.Context) {
tracer := otel.Tracer("my-service")
ctx, span := tracer.Start(ctx, "work-operation")
defer span.End()
// Add event to span
span.AddEvent("processing started")
// Your work here
span.AddEvent("processing completed")
}See Tracing Documentation for complete API reference.
Collect and aggregate metrics for monitoring application performance.
Key Features:
Quick Example:
import (
"context"
"go.opentelemetry.io/otel"
"go.opentelemetry.io/otel/metric"
)
func recordMetrics(ctx context.Context) error {
meter := otel.Meter("my-service")
// Create counter
counter, err := meter.Int64Counter(
"requests.count",
metric.WithDescription("Total number of requests"),
metric.WithUnit("{request}"),
)
if err != nil {
return err
}
// Record measurement
counter.Add(ctx, 1, metric.WithAttributes(
attribute.String("method", "POST"),
))
return nil
}See Metrics Documentation for complete API reference.
Emit structured logs integrated with trace and metric context.
Key Features:
Quick Example:
import (
"context"
"go.opentelemetry.io/otel/log"
"go.opentelemetry.io/otel/log/global"
)
func emitLog(ctx context.Context) {
logger := global.GetLoggerProvider().Logger("my-service")
// Emit log record
logger.Emit(ctx, log.Record{
Timestamp: time.Now(),
ObservedTimestamp: time.Now(),
Body: log.StringValue("User action performed"),
Severity: log.SeverityInfo,
SeverityText: "INFO",
Attributes: []log.KeyValue{
log.String("user.id", "12345"),
log.String("action", "login"),
},
})
}See Logging Documentation for complete API reference.
Propagate context across service boundaries using W3C standards.
Key Features:
Quick Example:
import (
"net/http"
"go.opentelemetry.io/otel"
"go.opentelemetry.io/otel/propagation"
)
func setupPropagation() {
// Configure composite propagator
otel.SetTextMapPropagator(propagation.NewCompositeTextMapPropagator(
propagation.TraceContext{},
propagation.Baggage{},
))
}
func injectContext(req *http.Request, ctx context.Context) {
otel.GetTextMapPropagator().Inject(ctx, propagation.HeaderCarrier(req.Header))
}
func extractContext(req *http.Request) context.Context {
return otel.GetTextMapPropagator().Extract(req.Context(),
propagation.HeaderCarrier(req.Header))
}See Context Propagation Documentation for complete API reference.
Define and manage key-value attributes for telemetry data enrichment.
Key Features:
See Attributes Documentation for complete API reference.
The SDK provides concrete implementations of the API interfaces.
Key Features:
See:
Export telemetry data to observability backends.
Available Exporters:
See:
Integrate with existing observability systems.
Available Bridges:
See Bridges Documentation for complete API reference.
Standard attribute names and values for common operations.
Available Conventions:
See Semantic Conventions Documentation for complete API reference.
OpenTelemetry Go uses a global error handler to report irremediable errors:
import "go.opentelemetry.io/otel"
// Set custom error handler
otel.SetErrorHandler(otel.ErrorHandlerFunc(func(err error) {
log.Printf("OpenTelemetry error: %v", err)
}))
// Get current error handler
handler := otel.GetErrorHandler()
// Handle an error
otel.Handle(err)type ErrorHandler interface {
Handle(error)
}type ErrorHandlerFunc func(error)
func (f ErrorHandlerFunc) Handle(err error) {
f(err)
}OpenTelemetry uses internal logging to report non-critical issues and debug information. By default, it uses a no-op logger. You can configure a custom logger using logr-compatible implementations:
import (
"go.opentelemetry.io/otel"
"github.com/go-logr/logr"
"github.com/go-logr/zapr"
"go.uber.org/zap"
)
func main() {
// Configure zap logger
zapLogger, _ := zap.NewDevelopment()
otel.SetLogger(zapr.NewLogger(zapLogger))
// Now OpenTelemetry internal logs will use your logger
}Note: This is separate from the application-level logging signal (go.opentelemetry.io/otel/log). SetLogger configures how OpenTelemetry itself logs its internal operations, while the log API is for instrumenting your application's logs.
OpenTelemetry Go follows a layered architecture:
Application Code
|
v
API Layer (Interfaces)
|
v
SDK Layer (Implementation)
|
v
Exporters (Backends)API Layer: Defines interfaces for instrumentation (trace.TracerProvider, metric.MeterProvider, log.LoggerProvider)
SDK Layer: Provides default implementations with batching, sampling, and processing
Exporters: Send data to observability backends
Set up global providers once during application initialization:
func main() {
ctx := context.Background()
// Setup providers
tp := setupTracerProvider(ctx)
defer tp.Shutdown(ctx)
otel.SetTracerProvider(tp)
mp := setupMeterProvider(ctx)
defer mp.Shutdown(ctx)
otel.SetMeterProvider(mp)
// Run application
run()
}Use descriptive names for tracers and meters:
// Good: Package-specific name
tracer := otel.Tracer("github.com/myorg/myapp/database")
// Bad: Generic name
tracer := otel.Tracer("tracer")Pass context through your call chain to maintain trace correlation:
func handler(ctx context.Context) {
ctx, span := tracer.Start(ctx, "handler")
defer span.End()
// Pass context to downstream calls
processRequest(ctx)
}
func processRequest(ctx context.Context) {
ctx, span := tracer.Start(ctx, "processRequest")
defer span.End()
// Work happens here
}Always defer span.End() immediately after starting a span:
ctx, span := tracer.Start(ctx, "operation")
defer span.End()
// Your code hereUse semantic conventions when available:
import semconv "go.opentelemetry.io/otel/semconv/v1.37.0"
span.SetAttributes(
semconv.HTTPMethod("GET"),
semconv.HTTPStatusCode(200),
semconv.HTTPRoute("/api/users/:id"),
)Record errors in spans when they occur:
import (
"go.opentelemetry.io/otel/codes"
"go.opentelemetry.io/otel/trace"
)
func operation(ctx context.Context) error {
ctx, span := tracer.Start(ctx, "operation")
defer span.End()
result, err := doWork()
if err != nil {
span.RecordError(err)
span.SetStatus(codes.Error, err.Error())
return err
}
return nil
}Define resources to identify your service:
import (
"go.opentelemetry.io/otel/sdk/resource"
semconv "go.opentelemetry.io/otel/semconv/v1.37.0"
)
res, err := resource.New(ctx,
resource.WithAttributes(
semconv.ServiceName("my-service"),
semconv.ServiceVersion("1.0.0"),
semconv.ServiceInstanceID("instance-1"),
),
resource.WithProcess(),
resource.WithHost(),
resource.WithOS(),
)import (
"net/http"
"go.opentelemetry.io/otel"
"go.opentelemetry.io/otel/attribute"
"go.opentelemetry.io/otel/codes"
semconv "go.opentelemetry.io/otel/semconv/v1.37.0"
)
func instrumentedHandler(w http.ResponseWriter, r *http.Request) {
tracer := otel.Tracer("http-server")
// Extract context from incoming request
ctx := otel.GetTextMapPropagator().Extract(
r.Context(),
propagation.HeaderCarrier(r.Header),
)
// Start span
ctx, span := tracer.Start(ctx, r.URL.Path)
defer span.End()
// Add HTTP attributes
span.SetAttributes(
semconv.HTTPMethod(r.Method),
semconv.HTTPRoute(r.URL.Path),
semconv.HTTPScheme(r.URL.Scheme),
)
// Handle request
statusCode := http.StatusOK
// ... your handler logic ...
// Record response
span.SetAttributes(semconv.HTTPStatusCode(statusCode))
if statusCode >= 400 {
span.SetStatus(codes.Error, "HTTP error")
}
w.WriteHeader(statusCode)
}func makeRequest(ctx context.Context, url string) error {
tracer := otel.Tracer("http-client")
ctx, span := tracer.Start(ctx, "HTTP GET")
defer span.End()
req, err := http.NewRequestWithContext(ctx, "GET", url, nil)
if err != nil {
span.RecordError(err)
return err
}
// Inject context into request
otel.GetTextMapPropagator().Inject(ctx,
propagation.HeaderCarrier(req.Header))
// Add attributes
span.SetAttributes(
semconv.HTTPMethod(req.Method),
semconv.HTTPURL(url),
)
// Make request
resp, err := http.DefaultClient.Do(req)
if err != nil {
span.RecordError(err)
span.SetStatus(codes.Error, err.Error())
return err
}
defer resp.Body.Close()
span.SetAttributes(semconv.HTTPStatusCode(resp.StatusCode))
return nil
}func queryDatabase(ctx context.Context, query string) error {
tracer := otel.Tracer("database")
ctx, span := tracer.Start(ctx, "db.query")
defer span.End()
span.SetAttributes(
semconv.DBSystem("postgresql"),
semconv.DBStatement(query),
semconv.DBName("users"),
)
// Execute query
result, err := db.QueryContext(ctx, query)
if err != nil {
span.RecordError(err)
span.SetStatus(codes.Error, err.Error())
return err
}
defer result.Close()
return nil
}This documentation is organized into the following sections: