How OpenTelemetry Integrates with Service Mesh Telemetry

How OpenTelemetry Integrates with Service Mesh Telemetry

Two systems watch the same requests from different seats. This guide shows how to make them tell one story, so that the traces, metrics and logs from your mesh proxies and from your application code join up instead of competing.

Picture a busy airport. The airline knows exactly which passengers are on which flight and what each one ordered for dinner. The security checkpoint knows who walked through which gate, when, and whether anyone was stopped. Both are recording the same journey, yet neither record is complete. If you want the full story of one traveler, you need to put the two records side by side and match them up.

Modern software has the same situation. Your application code records what the business logic did. A service mesh, a layer of helper programs that sits beside your services and handles their network traffic, records what happened on the wire. OpenTelemetry is the open standard that lets both of them speak one language. When they are joined well, you can follow a single customer request from the front door to the deepest database and see every pause, every retry and every failure along the way.

You do not need to know anything about meshes or monitoring to follow this guide. Every new word is explained the first time it appears, and every big idea has an everyday comparison.

1The Vocabulary: Services, Proxies, Meshes and Telemetry

Eight words carry the entire topic. Learn them here and the rest of the guide reads easily.

Many modern applications are built as a collection of small programs called microservices. One handles logins, another handles payments, another recommends products. They talk to each other over the network all day long. As the number of services grows into the dozens or hundreds, a new problem appears: every service needs the same network chores done, such as encrypting traffic, retrying failed calls and measuring how long calls take. Writing that code again and again in every service is slow and error-prone.

Everyday Analogy

Think of a large office building where hundreds of employees send and receive packages. Instead of asking each employee to learn how to seal, label, track and insure a parcel, the building gives every desk a personal assistant. The employee hands over the parcel, and the assistant does all the shipping chores. A service mesh is that army of personal assistants for network calls.

Mesh

Proxy

A small program that sits in the path of network traffic and can inspect, forward, retry or block it. Envoy is the most widely used one, created at the ride-sharing company Lyft.

Mesh

Sidecar

A proxy that runs right next to each service instance, in the same pod. All traffic in and out of the service passes through it, like a sidecar attached to a motorbike.

Mesh

Data Plane

The group of all those proxies. They move the actual requests and are the part that does the work.

Mesh

Control Plane

The central brain that tells every proxy the rules: where to send traffic, who may talk to whom, how to record telemetry. Istio’s control program is one example.

Telemetry

Traces, Metrics, Logs

The three kinds of recorded evidence. A trace is the story of one request. A metric is a number over time. A log is a written record of an event.

Standard

OpenTelemetry

Usually shortened to OTel. A vendor-neutral standard and toolset for producing and moving telemetry, run under the Cloud Native Computing Foundation.

Popular meshes include Istio, Linkerd, Consul and Cilium’s service mesh. They differ in design, but they share the idea that a layer outside your application code handles network behavior. That layer is in a unique position to watch every request, which is why meshes produce so much telemetry for free.

T
Remember Two Words

The mesh sees the wire. OpenTelemetry in your application sees the work. Integration is the art of joining the two views.

2What a Service Mesh Records on Its Own

Without writing a single line of monitoring code, a mesh already produces three kinds of telemetry.

Because every request passes through a proxy, the proxy can note it down. Nobody has to change the application. That is the great attraction of mesh telemetry: it works uniformly across every service, whatever language it was written in, including old programs nobody wants to touch.

SignalWhat the proxy recordsTypical example
MetricsCounts, sizes and timings of requests, grouped by source, destination and resultRequests per second from Orders to Payments, with the share that failed
TracesA span for each hop a request makes through a proxyA span showing the call from Orders to Payments took 120 milliseconds
Access logsOne line per request with addresses, status, timing and flagsA record that a call was refused because no healthy server was available

The golden metrics

Istio, for example, produces a standard set of metrics inside the proxy itself. They count requests, measure how long requests took, and measure the size of requests and responses. Each is labeled with who called, who answered, the response code and more. Together they give you the classic health indicators of a service: how much traffic, how many errors, and how slow. Engineers sometimes call these the RED measures, for rate, errors and duration.

Spans without application code

The proxy can also start and finish spans. Each proxy creates a span for the request it handles, recording the time it took and details such as the destination and the status. Envoy, the proxy under several meshes, supports sending these spans out using the OpenTelemetry protocol, and Istio can be told to use an OpenTelemetry provider for tracing.

Logs of every call

An access log is a line written for each request. It is the oldest form of network telemetry, and it is rich: it includes the response code, how long the call took, the bytes moved and a short code called a response flag that explains unusual endings such as a timeout or a refused connection.

i
A Free Starting Point

If you have no monitoring today, turning on mesh telemetry can give you a map of which services call which, plus error and speed figures, within a day. It is the quickest route from nothing to something.

3What OpenTelemetry Adds, and Why Two Systems Are Needed

If the mesh already records so much, why bring OpenTelemetry in at all?

The mesh sees only the edges of each service. It knows a request entered the payment service and left 90 milliseconds later. It cannot know what happened inside those 90 milliseconds: whether the time went into a database query, a call to a bank or a slow loop. Only code inside the service can say that, and OpenTelemetry is the standard way for that code to speak.

Everyday Analogy

A delivery company’s tracking page says “package arrived at the warehouse at 9:00 and left at 11:30.” That is the mesh view. It does not say what happened during those two and a half hours. Warehouse staff would record “waiting for a pallet,” “scanned,” “repacked” and “loaded.” That is the application view. The full story needs both.

