Observability Unleashed: Custom AWS SDK Instrumentation with OpenTelemetry in Go
In modern cloud-native applications, observability is essential for understanding system behavior, ensuring performance, and detecting issues before they impact users. When building Go applications that interact with AWS services using the AWS SDK, capturing telemetry data (traces, metrics, logs) gives you deep visibility into service calls and dependencies.
This guide will walk you through custom instrumentation of the AWS SDK in Go using OpenTelemetry. You can trace requests to services like S3, DynamoDB, or Lambda and integrate seamlessly with tracing backends such as AWS X-Ray, Jaeger, or Grafana Tempo.
Prerequisites
To follow this guide, you'll need:
Go 1.20+
The AWS SDK v2 for Go (github.com/aws/aws-sdk-go-v2)
OpenTelemetry Go SDK (go.opentelemetry.io/otel)
An observability backend (e.g., AWS X-Ray, Jaeger)
Basic knowledge of Go and AWS SDK
Why Instrument AWS SDK with OpenTelemetry?
By default, the AWS SDK doesn’t emit OpenTelemetry traces. Custom instrumentation allows you to:
Trace individual AWS service calls
Identify latency bottlenecks
Correlate logs, traces, and metrics
Visualize full request paths across services.
Step 1: Initialize OpenTelemetry
You need to set up an OpenTelemetry tracer provider with a span exporter:
import (
"go.opentelemetry.io/otel"
"go.opentelemetry.io/otel/exporters/stdout/stdouttrace"
"go.opentelemetry.io/otel/sdk/resource"
sdktrace "go.opentelemetry.io/otel/sdk/trace"
semconv "go.opentelemetry.io/otel/semconv/v1.17.0"
)
func InitTracer() {
exporter, _ := stdouttrace.New(stdouttrace.WithPrettyPrint())
provider := sdktrace.NewTracerProvider(
sdktrace.WithBatcher(exporter),
sdktrace.WithResource(resource.NewWithAttributes(
semconv.SchemaURL,
semconv.ServiceName("aws-sdk-instrumented-service"),
)),
)
otel.SetTracerProvider(provider)
}
Step 2: Create Custom Middleware for AWS SDK
The AWS SDK v2 for Go supports custom middleware. You can inject a middleware that starts and ends OpenTelemetry spans:
import (
"context"
"time"
"github.com/aws/smithy-go/middleware"
"go.opentelemetry.io/otel"
"go.opentelemetry.io/otel/attribute"
"go.opentelemetry.io/otel/trace"
)
func OTelTracingMiddleware() middleware.InitializeMiddleware {
return middleware.InitializeMiddlewareFunc("OTelTracingMiddleware", func(
ctx context.Context, in middleware.InitializeInput, next middleware.InitializeHandler,
) (
out middleware.InitializeOutput, metadata middleware.Metadata, err error,
) {
tracer := otel.Tracer("aws-sdk")
awsOperation := in.Parameters.(interface{ OperationName() string }).OperationName()
ctx, span := tracer.Start(ctx, awsOperation,
trace.WithAttributes(attribute.String("aws.operation", awsOperation)),
)
defer span.End()
out, metadata, err = next.HandleInitialize(ctx, in)
if err != nil {
span.RecordError(err)
}
return out, metadata, err
})
}
Step 3: Inject Middleware into the AWS SDK Client
When you create your AWS SDK client (e.g., S3 or DynamoDB), register your custom OpenTelemetry middleware:
import (
"github.com/aws/aws-sdk-go-v2/aws"
"github.com/aws/aws-sdk-go-v2/config"
"github.com/aws/aws-sdk-go-v2/service/s3"
)
func CreateInstrumentedS3Client() *s3.Client {
cfg, _ := config.LoadDefaultConfig(context.TODO())
client := s3.NewFromConfig(cfg, func(o *s3.Options) {
o.APIOptions = append(o.APIOptions, OTelTracingMiddleware())
})
return client
}
Step 4: Visualize the Traces
Once the application runs, you can export traces to your preferred backend like:
Jaeger (local development)
AWS X-Ray (production, via OTLP exporter)
Grafana Tempo, Honeycomb, or Lightstep
Use attributes such as aws.operation, aws.region, aws.service for filtering and searching traces.
Optional: Enhance Context with Correlated Logs
Add trace and span IDs to logs using the context for unified observability:
spanCtx := trace.SpanContextFromContext(ctx)
log.Printf("TraceID: %s SpanID: %s", spanCtx.TraceID(), spanCtx.SpanID())
Best Practices
Use batching for exporters in production to reduce overhead.
Avoid tracing overly frequent or low-value operations.
Use semantic conventions for AWS where applicable.
Aggregate AWS SDK spans under higher-level business logic spans.
Conclusion
Custom instrumentation of AWS SDK in Go with OpenTelemetry empowers developers with rich observability into cloud service interactions. This setup not only aids in debugging and performance optimization but also lays the foundation for robust distributed tracing and monitoring.
OpenTelemetry offers vendor-neutral, future-proof visibility into your AWS interactions, whether using this in a microservices architecture or a monolith.

Comments
Post a Comment