5 min read
Treat OpenTelemetry as an application-to-platform boundary
Keep the contracts separate
OpenTelemetry is not one switch that turns observability on. Its APIs give application code a way to create telemetry; SDKs implement those APIs and control how telemetry is recorded and exported; instrumentation observes libraries and application behavior; the protocol defines how telemetry is exchanged; and the Collector can receive, process, and export it. The OpenTelemetry instrumentation ecosystem overview lays out those distinct pieces.
I treat that separation as an architecture boundary. The application should describe useful facts about its work. The platform should decide how to move, process, and deliver those facts. When those concerns get mixed together, a change to a backend or deployment topology can turn into application code changes across many services. When they are separated without an ownership agreement, the result is often telemetry that is technically flowing but nobody can explain or safely change.
The boundary is not “all instrumentation in code, all configuration elsewhere.” Automatic instrumentation may observe common framework behavior, and application code may need to add context that no generic agent can infer. The useful question is narrower: which decisions express what this application means, and which decisions express how the organization operates telemetry?
Put application meaning in application code
An application knows whether it is accepting a payment, loading a profile, or rejecting an invalid request. That meaning belongs close to the code that performs the work. I want service-owned spans and measurements to describe operations and meaningful outcomes, not the name of the observability vendor or the current collector address.
For example, a small .NET instrumentation point can name an operation and attach an application-level attribute:
using System.Diagnostics;
private static readonly ActivitySource Source = new("Example.Service");
public void HandleRequest()
{
using var activity = Source.StartActivity("request.handle");
activity?.SetTag("app.operation", "handle");
// Application work happens here.
}
This is illustrative, not a complete tracing setup. The OpenTelemetry .NET instrumentation guide shows the ActivitySource pattern. The important design choice is that the code expresses an operation in the application’s vocabulary. It does not select a destination, embed credentials, or decide the organization’s retention policy.
That does not mean application teams should invent a new vocabulary for every service. I would agree on naming and attribute rules across services, then keep the instrumentation that needs domain knowledge with the code. A platform team can help define the shared contract, but it cannot reliably infer every meaningful business boundary from runtime data alone. Conversely, application teams should not individually configure backend routing just because their SDK can export directly.
Keep transport and processing replaceable
The Collector is a natural place for platform-owned transport and processing decisions. The Collector documentation describes it as a vendor-agnostic way to receive, process, and export telemetry. Its configuration can route data and apply processing without requiring each application to implement those operational choices itself.
A simplified traces pipeline might look like this:
receivers:
otlp:
protocols:
grpc:
processors:
batch:
exporters:
otlp:
endpoint: collector:4317
service:
pipelines:
traces:
receivers: [otlp]
processors: [batch]
exporters: [otlp]
The endpoint here is illustrative; a real deployment must define its own connectivity and security. The point is that the service can send telemetry to a stable platform endpoint while the platform owns the onward path. The Collector can also be useful for concerns such as retries, batching, encryption, and filtering, as described in the Collector guide to sending data.
Direct export from an application can be a reasonable starting point, and the Collector documentation notes that it may work well for development or smaller environments. I would still avoid making direct-to-backend configuration the default architecture for a fleet of services. It spreads credentials, destination knowledge, and operational policy across application deployments. A Collector adds another component to run and monitor, but that cost buys a place to change transport and processing independently of application releases.
Portability needs an owner
Using OpenTelemetry does not make every application automatically portable between every backend. The vendor support requirements describe what it means for a vendor to support OpenTelemetry, including accepting telemetry through the protocol or an exporter. That is an important interoperability contract, not a promise that every backend interprets every attribute, query, or feature identically.
I would therefore define portability as a set of explicit responsibilities, not as a label on the SDK. Application teams own the meaning and quality of the telemetry they create: operation names, useful context, and avoiding accidental disclosure of sensitive data. The platform team owns the SDK configuration, collector deployment, routing, credentials, and backend-specific processing. Both sides own the conventions and compatibility checks that cross the boundary.
That shared contract should cover resource identity, attribute naming, what data is safe to emit, and which SDK or instrumentation changes need validation. It should also distinguish stable application signals from backend-specific conveniences. If a backend requires a proprietary field for a useful feature, I would make that dependency visible rather than quietly putting it into every service’s instrumentation.
Test the seam when things change
A boundary is only useful if teams can change either side without guessing what the other side expects. Before changing an SDK, instrumentation package, Collector pipeline, or backend, I would check a few things:
- Does the application still emit the telemetry its owners consider meaningful?
- Does the platform still receive and route the signals the service sends?
- Are resource attributes and shared conventions preserved through processing?
- Does a changed exporter or backend requirement introduce application coupling?
- Is the change safe for services that adopt instrumentation on a different schedule?
These are compatibility questions as much as observability questions. I would version and review shared conventions deliberately, just as I would other interfaces between teams. The application should be free to evolve its description of its work; the platform should be free to evolve how that description travels. Neither freedom is real unless the other side knows what it can rely on.
Drafted with AI assistance, reviewed and edited by Ali.