Four things OpenTelemetry provides

  • SDKs and instrumentation that live inside your application and create detailed spans, metrics and logs.
  • A common data protocol, OTLP, so telemetry from any source travels in one format.
  • Semantic conventions, which are agreed names for common attributes, such as the HTTP method or the database name, so that data from different tools lines up.
  • The Collector, a standalone program that receives telemetry from many sources, changes it and forwards it to storage.

The comparison

QuestionMesh telemetryOpenTelemetry in the application
Needs code changes?NoSome, or an agent that instruments automatically
Works for every language?YesOnly where an SDK or agent exists
Sees inside a service?NoYes
Sees network behavior?Yes, in great detailOnly from the application’s side
Knows business meaning?NoYes, such as order number or customer tier

Each side covers the other’s blind spot. A wise team does not choose one. It joins them, and the rest of this guide explains how.

4The Big Picture: One Collector, Two Sources

The simplest and most reliable design sends both streams to one OpenTelemetry Collector.

Here is the overall shape of a healthy integration. The application, using an OpenTelemetry SDK, sends its telemetry out. The proxy next to it sends its own telemetry out. Both go to a Collector, which tidies, labels and combines them, and then forwards everything to the backend where people look at dashboards.

flowchart LR
A["Application with OTel SDK"] -->|"local traffic"| P["Mesh proxy beside the app"]
A --> C["OpenTelemetry Collector"]
P --> C
CP["Mesh control plane"] -.-> P
C --> B["Backend for traces metrics and logs"]
    
FIG 1. Application and proxy telemetry meet in a single Collector

Five jobs of the integration

1

Join the traces

Mesh spans and application spans must share one trace identifier, so they appear as one story.

2

Agree on sampling

Decide once, at the start, whether a request is recorded, so no hop records it differently.

3

Unify the metrics

Bring mesh metrics and application metrics into one naming and unit scheme, and avoid counting the same request twice.

4

Correlate the logs

Stamp every log line with the trace identifier, so one click moves from a slow span to the exact log lines.

5

Name things consistently

Give each service one name and one set of labels in every signal, so charts and traces agree.

T
Why One Collector Path Matters

When both streams pass through the same Collector, you have one place to fix labels, drop noise, limit volume and add security. Sending proxy data to one vendor and application data to another makes joining them nearly impossible.

5Traces: Stitching Mesh Spans and Application Spans Together

The most valuable integration is also the most delicate one. A trace is only as good as its weakest link.

A trace is a tree of spans that share one trace identifier, a long random number created when the request first arrives. Every service that touches the request adds its own span to the tree. To do that, it must know the trace identifier, and the only way to learn it is from the request itself. The identifier is carried in a small set of HTTP headers. The web standard for this is called W3C Trace Context, and its main header is named traceparent.

sequenceDiagram
participant U as User
participant G as Ingress Gateway Proxy
participant PA as Proxy of Service A
participant A as Service A App
participant PB as Proxy of Service B
participant B as Service B App
U->>G: Request
Note over G: Creates the root span
G->>PA: Request with trace headers
PA->>A: Request with trace headers
Note over A: App reads the headers and keeps the trace id
A->>PA: Outgoing call with the same trace headers
PA->>PB: Request with trace headers
PB->>B: Request with trace headers
    
FIG 2. The trace identifier travels in headers across every proxy and application

The part the mesh cannot do alone

A proxy sees an incoming request and, a moment later, an outgoing request. It cannot tell that the outgoing request was caused by the incoming one. Only the application knows that. So the application must copy the trace headers from the incoming request onto every outgoing request. This is called context propagation. If the application forgets, the outgoing call starts a brand new trace, and the story splits in two.

Everyday Analogy

Imagine a relay race where each runner must hand the baton to the next. The proxies are the track officials who note each handoff. If a runner drops the baton and picks up a fresh one, the officials still record every handoff, but they now think two separate races are happening. The application is the runner. Passing the headers along is passing the baton.

Two easy ways to pass the baton

  • Use an OpenTelemetry SDK or automatic agent. It reads the incoming headers and writes them onto outgoing calls without extra effort. This also adds the application’s own spans inside the same trace.
  • Copy the headers by hand. For tiny services without an SDK, forward the trace headers your mesh uses. This keeps the trace connected but adds no inner detail.

How mesh spans and application spans nest

In a healthy joined trace, each hop appears as a pair of mesh spans around the application’s own work. The calling proxy records a client span, which measures the call from the caller’s side, including time on the wire. The receiving proxy records a server span, which measures the request from the moment it arrived. Inside the server span, the application adds its own spans for the work it does. Reading from the top, the trace therefore shows: caller proxy, receiving proxy, application code, and then the next call out. The gaps between neighboring spans are time spent in transit or in queues, which is exactly the information you cannot get from application spans alone.

!
The Most Common Failure

Many teams turn on mesh tracing, open the trace view, and find disconnected fragments: one tiny trace per hop. The cause is almost always headers not being forwarded by the application. Fixing that single behavior joins the whole picture.

6Propagation Formats and Who Starts the Trace

Everybody must speak the same header dialect, and someone must be first to speak.

Trace headers come in more than one dialect. The modern standard is W3C Trace Context. An older and still common format, B3, came from the Zipkin tracing project. Other formats exist for particular vendors. A propagator is the small piece of code that reads and writes one of these formats. If the proxy writes B3 headers and the application only understands W3C headers, the application sees no trace at all and starts a new one.

