How to Implement OpenTelemetry for Better Observability
We’re building a system that provides better observability with OpenTelemetry implementation. This matters since observability is key to understanding what’s happening in your applications, especially as they grow in complexity.
Prerequisites
- Go 1.17+
- Python 3.8+
- Node.js 14+
- Docker
- Access to a cloud provider (AWS, GCP, or Azure)
Step 1: Install OpenTelemetry Packages
You need to install OpenTelemetry SDKs for your programming languages. This doesn’t mean just one language; we’re going multi-language here. We’ll cover Go, Python, and Node.js.
# Go
go get go.opentelemetry.io/otel
# Python
pip install opentelemetry-api opentelemetry-sdk
# Node.js
npm install @opentelemetry/api @opentelemetry/sdk-node
Why install these? It’s straightforward: you need the SDKs to instrument your applications. Skip this step, and you’ll get errors later when trying to import the libraries. Trust me, I’ve been stuck in that purgatory for hours.
Step 2: Create a Basic Tracer
Now that you’ve installed the SDKs, let’s create a basic tracer. This will allow you to start tracing requests as they come into your application.
package main
import (
"go.opentelemetry.io/otel"
"go.opentelemetry.io/otel/sdk/trace"
"log"
)
func main() {
tp := trace.NewTracerProvider()
otel.SetTracerProvider(tp)
// Further code will go here
}
from opentelemetry import trace
tracer = trace.get_tracer(__name__)
with tracer.start_as_current_span("main"):
print("Hello, OpenTelemetry!")
const { NodeTracerProvider } = require('@opentelemetry/sdk-node');
const provider = new NodeTracerProvider();
provider.register();
const tracer = provider.getTracer('example-tracer');
tracer.startSpan('main').end();
This step is crucial. The tracer is your gateway into observability. If you forget to create it, you won’t capture any data, and you’ll be left wondering why your application seems to be in a black hole.
Step 3: Send Traces to a Backend
Sending traces somewhere useful is where the magic happens. You’ll want to send your traces to a backend like Jaeger or Zipkin. Below is how to do it for each language.
package main
import (
"go.opentelemetry.io/otel/exporters/trace/jaeger"
"go.opentelemetry.io/otel/sdk/trace"
)
func main() {
exporter, err := jaeger.NewRawExporter(jaeger.WithCollectorEndpoint(""))
if err != nil {
log.Fatal(err)
}
tp := trace.NewTracerProvider(trace.WithBatcher(exporter))
otel.SetTracerProvider(tp)
// Further code here
}
from opentelemetry.exporter.jaeger import JaegerExporter
from opentelemetry.sdk.trace import TracerProvider
from opentelemetry.sdk.trace.export import BatchSpanProcessor
jaeger_exporter = JaegerExporter(
agent_host_name='',
agent_port=6831,
)
trace_provider = TracerProvider()
trace_provider.add_span_processor(BatchSpanProcessor(jaeger_exporter))
trace.set_tracer_provider(trace_provider)
const { JaegerExporter } = require('@opentelemetry/exporter-jaeger');
const jaegerExporter = new JaegerExporter({
serviceName: 'my-service',
host: '',
port: 6831,
});
provider.addSpanProcessor(new BatchSpanProcessor(jaegerExporter));
Without this, you’ll have all this tracing data sitting in your app but nowhere to go. It’s like throwing a party but forgetting to invite anyone. You’ll end up with a bunch of traces that just sit there—unseen.
Step 4: Instrument Your Code
This is the fun part where you wrap your methods to collect traces automatically. You’ll sprinkle tracing throughout your application code.
func main() {
tracer := otel.Tracer("example-tracer")
_, span := tracer.Start(context.Background(), "my-operation")
defer span.End()
// Simulate some operation
}
with tracer.start_as_current_span("database-call"):
# Code for database operation
pass
const span = tracer.startSpan('http_request');
// Simulate an HTTP request
span.end();
This is where the magic happens. You get to see not only the requests but also how long they take. Forgetting this step is like having a car without wheels—you’re not going anywhere fast.
Step 5: Verify Your Setup
After all this, you want to make sure everything works. Check your backend for incoming traces. If you’re not seeing anything, here are the common issues:
- Network issues: Make sure your backend is reachable from your application.
- Configuration errors: Double-check your endpoints and ports.
- Missing imports: Ensure you’ve imported everything needed in your code.
When I first set up OpenTelemetry, I spent a whole evening wondering why nothing was showing up. Turned out, I had a typo in my endpoint URL. A simple change saved my sanity.
The Gotchas
Here are a few things that caught me off guard when I first tried OpenTelemetry implementation:
- Sampling rates: Make sure you understand how many traces you want to send. Too many can overwhelm your backend; too few can leave you blind.
- Environment variables: Many configurations can be set via environment variables, and it’s easy to forget them or set them wrong.
- Version mismatches: Always double-check that the library versions you’re using are compatible with each other.
Full Code Example
Here’s a complete example that combines everything we’ve discussed. This example will send data to Jaeger.
package main
import (
"context"
"go.opentelemetry.io/otel"
"go.opentelemetry.io/otel/exporters/trace/jaeger"
"go.opentelemetry.io/otel/sdk/trace"
"log"
)
func main() {
exporter, err := jaeger.NewRawExporter(jaeger.WithCollectorEndpoint(""))
if err != nil {
log.Fatal(err)
}
tp := trace.NewTracerProvider(trace.WithBatcher(exporter))
otel.SetTracerProvider(tp)
tracer := otel.Tracer("example-tracer")
_, span := tracer.Start(context.Background(), "my-operation")
defer span.End()
// Simulate some operation
}
from opentelemetry import trace
from opentelemetry.exporter.jaeger import JaegerExporter
from opentelemetry.sdk.trace import TracerProvider
from opentelemetry.sdk.trace.export import BatchSpanProcessor
jaeger_exporter = JaegerExporter(
agent_host_name='',
agent_port=6831,
)
trace_provider = TracerProvider()
trace_provider.add_span_processor(BatchSpanProcessor(jaeger_exporter))
trace.set_tracer_provider(trace_provider)
tracer = trace.get_tracer(__name__)
with tracer.start_as_current_span("main"):
print("Hello, OpenTelemetry!")
const { NodeTracerProvider } = require('@opentelemetry/sdk-node');
const { JaegerExporter } = require('@opentelemetry/exporter-jaeger');
const provider = new NodeTracerProvider();
const jaegerExporter = new JaegerExporter({
serviceName: 'my-service',
host: '',
port: 6831,
});
provider.addSpanProcessor(new BatchSpanProcessor(jaegerExporter));
provider.register();
const tracer = provider.getTracer('example-tracer');
const span = tracer.startSpan('main');
span.end();
What’s Next
Take a good hard look at the metrics you’re collecting and see if there are any gaps. Adding metrics collection alongside traces can give you more insights into your application.
FAQ
- What is OpenTelemetry? OpenTelemetry is an observability framework for cloud-native software, providing a way to collect, process, and export telemetry data.
- Is OpenTelemetry only for tracing? No, it can also handle metrics and logs, making it a versatile choice for observability.
- Can OpenTelemetry work with existing services? Yes, OpenTelemetry can be integrated with various backend services like Jaeger, Zipkin, and Prometheus.
Data Sources
For more information, check the official documentation:
Last updated May 12, 2026. Data sourced from official docs and community benchmarks.
🕒 Published: