Observability with OpenTelemetry: one instrumentation for AWS, Azure and your own backends

Instrument services once, send the data wherever the environment needs it, keep the telemetry bill in proportion, and change backends without going blind.

The problem

Most systems become observable by accident. Each service got the logging library its team liked, metrics came from whatever the platform exposed, and tracing arrived later, added by one team with whichever vendor SDK was current that year. It holds up until an incident crosses a service boundary. Then the trace stops at the first queue, two services log the same request under different field names, and the dashboard everyone trusts turns out to count something slightly different from what its title says.

Three costs follow from that.

OpenTelemetry deals with all three by separating the parts. Services are instrumented once, against a vendor-neutral API. A Collector receives the data, processes it and exports it. The backend becomes a decision you can revisit: AWS X-Ray and CloudWatch, Azure Monitor, Prometheus and Grafana, Jaeger, or more than one at a time.

I have built observability this way on an airline group's operations platform spread across four clouds, on a streaming simulation for a US federal research agency's public challenge, on a Berlin software company's move from a monolith to microservices, and on an audio-commerce platform on Amazon EKS. The patterns below come from that work.

The levers that actually work

Agree the vocabulary before the first span

The most valuable hour in an observability project is spent before anyone writes code: write down the semantic conventions. OpenTelemetry publishes standard attribute names for HTTP, databases, messaging and more, and those should be used as published. What it cannot define is your domain: the tenant, the customer tier, the business transaction. Decide those names once, decide which signal may carry which attribute, and keep the list in the repository next to the Collector configuration.

On the airline platform, five airlines ran on shared services. A shared vocabulary with a per-airline attribute made their data comparable and let each airline's view be filtered cleanly. Without it, five teams would have invented five spellings of the same field, and every dashboard would have needed five queries.

Instrument with the OpenTelemetry SDK, not a vendor's

Application code should call the OpenTelemetry API and nothing else. Auto-instrumentation covers frameworks and clients, which in Python means the packages for FastAPI, requests, botocore, SQLAlchemy and similar. Manual spans cover the business steps no library knows about. Custom metrics go through the same SDK. Where the data is sent is configuration, not code.

The part that breaks in practice is context propagation. Inside one process, the SDK carries the trace for you. Across a network hop the context travels in headers, and OpenTelemetry's default is W3C Trace Context (the traceparent header) plus Baggage. Two kinds of hop lose it routinely:

Do the heavy work in the Collector

The OpenTelemetry Collector is a pipeline of receivers, processors and exporters, described in YAML. That makes it the right place for everything that should not be the application's concern:

On the airline platform, moving this work into Collectors close to the source is what relieved the network and ingestion pressure the old log shipping had caused. The detail, including the Collector pipeline and the cutover, is in one OpenTelemetry layer across four clouds.

One instrumentation, several backends

Because export is configuration, the same instrumented service can report to whichever backend its environment prefers.

On the audio-commerce platform, Python services on EKS sent traces to X-Ray and metrics and structured logs to CloudWatch. The team kept working in the AWS consoles it already knew, and the instrumentation stayed neutral. On the airline platform, one layer served AWS, GCP, Azure and Oracle Cloud at once. The point is not that you will change backend often. It is that you can, and that changes every conversation with a vendor.

Migrate with a parallel run, not a cutover date

Replacing the observability stack of a live system is risky for a specific reason: the old stack is the thing that would tell you the new one is failing. So do not switch on a date. Run the new pipeline beside the old one, compare dashboards and alerts over the same period until they agree, then cut over one service at a time, keeping the old path until each service is proven.

That is how the airline platform moved from ELK to OpenTelemetry without a gap in coverage. The parity check is the step teams skip, and it is the only evidence that the new system sees what the old one saw.

How to measure it

Five numbers show whether observability works and whether it is worth what it costs:

Keep the health of the pipeline itself beside those: dropped spans, exporter queue length, Collector memory. A telemetry pipeline that drops data under load fails silently, and at the worst possible moment.

Common mistakes

AI workloads need the same foundations plus a few more signals: token usage, which model answered, and whether the answer was right. That is the subject of agent observability.

Every post on Observability & OpenTelemetry