FormatMain headerNotes
W3C Trace Contexttraceparent and tracestateThe standard, and the OpenTelemetry default
B3X-B3-TraceId and friends, or a single b3 headerCommon in older meshes and Zipkin setups
Vendor formatsVaryAvoid unless forced, since they split the ecosystem

Pick one format and use it everywhere

The cleanest choice is W3C Trace Context in both the mesh and the applications. If a migration is slow, configure the application SDK with both propagators at once, so it can read whichever the proxy sent and write both when calling onward. Check your mesh’s documentation for how to set the format, because the setting differs between products and versions.

Who is the root?

The first proxy a request meets, usually the ingress gateway at the edge of your cluster, decides whether a trace exists. If the incoming request has no trace headers, the gateway creates the identifier and the first span, called the root span. Every later hop becomes a child. If the caller outside your system already sent trace headers, such as a partner’s system, you can choose to continue their trace or start your own. Starting your own is safer when the caller is untrusted, because outsiders could otherwise influence your sampling or flood your storage.

Baggage: a small passenger on the same ride

Baggage is a companion standard that carries small name and value pairs along with the request, such as a customer tier or a test-traffic flag. Proxies generally pass baggage along untouched, so a service far down the chain can read what the front door noted. It is handy for labeling, but remember that baggage travels in clear headers, so never put secrets in it.

i
Request Identifiers

Meshes often add their own request identifier header, which is a separate short random value used to find a single request in logs. It is useful, but it is not the same as the trace identifier. Put the trace identifier in your logs as well, so both can be searched.

7Sampling: Deciding Which Requests Get Recorded

Recording everything is expensive. The danger is that the mesh and the application each decide differently.

Storing a trace for every request would drown most systems, so engineers record only a fraction. This is called sampling. In a mesh, the proxies can sample, and the applications can sample too. If they do so independently, you get half-finished stories: the proxy kept the trace, but the application dropped it, or the other way around.

flowchart TB
R["Request arrives at the gateway"] --> D{"Is there already a sampling decision in the headers?"}
D -- "Yes" --> F["Every proxy and app follows that decision"]
D -- "No" --> M["Gateway rolls the dice at the configured rate"]
M --> K{"Sampled?"}
K -- "Yes" --> S["Mark the trace as sampled and record spans"]
K -- "No" --> N["Mark as not sampled and skip spans"]
    
FIG 3. One decision at the edge, followed by everyone else

The rule that keeps stories whole

The trace headers include a sampled flag. The first component to see the request decides and writes the flag. Everyone downstream should follow the parent’s decision. In OpenTelemetry SDKs this behavior is called a parent-based sampler. When the application uses it, the application records spans exactly when the gateway did, and the trace is complete or absent, never half there.

Everyday Analogy

A school decides which classes will be filmed for a documentary. If the principal’s office says “film Room 12,” every teacher and cameraman in Room 12 films. If each camera operator flipped a coin on their own, the footage would have missing scenes. A single instruction from the top keeps the film whole.

A note on defaults

Meshes usually ship with a low default sampling rate, since tracing every request on a busy mesh is costly. Istio, for instance, commonly defaults to about one percent. That is wise for production, and it surprises people who try the system with a single test request and see nothing. In test environments, raise the rate to a hundred percent. In production, raise it only as far as your budget and storage allow.

Choosing a rate: a quick calculation

Suppose the gateway receives 1,000 requests per second. At one percent sampling, ten requests per second are kept, which is 864,000 traces per day. At a hundred percent, it would be 86,400,000 traces per day, a hundred times more. Because every trace holds many spans, one for each hop and each internal step, the span count is higher still. A sensible plan keeps a low rate for ordinary traffic and finds a way to keep the interesting requests, such as errors and slow ones, through smarter sampling in the Collector.

!
Do Not Double-Sample

If the proxy samples at ten percent and the application then independently samples at ten percent, you keep only one percent and most traces are broken. Always have the application defer to the incoming decision.

8Metrics: Bringing Mesh Numbers into OpenTelemetry

Mesh metrics speak a Prometheus dialect. OpenTelemetry has its own conventions. The Collector translates between them.

A mesh like Istio exposes its metrics in the Prometheus format, a plain-text layout that is read by periodically visiting a web address on each proxy, an act called scraping. The OpenTelemetry Collector has a Prometheus receiver that does exactly this, turning the scraped values into OpenTelemetry metrics and passing them along with everything else.

Same idea, different names and units

IdeaTypical mesh metricOpenTelemetry convention
Request countistio_requests_totalCount derived from the HTTP duration metric
Request durationistio_request_duration_millisecondshttp.server.request.duration, measured in seconds
Caller and calleesource and destination workload labelsResource attributes and peer attributes

Notice the unit difference. The mesh reports milliseconds, and the OpenTelemetry convention uses seconds. A call that takes 250 milliseconds is 0.25 seconds. If you plot the two on one chart without converting, one line is a thousand times larger than the other. The Collector can rename metrics and convert units, so decide on one convention and let the Collector apply it.

The double counting trap

If both the proxy and the application measure the same request, you now have two request counts for one request. Adding them together doubles every number. The usual remedy is to choose a source of truth for each question. Use the mesh metrics for traffic between services, since they cover every service uniformly. Use the application metrics for business meaning and internal work. Keep them in separate, clearly named families, and never sum across the two.

T
Watch the Series Count

