What Is a Span in OpenTelemetry?
A plain-English guide to the building block of distributed tracing — what a span records, how spans connect into traces, and why almost every modern observability tool depends on them.
Imagine your favorite online store feels slow today. A customer clicks “Place order” and waits for six long seconds. Behind that single click, dozens of small programs may have cooperated: one checked the shopping cart, another verified the payment, another updated the stock, and another sent a confirmation email. Somewhere in that chain, one step is dragging. Which one?
The answer lives in a small but powerful idea called a span. A span is a tiny, labeled record of one piece of work, complete with a start time, an end time, and notes about what happened. When many spans are joined together, they draw a map of the whole journey, and the slow step stands out like a traffic jam on a road map.
This guide explains spans from the ground up. You will learn what a span contains, how spans relate to each other, how they travel between programs, and how to use them wisely. Every new word is explained the first time it appears, so you do not need any earlier knowledge of observability.
1What Is a Span?
The short answer first, then the details that make it useful.
A span represents a single unit of work done by a piece of software. Handling a web request is a unit of work. So is reading from a database, calling another service, resizing an image, or sending a message to a queue. For each such piece of work, software can create a span that records what the work was, when it began, how long it took, and whether it succeeded.
Spans are part of OpenTelemetry, often shortened to OTel. OpenTelemetry is a free, open source framework, hosted by the Cloud Native Computing Foundation, that gives developers one standard way to create and collect information about how their software behaves. That information is called telemetry, a word that simply means measurements sent from a distance. OpenTelemetry covers three kinds of telemetry: traces, metrics, and logs. Spans belong to the first kind.
A trace is the complete story of one request as it moves through a system. A trace is made of spans, just as a book is made of pages. One span tells you about one step. The whole trace tells you about the whole journey.
Think about tracking a parcel you ordered online. The whole journey from the warehouse to your door is the trace. Each scan along the way is a span: “packed at the warehouse,” “loaded on the truck,” “arrived at the local depot,” “out for delivery.” Each scan has a start time, an end time, a place, and a result. If your parcel is late, you read the scans to find the leg that took too long. Spans let engineers do the same for software.
What a span tells you
- What happened. Every span has a name, such as “GET /orders” or “charge credit card.”
- When it happened. It records the exact moment the work began and the exact moment it finished.
- How long it took. The duration is simply the finish time minus the start time.
- Whether it worked. A status says if the operation succeeded or failed.
- Extra details. Notes called attributes can say which user, which product, or which database was involved.
- Where it fits. A span knows its parent, which is the span that caused it, so many spans can be drawn as a tree.
What a span is not
A span is not a log line. A log is a single sentence about a moment in time, while a span covers a stretch of time with a beginning and an end. A span is also not a metric. A metric is a number summarizing many events, such as “1,200 requests per minute,” while a span describes one specific event in detail. Finally, a span is not the whole request. It is only one step of it. The whole request is the trace.
Why spans matter to a business
Spans are not only a tool for engineers. Slow pages cost money, because shoppers who wait too long often leave without buying. When a team can see precisely which step is slow, it can repair the right thing instead of guessing, and repairs arrive in hours instead of weeks. Support staff also benefit: when a customer reports a failed order, someone can search for that order’s trace and see exactly where it stopped.
Spans also help teams plan. By looking at many traces over a month, an architect can see which services receive the most calls, which calls are the slowest, and which dependencies are quietly becoming critical. That evidence turns arguments about what to improve next into simple, calm conversations backed by real numbers.
A trace is the journey and a span is one leg of that journey. If you can say “this one operation, from here to there, with these details,” you are describing a span.
2Why Spans Exist: A Short History
Every good idea begins as a response to a painful problem. Spans are no exception.
In the past, most applications were one large program running on one machine. When something went wrong, a developer opened the log file and read it from top to bottom. That worked because everything happened in one place. Then companies began splitting their software into many small services spread across many computers. This style is called a distributed system, meaning the parts run in different places and talk to each other over a network.
Distributed systems brought a new headache. One customer request might touch twenty services, each writing its own separate log file on its own machine. Finding the slow or broken service meant searching twenty places and trying to guess which messages belonged to the same request. Engineers needed a way to follow a single request across all those boundaries.
Google solved the problem inside its own walls and described the solution in a 2010 research paper about a system called Dapper. Dapper introduced the idea of recording each step of a request as a small timed record linked to its parent step. Those records are the ancestors of today’s spans. After the paper appeared, other companies built open source tools inspired by it.
2010 — The Dapper paper
Google explains how it follows requests across thousands of machines using timed records linked in a tree.
2012 — Zipkin
Twitter releases Zipkin as open source, giving the world a ready-made tracing system based on the Dapper ideas.
2015 to 2017 — More tools appear
OpenTracing arrives as a shared way to write tracing code, and Uber creates Jaeger, which later joins the CNCF.
2018 — OpenCensus
Google and others launch OpenCensus, a second project that collects both traces and metrics. Now there are two competing standards, which confuses everybody.
2019 — The merger
OpenTracing and OpenCensus combine into one project: OpenTelemetry. One standard replaces two.
2021 and beyond — Stable tracing
The tracing part of the specification reaches its stable 1.0 level, so companies can build on it with confidence, and support spreads across many programming languages.
Imagine every country using a different shape of electrical plug. Every time you travel, you would need a bag full of adapters. Before OpenTelemetry, each monitoring product had its own plug. Changing products meant rewiring your whole house. OpenTelemetry is like agreeing on one universal plug: you write your instrumentation once, and you can connect it to many different tools.
Instrumentation means adding the code that creates telemetry, such as spans. A program that has been given such code is said to be instrumented. You will see this word again and again.
3Core Concepts Around Spans
A handful of words explain nearly everything in the tracing world. Let us meet them one at a time.
Before looking inside a span, it helps to understand the family of ideas that surround it. Do not worry about memorizing them. Read the cards once, and the later chapters will keep reminding you.
Trace
The full story of one request across all services. Every span in the trace shares the same trace ID, like all the pages of one book sharing a book number.
Span
One timed step inside a trace. It has its own span ID, so it can be told apart from every other step, just as each page has its own page number.
Parent and child
When one operation causes another, the first span is the parent and the second is the child. A web request that calls a database creates a parent span and a child span, like a grandmother and her grandchild in a family tree.
Root span
The very first span of a trace, the one with no parent. It is the trunk of the tree, usually created when a request first enters the system.
Span context
The small bundle of identity that travels with a span: the trace ID, the span ID, a few flags, and some optional extra state. It is like a name badge that lets other programs recognize which trace they have joined.
Resource
A description of the thing that produced the span, such as the service name, its version, and the machine it runs on. It answers the question “who made this span?”
Instrumentation scope
The name and version of the library that created the span, such as a web framework or a database driver. It answers “which piece of code recorded this?”
Context propagation
The technique of passing the span context from one program to the next, so spans created in different places can be stitched into the same trace.
The tree shape of a trace
Because every span (except the root) points to its parent, a trace naturally forms a tree. The root is at the top, and each step that it triggers hangs beneath it. Siblings, meaning spans with the same parent, may happen one after another or at the same time. Drawing the tree is the first thing most tracing tools do, and it is the reason a trace is so easy to read.
Picture a restaurant order. The waiter writes one order slip, which is the root. From that slip, the kitchen starts three jobs: grilling the meat, chopping the salad, and baking the bread. The grill job might have its own smaller tasks, such as seasoning and flipping. Every task knows which job started it. Draw all of them with lines to their parent and you have a trace tree.
A span is a small object in memory. Creating one takes a tiny amount of time, which is why large companies can create spans for millions of requests. Even so, creating them wastefully can add up, and we will see how to control that in the chapter on sampling.
4The Anatomy of a Span
Open a span like a toolbox and look at every compartment.
Every span, no matter which programming language created it, carries the same set of fields. That sameness is the entire point of a standard. A tool that understands one span can understand all of them.
| Field | What it holds | Simple comparison |
|---|---|---|
| Name | A short label for the operation | The title of a chapter |
| Span context | Trace ID, span ID, flags, optional trace state | A passport number |
| Parent span ID | The ID of the span that started this one, or nothing for a root span | The parent’s name in a family tree |
| Kind | The role the span plays: internal, server, client, producer, or consumer | The job title |
| Start and end time | Exact moments the work began and finished | Clock-in and clock-out |
| Attributes | Key and value details about the work | Sticky notes on a page |
| Events | Timestamped messages inside the span | Diary entries during a trip |
| Links | References to spans in other traces | “See also” notes |
| Status | Unset, Ok, or Error, with an optional message | The final grade |
Name
The name should describe a kind of operation, not one individual case. A good name for a web request is “GET /orders/{id},” where the part in braces is a placeholder. A bad name would be “GET /orders/48291,” because that creates a different name for every order and floods your tools with endless variations. Names with a small, predictable set of values are said to have low cardinality, and that is exactly what you want.
Attributes
Attributes are pairs of a key and a value that describe the work. For a database span, attributes might say which database system was used and the name of the table. For a web span, they might include the method, the route, and the response code. OpenTelemetry publishes semantic conventions, which are agreed names for common attributes, so that everyone calls the same thing by the same name. Following them means your dashboards and other people’s dashboards understand each other.
Events
An event is a message with a timestamp that happens at one moment inside a span. For example, during a checkout span you might record an event when a discount code was applied and another when stock was reserved. When a program catches an error, it can also record the error as an event on the span, with the message and the place where it occurred. Events are lighter than creating new spans, which makes them ideal for noteworthy moments that take no meaningful time.
Links
Sometimes a span is related to another span, but it is not its child. A link points to another span’s identity without creating a parent relationship. This is helpful when one job collects many incoming requests into a batch. The batch span can link back to every request span that contributed, even though those requests belong to different traces.
Status
Status says how the operation ended. It has three possible values. Unset is the default and means “nothing special to report.” Error means the operation failed. Ok means the developer deliberately confirmed success, which is rarely needed. Choosing the right one takes a little care, as the note below explains.
Resource and scope travel with the span
Two more pieces of information accompany every span even though developers rarely set them by hand. The resource describes the program that produced the span, with details such as the service name, its version, and the cluster or machine it runs on. The instrumentation scope names the library that recorded the span, for example a web framework or a database driver. Together they answer two practical questions during an investigation: which service made this span, and which piece of code made it? Setting the service name correctly is one of the most valuable five minutes you can spend, because without it every span in your tool carries the same unhelpful label of an unknown service.
Suppose a customer asks a server for a page that does not exist, and the server correctly answers “not found.” The server did its job properly, so its span is normally left unset, not marked as an error. The caller who expected the page may well mark its own span as an error. Think of it this way: the server only fails when the server itself breaks.
5Span Kinds and Trace Trees
Not every span plays the same role. The kind of a span tells tools how to draw the conversation between services.
Each span carries a kind, a label that describes its role in a conversation. Tools use the kind to draw arrows between services and to calculate things like network time. There are five kinds.
| Kind | When to use it | Simple comparison |
|---|---|---|
| Server | A program is handling a request that someone else sent it | The shop assistant answering a customer |
| Client | A program is sending a request to someone else and waiting for the reply | The customer asking the question |
| Producer | A program creates a job or message to be handled later | Dropping a letter into a mailbox |
| Consumer | A program processes a job or message created earlier | The postal worker opening the letter |
| Internal | Work that happens inside one program and crosses no boundary | Someone thinking quietly at their desk |
Client and server spans come in pairs. When service A calls service B and waits, A creates a client span and B creates a server span. The time on A’s client span is always a little longer than B’s server span, and that gap reveals the time spent on the network and in queues between them. Producer and consumer spans work the same way for messages, except the producer does not wait for the consumer.
The kind matters because tools make decisions from it. A monitoring product uses server spans to count the requests a service handled, and it uses client spans to draw an arrow showing which service called which. If you label every span as internal, your service map will show a collection of unconnected dots instead of a web of conversations. Choosing the right kind is therefore a small act of kindness toward everyone who will read your traces later.
flowchart TD
A["Root span: GET checkout, kind Server"]
B["Child span: validate cart, kind Internal"]
C["Child span: call payment service, kind Client"]
D["Span in payment service: POST pay, kind Server"]
E["Child span: query orders table, kind Client"]
F["Child span: publish order created, kind Producer"]
G["Span in email service: send receipt, kind Consumer"]
A --> B
A --> C
C --> D
D --> E
A --> F
F -.->|message queue| G
Read the diagram from the top. The root span is the web request arriving at the store. It has three children: an internal step that validates the shopping cart, a client span calling the payment service, and a producer span that publishes a message saying the order was created. The payment service answers with its own server span, which in turn makes a database call. Later, the email service picks up the message and sends a receipt. The dotted line shows that a queue sits between the producer and the consumer, so the two spans are connected by identity but not by a waiting caller.
If a span does not clearly describe a call into or out of the program, mark it as internal. That is the default kind, and it is a perfectly respectable choice for most inner steps.
6How Spans Travel Between Services
A span can only join a trace if it knows which trace to join. This chapter shows how that knowledge is passed along.
When service A calls service B over the network, B is a completely separate program. It has no way to peek into A’s memory. So how does B know that its work belongs to the same trace? The answer is context propagation: A attaches the span context to the outgoing request, and B reads it from the incoming request.
At a music festival, you get a colored wristband at the front gate. Every stage, food stall, and security guard can look at your wristband and know that you belong to the same ticket group. You never have to explain yourself again. The span context is that wristband, and the network request is your wrist.
Most systems carry the context in an HTTP header, a small label attached to a web request. The widely used standard is the W3C Trace Context format, and its header is called traceparent. It packs four pieces of information into one line: a version number, the trace ID, the ID of the calling span (which will become the new span’s parent), and a few flags, including one that says whether this trace is being recorded. OpenTelemetry uses this format by default. Older formats, such as the B3 headers from Zipkin, are also supported for compatibility.
sequenceDiagram
participant U as User Browser
participant A as Store Service
participant B as Payment Service
participant D as Database
U->>A: 1. Request arrives with no trace
Note over A: 2. Start root span and make trace ID
A->>B: 3. Call with traceparent header
Note over B: 4. Read header and start child span
B->>D: 5. Query with new child span
D-->>B: 6. Return rows
B-->>A: 7. Return payment result
Note over A: 8. End spans and send to collector
A-->>U: 9. Send the page
Here is the story in order. In step one, the browser sends a request that carries no trace information, so the store service starts a brand new trace and creates the root span in step two. In step three it calls the payment service and adds the traceparent header. The payment service reads that header in step four and starts a child span with the same trace ID. When it queries the database in step five, it repeats the idea by creating another child span. When the answers flow back, each service ends its spans and sends them away for storage and display.
Using a shared header format matters because the services in a large company are rarely written in the same language or by the same team. One service may be written in Java, another in Go, and a third in Python. When all of them read and write the same traceparent header, a trace passes through every one of them without any translation. This is a quiet but important reason why standards win over private conventions.
Two things that are not the same
Do not confuse the trace context with baggage. The trace context carries only identity. Baggage is a separate bundle of key and value pairs, such as a customer tier or a test flag, that you deliberately want every downstream service to see. Baggage is powerful, but it travels with every call, so it should be small and must never carry secrets.
Crossing queues and background jobs
The same idea works when a message waits in a queue. The producer writes the span context into the message’s metadata, and the consumer reads it later. The consumer can either make its span a child of the producer span, or create a fresh trace and add a link back to the producer. Links are usually better for large batches, because a single consumer may handle hundreds of messages at once, and a tree with hundreds of parents would make no sense.
Traces break when context is lost on the way. This happens when a program hands work to a background thread or an asynchronous task without carrying the context along, or when a proxy strips unknown headers. The symptom is a trace that suddenly stops, with the later spans appearing as separate, disconnected traces. If a trace looks cut in half, suspect lost context first.
7The Life of a Span
A span is born, does its job, and is sent away. Following that life explains most of the surprises people meet in practice.
Every span passes through the same stages, in the same order. Understanding the order makes it easy to see where things can go wrong, such as a span that never appears in your tracing tool.
A span behaves like a stopwatch used at a school sports day. A helper presses start when the runner leaves the line, scribbles a few notes while the race goes on, and presses stop at the finish. Only then is the result written on the scoreboard. If the helper forgets to press stop, the time never reaches the scoreboard, and nobody knows how the runner did.
flowchart LR
A["1. Start span and record start time"] --> B["2. Make it the active span"]
B --> C["3. Add attributes and events"]
C --> D{"4. Did the work fail?"}
D -->|Yes| E["Record the error and set status to Error"]
D -->|No| F["Leave status Unset or set Ok"]
E --> G["5. End span and record end time"]
F --> G
G --> H["6. Span processor queues it"]
H --> I["7. Exporter sends a batch"]
I --> J["8. Collector or backend stores it"]
Stage by stage
Starting. When work begins, the program asks a tracer to start a span. A tracer is simply the object that creates spans for one library or one piece of code. If an active span already exists, the new span automatically becomes its child. If none exists, the new span becomes a root.
Making it active. The new span is placed into the current context, a small storage area that follows the running code. Any span created next, deeper in the same flow, discovers the active span and attaches itself beneath it. That is how a tree forms without developers passing parents around by hand.
Enriching. While the work runs, the program adds attributes and events. It might note the number of items in the cart or record the moment a retry began. These small notes cost very little and are the most valuable part of the span when someone investigates a problem later.
Handling failure. If something breaks, the code records the error as an event and sets the status to Error. Failing to do so means the span looks healthy even though the operation was not.
Ending. The program calls end, which stamps the finish time. After that moment the span is frozen. Changes made to it afterward are ignored, so all notes must be added before the end.
Processing and exporting. A span processor receives every finished span. The usual choice is a batch processor, which collects spans in a queue and sends them in groups every few seconds. This is far cheaper than sending each span alone. An exporter then ships each batch to a collector or a backend.
A note about clocks
Every span reads time from the clock of the machine that created it. Inside one machine, durations are very trustworthy, because both the start and end come from the same clock. Across machines, clocks can disagree by a few milliseconds, so a child span on another computer may appear to start slightly before its parent or end slightly after it. This is called clock skew. It is normal and does not mean the trace is broken. Rely on the parent and child relationships for the structure of a trace, and on each span’s own duration for how long it took.
Exporters send only finished spans. If code starts a span but skips the end call, for example because an error jumped out of the middle of the function, the span silently disappears and may also hold memory. Always end spans in a place that runs even when failures happen, or use the helper functions that end spans for you.
8Where Spans Go: The Pipeline
A span is only useful once someone can see it. Here is the road it travels from your code to your screen.
OpenTelemetry splits its work into clearly separated parts. This keeps your application code simple and lets you swap tools without rewriting anything.
The API
The set of functions your code calls to start spans and add notes. It contains no heavy logic, and if no SDK is installed it quietly does nothing, so libraries can add spans without forcing a choice on their users.
The SDK
The working engine behind the API. It creates real spans, applies sampling, runs processors, and calls exporters. Application owners configure it.
Instrumentation
The code that actually makes spans. Automatic instrumentation attaches to popular libraries for you. Manual instrumentation is code you write for your own business steps.
Exporter and OTLP
The exporter sends spans out. The default language is OTLP, the OpenTelemetry Protocol, which travels over gRPC or HTTP.
The Collector
An optional standalone program that receives telemetry, cleans it, and forwards it. It has receivers to accept data, processors to change it, and exporters to send it onward.
The backend
The place where spans are stored and displayed, such as Jaeger, Zipkin, Grafana Tempo, or a commercial service. You can change it without touching your application.
flowchart LR
subgraph APP["Your application"]
CODE["Your code and libraries"]
API["OpenTelemetry API"]
SDK["OpenTelemetry SDK"]
PROC["Span processor and sampler"]
EXP["OTLP exporter"]
end
subgraph COL["OpenTelemetry Collector"]
REC["Receiver"]
CPR["Processors: batch, filter, redact"]
CEX["Exporters"]
end
subgraph BACK["Backends"]
B1["Jaeger"]
B2["Zipkin"]
B3["Grafana Tempo"]
end
CODE --> API
API --> SDK
SDK --> PROC
PROC --> EXP
EXP -->|OTLP| REC
REC --> CPR
CPR --> CEX
CEX --> B1
CEX --> B2
CEX --> B3
Follow the arrows. Your code calls the API. The SDK, configured once at start-up, creates the spans and decides whether to keep each one. Finished spans enter the processor, and the exporter sends them as OTLP to the Collector. The Collector can add tags, remove sensitive details, group spans into efficient batches, and then send copies to as many backends as you like. Your application never needs to know which backend is in use.
Think of the postal system. Your application drops letters into a street mailbox, which is the SDK and exporter. A local post office, the Collector, sorts them, stamps them, and throws away junk mail. Finally, letters reach big sorting warehouses, the backends. Because the mailbox and the post office speak the same standard, you can change which warehouse stores your letters without ever changing the mailbox.
It is also worth knowing what happens when things go wrong. The batch processor keeps finished spans in a queue of limited size. If the Collector or backend is unreachable for too long, the queue fills, and the oldest or newest spans are dropped so the application is never slowed down or crashed. This design protects your customers first and your telemetry second, which is exactly the right priority. It is also the reason why watching dropped-span counts is so important.
You can export directly from an application to a backend, and that works well for small projects. A Collector becomes valuable when you have many applications. It moves heavy work out of your code, lets you change destinations in one place, keeps backend passwords out of every service, and can protect sensitive data before it leaves your network.
9Sampling: Keeping the Right Spans
Recording every span of every request is wonderful and also expensive. Sampling is how teams find a sensible middle.
Imagine a busy service handling ten thousand requests per second. If each request creates fifteen spans, that is a hundred and fifty thousand spans every second. Storing and searching them all can cost more than running the service itself. Sampling means keeping only a portion of the traces and discarding the rest, while still leaving enough to answer your questions.
A shopping mall has hundreds of security cameras, and keeping every hour of video forever would fill a warehouse. So the mall keeps all the footage from the night a window was broken, and only a few sample hours from ordinary days. Sampling for spans follows the same logic: keep the interesting stuff in full and a representative slice of the boring stuff.
Head sampling
In head sampling, the decision is made at the very beginning of a trace, when the root span is created. A simple rule might say “keep one trace in ten.” The decision is written into the trace flags that travel in the traceparent header, so every downstream service follows the same choice. A sampler that respects the caller’s decision is often called parent-based. Head sampling is cheap and simple, but it has a weakness: it decides before knowing whether the request will turn out to be slow or broken, so it can throw away exactly the traces you wanted.
Tail sampling
In tail sampling, the decision is made after the whole trace has finished, usually inside the Collector. At that point rules can be much smarter, such as “keep every trace with an error, keep every trace slower than two seconds, and keep five percent of the rest.” The cost is that the Collector has to hold the spans of each trace in memory until the trace is complete, and all spans of one trace must reach the same Collector instance.
| Question | Head sampling | Tail sampling |
|---|---|---|
| When is the decision made? | At the start of the trace | After the trace is complete |
| Where does it run? | In the application SDK | Usually in the Collector |
| Can it keep all error traces? | No, it does not know yet | Yes |
| How expensive is it? | Very cheap | Needs memory and careful routing |
| How easy is it to run? | Easy | More complex |
Choosing a starting point
Many teams begin by keeping a small percentage of traces, perhaps one in a hundred for very busy services and every trace for quiet ones. They then watch two things: whether the kept traces are enough to answer real questions, and whether the monthly bill is comfortable. If an investigation keeps failing because the right trace was thrown away, the rate goes up or a smarter rule is added. If the bill is too high, the rate goes down. Sampling is not a decision made once; it is a dial that you adjust as your system and your needs change.
Good sampling keeps or drops an entire trace together. If each service flips its own coin, you end up with traces that have holes in the middle, which are confusing and hard to trust. Using the parent’s decision, as described above, prevents this.
10Security and Privacy for Spans
Spans are rich in detail, and rich detail can contain things that should never leave the building.
Because spans describe real traffic, they can capture sensitive information by accident. A database span that records the full text of a query might include a customer’s email address. A web span that records every header might include a secret login token. Once such data lands in a tracing tool, many more people can see it than ever should.
Risks and remedies
| Risk | Example | Remedy |
|---|---|---|
| Personal data in attributes | Names, emails, or phone numbers added to a span | Do not record them. Use opaque identifiers instead, and remove leftovers in the Collector. |
| Secrets in headers or queries | Authorization tokens or passwords in recorded headers and query text | Record only an allow-list of safe headers and replace query values with placeholders. |
| Data interception | Spans traveling across the network in clear text | Use encrypted connections between applications, Collectors, and backends. |
| Forged trace headers | An outsider sends a fake traceparent to force recording or to confuse your traces | At public entry points, ignore or restart incoming trace context, and link to it if needed. |
| Baggage leaks | Internal details passed along to outside companies | Keep baggage tiny and strip it before calls to third parties. |
| Too many viewers | Everyone in the company can read every trace | Apply access controls and sensible retention periods in the backend. |
Writing sticky notes on a package is useful for the delivery staff. But if you write your house key code on one, everyone who handles the package can read it. The rule is the same for spans: write down what helps you find problems, and never write down what would hurt you if a stranger read it.
Trusting the incoming header
Inside your own network, accepting the traceparent header from a trusted service is exactly what you want. At the edge, where requests arrive from the open internet, caution is wise. A stranger could send headers that force every request to be recorded, filling your storage and your bill. A common approach is to start a new trace at the public edge and store the outsider’s identity only as a link.
Retention and access
Two quiet questions deserve an answer before you launch. First, how long should spans be kept? Keeping traces for a couple of weeks is often enough for debugging, and shorter retention reduces both cost and exposure if something goes wrong. Second, who may read them? Treat your tracing tool like any other system that holds business data: give people only the access they need, and keep a record of who looked at what. These small policies cost almost nothing to set up and are painful to add after an incident.
Asking every team to remember every privacy rule is fragile. Put redaction rules into the Collector, so that even a careless service cannot leak a secret into storage. Combine this with code reviews that check which attributes new instrumentation adds.
11Spans, Logs, and Metrics Working Together
Spans are one of three signals. They are strongest when the other two stand beside them.
OpenTelemetry offers three signals, and each answers a different question. Metrics tell you that something is wrong, quickly and cheaply. Traces, made of spans, tell you where it is wrong. Logs tell you why, in the words of the program itself. The three become far more powerful when they are connected.
Spans and logs
When a log message is written while a span is active, OpenTelemetry can stamp the log with the trace ID and span ID. In your tool, you can jump from a span to the exact log lines that belong to it, and back again.
Spans and metrics
A metric chart can carry small markers called exemplars that point to example traces. When you spot a spike in slow requests, one click takes you to a real trace from that moment.
Metrics made from spans
The Collector can count spans and measure their durations to produce request rate, error rate, and latency numbers without writing any extra code. These are often called the RED metrics: Rate, Errors, and Duration.
A typical investigation
An alert fires
A metric shows that checkout requests are slower than usual and some are failing.
Open an example trace
From the chart, you follow an exemplar to a slow trace and look at the tree of spans.
Find the slow span
One span for a call to the inventory service takes four seconds, while all the others take milliseconds.
Read the attributes and events
The span shows that the call retried three times, and an event records a timeout.
Read the matching logs
Logs tagged with the same span ID reveal that the inventory database ran out of connections. The team knows what to fix.
Watching the watchers
The pipeline that carries spans also needs monitoring. If it silently drops data, you will believe everything is fine when you are actually blind. Useful things to watch include the number of spans dropped because a queue was full, the number of failed exports, the size of the Collector’s memory, and the delay between a span ending and appearing in the backend. The Collector publishes metrics about itself for exactly this purpose.
Service maps and health at a glance
Because every client span names the service it called, tracing tools can draw a service map, a picture of boxes and arrows showing how your programs depend on one another. Colors or numbers on the arrows show how many calls flow along them and how many fail. For a newcomer joining a team, this map is often the first honest diagram of the system they have ever seen, since it is drawn from real traffic and not from someone’s memory.
The best time to connect logs to traces is the day you set up logging. Turn on the setting that adds trace and span IDs to every log record. Doing this later, in the middle of an outage, is far more painful.
12Benefits, Drawbacks, and Trade-offs
Tracing with spans is a wonderful tool, and like any tool it has costs.
Advantages
- End-to-end visibility. You see one request across every service it touched.
- Fast problem finding. The slowest or failing step stands out in seconds instead of hours.
- Clear dependencies. Traces reveal which services call which, even when no diagram exists.
- Vendor freedom. A common standard means you can change backends without rewriting code.
- Rich context. Attributes and events explain what the work was, not only how long it took.
- Wide language support. Libraries exist for most popular programming languages.
Disadvantages
- Extra overhead. Creating and exporting spans uses a little processing power, memory, and network capacity.
- Storage cost. Large volumes of spans can become surprisingly expensive.
- Broken traces. Missing context propagation in one service leaves gaps for everybody.
- Privacy risk. Careless attributes can leak sensitive information.
- Learning effort. Teams must learn the vocabulary, conventions, and sampling choices.
- Uneven maturity. Support and features differ between languages and libraries.
Common trade-offs in plain language
| Choice | What you gain | What it costs |
|---|---|---|
| Automatic instrumentation | Spans appear quickly with almost no code | Less control, and sometimes too much detail |
| Manual instrumentation | Spans that match your business steps exactly | More code to write and maintain |
| Many fine-grained spans | A very detailed picture of each request | More overhead and bigger bills |
| Few coarse spans | Cheap and easy to read | Slow parts hide inside big spans |
| Sampling heavily | Much lower cost | Rare problems may be missed |
| Keeping everything | Nothing is ever missing | High storage and processing cost |
| Exporting through a Collector | Central control and cleaner applications | One more service to run and watch |
It helps to estimate the cost before you begin. Multiply the number of requests per second by the number of spans per request, then by the share you plan to keep after sampling. The result is the number of spans per second that your pipeline must carry and your backend must store. Doing this small piece of arithmetic early prevents unpleasant surprises, and it shows clearly why sampling and sensible span counts matter so much for busy services.
If your application is a single program with one database, good logs and a few metrics may be enough for now. Spans pay for themselves when requests cross several services, when many teams share one system, or when speed problems are hard to pin down by guessing.
13Good Habits and Design Patterns
Patterns are tried-and-true recipes. Habits are the small routines that keep your traces useful instead of noisy.
One span per meaningful operation
Create a span for work that a human would name: handling a request, calling another service, running a database query, processing a message. Skip steps that are too tiny to matter.
Automatic first, manual second
Start with automatic instrumentation to get the framework, client, and database spans for free. Then add manual spans only around the business steps that matter to your team.
Propagate everywhere
Make sure every outgoing call carries the trace context, including calls to queues and background workers. One missing hand-off breaks the trace for everyone downstream.
Link for batches
When one job handles many messages, create one span for the job and add links to the messages, instead of forcing a tree that makes no sense.
Tail sampling for errors
Use the Collector to keep every failed or slow trace and a small share of the normal ones, so you pay for the interesting traces only.
Spans feeding metrics
Derive rate, error, and duration numbers from spans, so your charts and your traces always tell the same story.
Adding spans to a new service, step by step
Name the service
Give the service a clear name and version in its resource, so every span is labeled with its true owner.
Turn on automatic instrumentation
Let the ready-made libraries create spans for incoming requests, outgoing calls, and database queries.
Check propagation
Call another service and confirm that both services appear in the same trace tree.
Add business spans and attributes
Wrap the one or two steps your team cares about most, and add a few helpful attributes.
Choose a sampling rule and watch the cost
Start with a modest share of traces, keep errors, and review the bill after a week.
Habits that pay off
- Name spans by what they do, not by who asked. Prefer “GET /orders/{id}” to a name with a real order number in it.
- Follow semantic conventions. Use the agreed attribute names for web calls, databases, and messaging, so every tool understands your data.
- Add useful attributes. Record things you would ask during an investigation, such as the type of customer plan, the number of items, or the region, but keep each value small.
- Record errors properly. When something fails, attach the error to the span and set its status to Error. Do not hide failures.
- Always end spans. Put the end call somewhere that runs even when an error occurs.
- Add service identity. Make sure every service sets its name and version in the resource so you can always tell who made a span.
- Test your traces. After adding instrumentation, send a test request and look at the tree. If it looks wrong to you, it will look wrong to the team during an outage.
- Review attributes like code. Ask in every review: could this value contain a secret or personal data?
Read the span name aloud as if it were a headline in a newspaper. If it describes a type of event that could happen many times a day, it is probably a good name. If it describes one single, specific event, it is probably too detailed.
14Common Mistakes, Anti-patterns, and Myths
Learning from other people’s mistakes is much cheaper than making your own.
An anti-pattern is a habit that looks sensible at first but causes trouble later. These four appear again and again in real tracing projects.
What it looks like
A team wraps almost every function in a span, producing traces with hundreds of tiny spans that last a fraction of a millisecond.
Why it hurts
The trace becomes unreadable, the overhead grows, and storage bills balloon. The important spans disappear in the crowd.
Better approach
Create spans for operations worth naming, and use events or attributes for small details inside them.
What it looks like
Span names include user IDs, order numbers, or full web addresses with changing parts.
Why it hurts
Each unique name creates a new category in your tools, making searching, grouping, and charting almost impossible, and sometimes raising costs sharply.
Better approach
Use stable names with placeholders, and put the changing value into an attribute.
What it looks like
Developers record full request bodies, login headers, or raw database queries with real values, “just in case” they help debugging.
Why it hurts
Sensitive data spreads into storage, dashboards, and backups where it may be seen by many people and kept for a long time.
Better approach
Record an allow-list of safe fields, use identifiers instead of personal data, and add redaction rules in the Collector.
What it looks like
A service uses a background thread, a queue, or an unusual network library that does not carry the trace context.
Why it hurts
The trace breaks into separate pieces at that point, and nobody can see the whole journey. Teams then wrongly conclude that tracing does not work.
Better approach
Test every boundary on purpose. Pass context explicitly across threads and queues, and check that the proxies in between forward the trace headers.
Myths and realities
| Myth | Reality |
|---|---|
| A span is just a fancy log line. | A span covers a stretch of time, has a parent, and joins other spans into a trace. A log line records a single moment. |
| A trace is the same thing as a span. | A trace is the whole journey, made of many spans. |
| Every failed request must have status Error on every span. | Status should reflect whether that particular operation failed. A server correctly answering “not found” has not failed. |
| OpenTelemetry is a monitoring product. | It is a standard and a set of tools for producing telemetry. You choose a separate backend to store and display it. |
| More spans always means better visibility. | Too many spans create noise and cost. Meaningful spans create clarity. |
| Sampling means losing information carelessly. | Smart sampling keeps the traces that matter, such as errors and slow requests, while discarding repetitive ones. |
15A Worked Example and Real-World Stories
Let us read a real-looking trace the way an engineer would.
Return to the online store from the beginning of this guide. A customer complained that checkout took almost half a second longer than normal. The tracing tool shows the following spans. Start and duration are measured in milliseconds from the moment the root span began.
| Span | Parent | Kind | Starts at | Lasts |
|---|---|---|---|---|
| GET /checkout | None (root) | Server | 0 | 480 |
| validate cart | GET /checkout | Internal | 5 | 20 |
| call payment service | GET /checkout | Client | 30 | 350 |
| POST /pay | call payment service | Server | 45 | 320 |
| query orders table | POST /pay | Client | 60 | 250 |
| publish order created | GET /checkout | Producer | 385 | 15 |
| send receipt | publish order created | Consumer | 600 | 90 |
Reading from the top, the whole request took 480 milliseconds. Validating the cart took only 20 and is not a problem. The payment call took 350 milliseconds, almost three quarters of the total. Inside it, the payment service spent 320 milliseconds working, so only about 30 milliseconds went to the network and waiting. Inside that, a single database query took 250 milliseconds. That one span is the bottleneck. The engineer opens it, reads the attributes, and sees that the query scanned an entire table because an index was missing. Adding the index fixes the problem. Without the span tree, the team might have spent days blaming the network, the payment company, or the web servers.
Where spans came from in real life
Google and Dapper
Google needed to understand why some searches were slow when each search touched many machines. Its Dapper system recorded timed steps linked in a tree for sampled requests, and the 2010 paper describing it inspired nearly every tracing tool that followed, including the idea of the span itself.
Twitter and Zipkin
Twitter built Zipkin to trace requests through its fast-growing set of services, and released it as open source in 2012. For many teams, Zipkin was the first tracing tool they ever tried, and its B3 headers are still understood by OpenTelemetry today.
Uber and Jaeger
A single ride request at Uber touches a large number of services. Engineers created Jaeger to record and display those paths, and later gave it to the CNCF. Jaeger can receive spans in the OpenTelemetry format.
Lyft and Envoy
Envoy, a proxy that began at Lyft, can create spans for the traffic that flows through it and can forward trace headers. This means even programs that were never modified appear in traces, because the proxy in front of them reports their traffic.
16Frequently Asked Questions
17Summary and Key Takeaways
The whole story in a few simple ideas you can carry with you.
Key Takeaways
- A span is one timed unit of work. It records a name, a start and end time, a status, and details, and it tells you what happened in one step of a request.
- Spans join to form traces. Every span in a trace shares a trace ID, and each span points to its parent, creating a tree you can read at a glance.
- A span has a clear anatomy. Its context, kind, attributes, events, links, and status work together to explain not only how long something took but why.
- Context propagation is the glue. The trace context travels in headers or message metadata so spans from different services land in the same trace.
- Spans must be ended, sampled, and exported with care. The SDK, processors, exporters, and the Collector form a pipeline that turns finished spans into something you can search.
- Protect your data and your budget. Keep secrets out of attributes, redact centrally, and use sampling to keep the traces that matter most.
- Spans are strongest alongside logs and metrics. Connect the three signals with trace IDs and exemplars, and you can move from alert to root cause in minutes.