Mesh metrics carry many labels, and histograms add one series per bucket. Suppose 100 workloads each call 10 others, which gives 1,000 caller and callee pairs. With 5 response codes and about 20 histogram buckets, that is 1,000 x 5 x 20 = 100,000 series from one metric family. Drop labels you never chart, before storage bills arrive.

Metrics and traces answer different questions

A metric tells you that something is wrong and how much, for all traffic, cheaply. A trace tells you where and why, but only for the requests you recorded. Because the mesh counts every request in its metrics, even when traces are sampled at one percent, mesh metrics are an unbiased measure of traffic. Use them to notice problems, and use traces to explain them. Teams that rely on sampled traces to count traffic end up undercounting by a factor of a hundred, while teams that rely on metrics alone know something is broken but cannot say where.

Linking numbers to stories

OpenTelemetry supports exemplars, small samples that attach a trace identifier to a metric point. With them, a spike on an error chart can link straight to a trace that shows a failing request. Support depends on your tools, but when present, it makes the integration of metrics and traces feel seamless.

9Logs: Access Logs That Point to Traces

An access log line is far more useful when it carries the trace identifier. This chapter shows how to get there.

By default, many meshes write access logs to the proxy’s standard output as plain text, which the platform collects like any other container log. That works, but it leaves the log as a separate island. Two improvements bring it into the OpenTelemetry world. First, the mesh can be told to send access logs through OpenTelemetry’s protocol directly to the Collector, as structured records rather than text. Istio’s configuration includes an OpenTelemetry access log provider for this, although the exact setup differs between versions. Second, the log should include the trace identifier, so a person looking at a slow span can jump to the exact log line for that request, and the other way around.

Everyday Analogy

A hotel keeps a door log of every key-card swipe, and a separate camera recording. The log says room 412 opened at 2:14 a.m. The camera shows the corridor. Without a shared timestamp or number, matching them takes ages. A trace identifier on the log line is that shared number.

What an access log adds that spans do not

  • Every request, not a sample. Traces are usually sampled at a low rate, but access logs can cover all requests, so no failed call goes unrecorded.
  • Response flags. Short codes that say why a request ended unusually, for example no healthy server was available, the upstream timed out or the connection was reset.
  • Network facts. Source and destination addresses, bytes sent and received, and the time spent in each phase of the call.

Volume is the catch

An access log line is typically a few hundred bytes. If one service receives 1,000 requests per second, that is 86.4 million lines per day. At about 500 bytes each, that is 43.2 billion bytes, or roughly 43 gigabytes, per day from just one busy service. A mesh with hundreds of services can produce terabytes. The good news is that the Collector can help. You can log only errors, drop health-check traffic, or keep a small random share of successes while keeping every failure.

!
Choose What to Keep

Logging every successful request in full is rarely worth the storage. Keep all failures, all slow requests, and a small sample of everything else. Meshes allow filtering at the proxy, and the Collector can filter again before storage.

10One Name for One Service: Resource Identity

If the mesh calls a service one thing and the application calls it another, your charts will show two services where there is only one.

Every piece of OpenTelemetry telemetry carries resource attributes, which describe who produced it. The most important is service.name. When the application’s SDK says “this is the checkout service” and the mesh proxy says “this came from a workload named checkout-v2 in the shop namespace,” a naive backend treats these as two different things. Traces then show a mesh node and an application node side by side instead of one merged service.

SourceHow it may name the serviceThe problem
Application SDKcheckoutSet by the developer, may differ from the platform name
Mesh proxy spansOften based on the workload or the host nameIncludes a namespace or suffix the app does not use
Mesh metricsWorkload, namespace and version labelsDifferent label names from the OpenTelemetry attributes

The cure: normalize in the Collector

The Collector sits in the middle and can fix this in one place. Its Kubernetes attributes processor labels every record with the pod, namespace and deployment it came from, using information from the cluster. Its attributes and transform processors can rename a value, remove a suffix or set service.name from a trusted source, such as a label on the workload. Choose a single rule, for example “the service name is the value of the app label on the pod,” and apply it to application and mesh data alike.

Other labels worth aligning

  • Version. Mesh metrics often include a workload version label, which is perfect for comparing a new release with the old one during a gradual rollout.
  • Environment. Production, staging and test, in one spelling.
  • Team or owner. Useful for routing alerts and attributing cost.
  • Cluster and region. Important for multi-cluster meshes, where the same service name may exist in several places.
T
Write the Rule Down

Document the naming rule in one short page and enforce it in the Collector configuration. A new service then joins the picture automatically with no bespoke work.

11Where the Collector Lives: Deployment Patterns

Every proxy needs a nearby place to send its telemetry. Where you put the Collector decides how reliable and how costly the whole system is.

Hundreds of proxies, each sending spans, logs and metrics, can overwhelm a single destination. The usual design uses two layers, shown below. A small node Collector runs on every machine, as a daemon-style program that Kubernetes places once per node. Proxies and applications on that machine send to it, over a fast local hop. The node Collectors then forward to a central gateway tier of larger Collectors that do the heavy work and talk to the backend.

flowchart LR
subgraph Node1["Node 1"]
A1["Pods with proxies"] --> D1["Node Collector"]
end
subgraph Node2["Node 2"]
A2["Pods with proxies"] --> D2["Node Collector"]
end
D1 --> G["Gateway Collector tier"]
D2 --> G
G --> B["Backend"]
    
FIG 4. A node-level Collector absorbs bursts, and a gateway tier does the shared processing
LayerGood atTypical jobs
Node CollectorBeing close, fast and simpleReceive from proxies and apps, add node and pod labels, batch, forward
Gateway CollectorSeeing everything at onceNormalize names, filter noise, apply tail sampling, route to backends
Everyday Analogy

A city has neighborhood post offices and one big sorting center. Residents drop letters at the nearest small office in seconds. The small offices bundle letters and send them to the sorting center, which sorts, stamps and dispatches. Nobody drives every letter across the city by hand.

A loop to avoid

If the Collector itself runs inside the mesh with a proxy beside it, the traffic that delivers telemetry is also traced by the mesh. Now the act of delivering spans creates new spans, which must be delivered, which creates more spans. This feedback loop wastes resources and fills your storage with telemetry about telemetry. Prevent it by keeping Collector pods out of the mesh, or by turning off tracing and access logging for their traffic.

Sizing and keeping the Collector reliable

The Collector is now part of your production path, so it needs the same care as any service. Give it memory and processor limits, and enable its memory limiter so it refuses data politely instead of crashing when overloaded. Run several gateway replicas, and put a load balancer that understands request traffic in front of them, so a single slow replica does not hold up many proxies. Watch its own metrics: queue size, dropped items and export failures. If the Collector falls behind, proxies and applications drop telemetry silently, and you lose the very evidence you need during an incident.

i
Keep the Hop Short

Proxies export on a schedule and use limited waiting space. A nearby Collector replies in milliseconds, which keeps those proxies from backing up. This is the same reasoning behind sending application telemetry to a local agent first.

12What the Mesh Sees That the Application Cannot

The proxy is the only witness for a whole family of events. Without its telemetry, they are invisible.

Many important events happen in the network layer, before a request ever reaches your code or after it leaves. The application cannot report them because it never knew. Only the proxy can.

Mesh Only

Retries

If a call fails and the proxy tries again, the application sees one request that took a long time. The proxy sees two or three attempts. Envoy records the attempt count, which explains mysterious delays.

Mesh Only

Timeouts

The proxy can give up on a slow server. The application sees an error. The proxy knows it was a timeout it enforced.

Mesh Only

Circuit Breaking

When a destination keeps failing, the proxy can stop sending traffic to it. The application just sees fast failures, and only the proxy knows why.

Mesh Only

Encrypted Identity

When two proxies use mutual TLS, each knows the verified identity of the other. The application only sees a plain local connection.

Mesh Only

Routing Decisions

Which version received the request during a canary rollout, or which rule matched. The proxy applied the rule, so it can record it.

Mesh Only

Refused Traffic

Requests blocked by a policy never reach the application at all, so only the proxy can count them.

Identity you can trust

When the mesh uses mutual TLS, each proxy proves who it is to the other using a certificate tied to the workload. The proxy can therefore record not just “a request came from this address,” but “a request came from this verified workload identity.” Addresses change as pods come and go, but identities stay meaningful. Recording the verified source and destination in metrics and logs makes security reviews far easier, because you can answer “which services actually call the payment service” from evidence rather than from a diagram that may be out of date.

Response flags are a gift

Envoy marks each request with short response flags. A few examples: one means no healthy upstream server was available, another means the upstream connection failed, another means the upstream request timed out, and another means no route matched. When an engineer sees a burst of errors, the flag turns “something is wrong” into “the destination had no healthy servers.” Carrying these flags into your logs and, where possible, into span attributes is one of the most useful pieces of integration work.

Everyday Analogy

You phone a restaurant and the call never connects. From your side, “the call failed.” The phone company’s records say “the restaurant’s line was busy” or “the number was out of service.” Those are the proxy’s records. They explain what you could not see from your own side.

T
A Debugging Shortcut

When a service looks slow in the application’s own spans but fast in its code, look at the mesh span for the same call. The gap is often retries, queuing or a timeout that the application never saw.

13What the Application Sees That the Mesh Cannot

The other half of the story is hidden from the proxy. Only your own code can tell it.

From the proxy’s point of view, a service is a closed box. A request goes in, a response comes out, and some time passes. The proxy has no idea what happened inside the box. OpenTelemetry in the application opens the box.

EventVisible to the proxy?Visible to the OpenTelemetry SDK?
A request entered and leftYesYes
A database query took 400 millisecondsOnly if the database is also behind a proxy, and then only as a network callYes, with the query type and table
A function loop was slowNoYes, with custom spans
A payment of a certain size was declinedNoYes, with business attributes
A cache hit saved a callNoYes
An exception was thrown and handledNoYes, with the error type and stack

Business meaning

A proxy knows the address of a request. Your application knows what the request meant: which customer, which product, which feature flag, which plan. Adding such labels to spans, as attributes, turns the mesh’s anonymous call graph into a story that product and support teams can use, such as “orders from premium customers are slowest on the payment step.”

Where the two meet in a trace

In a joined trace, the mesh span for the call to the payment service has the application’s own span nested inside it, and the database and bank calls hang under that. The outer mesh span shows the time on the wire, the inner spans show what the code did. If the outer span is 300 milliseconds and the inner code span is 100 milliseconds, then 200 milliseconds were spent outside your code, in the network, in queues or in retries. That subtraction is one of the great pleasures of joined telemetry.

The mesh tells you how long a request waited. The application tells you what it did with the time.

14Sidecars, Ambient Meshes and Other Designs

Not every mesh puts a proxy next to every service. The integration idea stays the same, but the details move.

The classic design puts one sidecar proxy in every pod. It is simple to reason about, and it costs memory and processor time for every pod. Newer designs try to reduce that cost.

flowchart LR
subgraph Sidecar["Sidecar model"]
S1["App"] --- S2["Proxy in every pod"]
end
subgraph Ambient["Ambient model"]
A1["App"] --- A2["Node level agent for connections"]
A2 --- A3["Optional shared request proxy"]
end
    
FIG 5. Where the proxy lives changes where the telemetry comes from

Ambient mode

Istio’s ambient mode, which has become a supported option in recent releases, removes the sidecar. A small agent on each node, named the zero-trust tunnel, handles secure connections for every pod on that node. It sees connection-level facts such as bytes and connections, and it can emit connection metrics. Where you need request-level detail, such as paths, status codes and spans, you add a shared waypoint proxy for a namespace or service. Request-level traces and metrics then come from the waypoint, so the integration rules from the earlier chapters apply to it.

Other meshes

  • Linkerd uses its own lightweight proxy rather than Envoy and exposes Prometheus metrics. It can send spans to a Collector through its tracing add-on, although the details vary by version.
  • Cilium’s service mesh uses kernel-level technology and a per-node Envoy, which again moves the telemetry source from the pod to the node.
  • Consul typically runs Envoy sidecars, so Envoy-based guidance applies.
i
What Stays the Same

Whatever the design, the questions are the same. Who creates the trace? Do the headers get forwarded? Who decides sampling? How do names and units line up? Ask them for each mesh you adopt, and read its documentation for the exact settings in your version.

15Performance and Overhead

Telemetry is not free. Both the mesh and OpenTelemetry cost something, and together they can cost more than expected.

Every proxy uses memory and processor time, and each hop through a proxy adds a small delay. Producing telemetry adds more. Spans must be built, logs formatted, metrics counted and everything sent over the network. At a handful of services this does not matter. At hundreds, it is a real line on the budget.

CostWhere it comes fromHow to reduce it
Proxy processingBuilding spans, formatting logsLower sampling, filter logs at the proxy
Network trafficSending telemetry to the CollectorBatching and compression
StorageSpans and logs kept at the backendTail sampling, shorter retention, log filtering
Metric seriesMany labels on mesh metricsDrop unneeded labels in the Collector
OperationsRunning the Collector tiersKeep the configuration simple and shared

Spans multiply

A request that passes through five services creates more spans than people expect. Each service can produce a span in the calling proxy, a span in the receiving proxy and one or more spans in the application code. Ten or more spans for a single request is normal. At 1,000 requests per second with ten spans each, that is 10,000 spans per second before sampling. This is why the sampling decision from Chapter 7 matters so much.

Everyday Analogy

Every assistant in the office keeps a diary of each parcel. With ten parcels a day that is easy. With ten thousand parcels a day, the diaries fill warehouses. A sensible office keeps full diaries for the few parcels that went wrong, and short notes for the rest.

T
Measure Before You Tune

Compare the cost of the mesh and the telemetry against what they replace. Many teams find the uniform visibility more than pays for itself, but only if they control sampling, labels and log volume from the start.

16Security and Privacy of Mesh Telemetry

Telemetry describes who talked to whom and what they said. Treat it as sensitive data.

A mesh often exists to improve security, with encrypted connections and identity checks between services. It would be ironic if its telemetry then leaked the very information it protects.

Headers

Do Not Log Secrets

Access logs can include request headers. Authorization tokens, cookies and keys must never be written. Allow-list the headers you record.

Paths

Mind the URLs

Web addresses sometimes contain personal data such as an email or an order number. Redact or hash such values before storage.

Baggage

Keep It Clean

Baggage travels through every proxy and may leave your system if a service calls a third party. Carry only harmless identifiers.

Transport

Encrypt the Path to the Collector

Use TLS between proxies, Collectors and the backend, and authenticate who may send telemetry in.

Access

Limit Who Sees It

Traces expose internal architecture. Give dashboards to those who need them and keep production detail away from broad audiences.

Edge

Do Not Trust Outside Headers

Strip or replace trace headers arriving from the public internet, so outsiders cannot force high sampling or inject fake identities.

The Collector as a privacy filter

Because all telemetry passes through the Collector, it is the natural place to enforce privacy rules. Its processors can delete sensitive attributes, mask parts of a value and drop whole records before anything reaches storage. Writing these rules once, centrally, is far safer than trusting every team to remember them in every service.

!
Check Regularly

Search your stored logs and spans for patterns like email addresses and long tokens, every month. Finding one early is a quick cleanup. Finding one after an audit is a long afternoon.

17Troubleshooting: Reading the Symptoms

Most integration problems show themselves in a handful of ways. Learn the symptoms and the fix is usually short.

When the integration is wrong, the trace view looks wrong in recognizable ways. The flow below turns the most common symptoms into a quick path to the cause.

flowchart TB
S["A trace looks broken"] --> Q1{"Do app spans and mesh spans share a trace id?"}
Q1 -- "No" --> A1["Check header forwarding in the app and the propagation format"]
Q1 -- "Yes" --> Q2{"Is a hop missing?"}
Q2 -- "Yes" --> A2["Check sampling flags and proxy tracing settings for that workload"]
Q2 -- "No" --> A3["Check the export path to the Collector and the backend"]
    
FIG 6. A short decision path for broken traces
SymptomLikely causeFirst thing to check
Each hop appears as a separate tiny traceThe application does not forward trace headersDoes the service use an SDK, or copy the headers by hand?
Application spans exist, but mesh spans are missingProxy tracing is off, or the sampling rate is very lowThe mesh tracing configuration and rate for that namespace
Mesh spans exist, but application spans start new tracesDifferent header formats between proxy and SDKThe propagators configured on both sides
One service appears twice under different namesInconsistent service namingThe Collector renaming rule
Request counts are doubledMesh and application metrics summed togetherWhich metric family each dashboard uses
Latency charts differ by a factor of one thousandMilliseconds mixed with secondsUnit conversion in the Collector
Gaps in data for one namespaceThe Collector is not reachable or is dropping dataCollector health and queue metrics
A flood of spans from the Collector’s own podsThe telemetry feedback loopWhether the Collector pods are inside the mesh

A test that finds most problems

Send one request with a known trace identifier through the system, with the sampling flag set so it is recorded. Then search for that identifier in the backend. You should find the gateway span, each proxy span and every application span, connected as one tree. Wherever the tree breaks, the break is your bug. Keep this as an automatic check after every mesh or Collector upgrade.

T
Check the Proxy’s Own Reports

Proxies and Collectors publish their own health metrics, including how many spans they exported and how many they dropped. A rising drop counter is the earliest warning that the telemetry path itself is struggling.

18Design Patterns and Trade-offs

Eight habits of teams that run mesh and OpenTelemetry together successfully, and the choices they weigh.

Pattern 1

One Collector Path

Send proxy and application telemetry through the same Collector tiers, so every fix and filter applies to both.

Pattern 2

W3C Everywhere

Standardize on W3C Trace Context in the mesh and in every SDK, and accept legacy formats only during migrations.

Pattern 3

Decide at the Edge

Let the ingress gateway make the sampling decision, and make every application follow it with a parent-based sampler.

Pattern 4

Mesh for Edges, App for Insides

Use mesh metrics for traffic between services and application telemetry for business meaning and internal work.

Pattern 5

Normalize in the Collector

Apply naming, unit and label rules centrally, so teams do not each invent their own.

Pattern 6

Keep Interesting Traces

Combine low random sampling with rules in the Collector that keep errors and slow requests.

Pattern 7

Trace Identifier in Every Log

Make every log line, from proxies and applications alike, carry the trace identifier.

Pattern 8

Test the Integration

Run a synthetic end-to-end trace check after each upgrade of the mesh, the SDKs or the Collector.

Trade-off: mesh-only tracing, or full application instrumentation?

Mesh-only tracing

  • No code changes and uniform across all languages.
  • Fast to roll out and easy to explain.
  • Gives a clear map of calls between services.

Limits

  • Cannot see inside services.
  • Traces fall apart where headers are not forwarded.
  • No business meaning on the spans.

Trade-off: sampling at the proxy, or in the Collector?

Sampling at the proxy

  • Cuts cost early, before data leaves the node.
  • Simple to configure.
  • Keeps the Collector load low.

Limits

  • Decides before the outcome is known, so errors may be dropped.
  • Cannot keep only the slow ones.
  • Rate changes require mesh configuration changes.

The usual compromise is a modest sample at the edge, so volume stays manageable, with extra rules in the Collector layer that preserve the high-value traces after the outcome is known. That second step needs all spans of a trace to reach the same Collector, which is a design constraint worth planning from the start.

19Anti-Patterns and Common Mistakes

Four habits that look sensible and quietly break the picture.

ANTI-PATTERN 01Avoid
What Teams Do

They turn on mesh tracing, declare distributed tracing done, and never ask application teams to forward headers.

Why It Hurts

Traces are broken into one-hop fragments. The dashboards look busy but nobody can follow a request end to end.

Do Instead

Treat header forwarding as a requirement for every service, ideally by adopting an OpenTelemetry SDK or agent that does it automatically.

ANTI-PATTERN 02Avoid
What Teams Do

They let the proxy sample at one rate and the applications sample at another, each with its own random choice.

Why It Hurts

Most traces end up half recorded, and the effective rate is a multiple of the two, which nobody predicted.

Do Instead

Decide once at the edge and make every other component follow the parent’s decision.

ANTI-PATTERN 03Avoid
What Teams Do

They add up request counts from the mesh and from the application to get a total.

Why It Hurts

Every request is counted twice, error rates are skewed, and capacity plans are wrong.

Do Instead

Pick one source of truth per question and label the metric families clearly so nobody sums across them.

ANTI-PATTERN 04Avoid
What Teams Do

They send proxy telemetry straight to the vendor and application telemetry through a separate path.

Why It Hurts

Names, labels and sampling cannot be reconciled, and there is no central place to filter or protect data.

Do Instead

Route both through the Collector so normalization, privacy rules and routing live in one place.

Other frequent mistakes

  • Running the Collector inside the mesh without excluding its own traffic.
  • Leaving the demo sampling rate in production, or the production rate in testing.
  • Forgetting that queues and background jobs need trace headers copied into messages.
  • Logging authorization headers in access logs.
  • Upgrading the mesh without re-running the end-to-end trace check.

20A Step-by-Step Rollout Plan

A practical order that delivers value early and avoids the traps above.

1

Stand up the Collector tiers

Deploy a node Collector and a small gateway tier outside the mesh. Confirm they receive test data and reach the backend.

2

Choose the propagation format

Pick W3C Trace Context and set it in the mesh. Configure SDKs to match, with legacy formats only where required.

3

Send mesh traces and logs to the Collector

Point the proxies at the node Collector using OpenTelemetry’s protocol. Start with a high sampling rate in one test namespace.

4

Instrument one service chain

Add an OpenTelemetry SDK or agent to two or three connected services. Use the end-to-end test from Chapter 17 to confirm a whole tree appears.

5

Normalize names, units and labels

Add the Collector rules for service names, seconds versus milliseconds and dropping unneeded labels.

6

Set production sampling and filtering

Lower the edge sampling rate, make applications parent-based and add Collector rules that keep errors and slow requests.

7

Roll out and guard

Expand service by service, add privacy rules and put the end-to-end test into the upgrade checklist.

i
Go Slowly Where It Counts

Rolling out one namespace at a time lets you spot issues such as double counting or exploding label counts while they are still small and cheap to fix.

21Real-World Scenarios

Five illustrative stories showing how the integration pays off.

These scenarios are illustrative composites. The numbers are chosen for clarity and are not measurements from any named company. For background, Envoy began at Lyft, Istio was started by Google, IBM and Lyft, and Linkerd, from the company Buoyant, is credited with popularizing the term “service mesh.”

Scenario 1: The invisible retries

A shopping site’s checkout looks fast in application traces, at about 100 milliseconds, yet customers complain it feels slow. The joined trace shows the mesh span for the payment call lasting 600 milliseconds, with three attempts. The first two attempts hit a server that was restarting. The mesh quietly retried, and the application never knew. The team fixes the restart behavior and the delay disappears.

Scenario 2: The half-sampled traces

A media company enables tracing at ten percent on its proxies. Application teams also sample at ten percent. Engineers wonder why only about one request in a hundred has a complete trace. Switching the applications to follow the gateway’s decision restores nearly every sampled trace to a full tree, with no change in total cost.

Scenario 3: The doubled dashboard

A fintech team builds a dashboard that sums mesh and application request counts. Its error rate looks half what the alerts say. The cause is that the mesh and application both count the same requests, which doubles the denominator for one and not the other. The team picks mesh metrics for service-to-service traffic, labels the application metrics as business events, and the chart finally agrees with the alerts.

Scenario 4: The telemetry that watched itself

A platform team installs the Collector inside the mesh with a sidecar. Within an hour the trace storage fills with spans whose only content is the delivery of other spans. Excluding the Collector pods from the mesh stops the loop, and storage use drops by about a third.

Scenario 5: The canary that looked fine

A team rolls out a new version of a recommendation service to five percent of traffic. Application metrics for the new version look healthy. Mesh metrics, which carry the workload version, show that calls to the new version time out about three times as often, because the new version starts slowly and the proxy gives up on it. Only the proxy saw those timeouts. The team pauses the rollout, adjusts the startup behavior and tries again.

22Frequently Asked Questions

Short answers to the questions people ask most often.

Q1Do I need both a service mesh and OpenTelemetry?
Not necessarily, but they complement each other. A mesh gives uniform network visibility with no code changes. OpenTelemetry gives detail inside services. Together they give a complete trace.
Q2Why are my mesh traces broken into small pieces?
Almost always because the application does not copy the incoming trace headers onto its outgoing calls. An OpenTelemetry SDK or agent does this automatically.
Q3Can the mesh proxy send telemetry directly to OpenTelemetry?
Yes. Envoy-based meshes such as Istio can send spans and access logs using the OpenTelemetry protocol to a Collector. Metrics are usually scraped in the Prometheus format by the Collector. Exact settings depend on your mesh version.
Q4Which propagation format should I use?
W3C Trace Context, in the mesh and in every SDK. If you must support B3 during a migration, configure SDKs to understand both.
Q5Who should decide sampling?
The first component in the path, usually the ingress gateway. Everyone downstream should follow that decision using a parent-based sampler.
Q6Will the two systems double count my requests?
They can if you add their metrics together. Choose one source of truth per question, and keep the families named separately.
Q7Why do the latency numbers differ by a thousand?
Mesh metrics commonly report milliseconds, while the OpenTelemetry convention uses seconds. Convert in the Collector.
Q8Does ambient mode change any of this?
It changes where telemetry comes from. Connection-level data comes from a node agent and request-level data from waypoint proxies. The rules for propagation, sampling and naming remain the same.
Q9Should the Collector run inside the mesh?
Usually not, or at least without tracing of its own traffic, to avoid the feedback loop where delivering telemetry creates more telemetry.
Q10How do I keep costs under control?
Sample traces, filter access logs to errors and a small share of successes, drop unneeded metric labels, and set shorter retention for bulky data.
Q11Where should I begin?
Deploy a Collector outside the mesh, send mesh traces to it in one test namespace, instrument one short chain of services with an OpenTelemetry SDK, and confirm that one full trace tree appears.

23Summary and Key Takeaways

Key Takeaways

  • A service mesh sees the wire, and OpenTelemetry in the application sees the work. Joined together, they show a request’s full story.
  • Send proxy and application telemetry through one Collector path, so naming, filtering and privacy rules live in one place.
  • Traces join only if applications forward trace headers and everyone speaks one propagation format, ideally W3C Trace Context.
  • Make one sampling decision at the edge, and have every other component follow it with a parent-based sampler.
  • Normalize service names and units in the Collector, and avoid summing mesh and application metrics, which double counts requests.
  • Use mesh data for retries, timeouts, circuit breaking, identity and routing, and application data for internal steps and business meaning.
  • Keep the Collector out of the mesh’s tracing loop, protect secrets in logs and headers, control volume, and test the end-to-end trace after every upgrade.