What Is Span Kind in OpenTelemetry?

What Is Span Kind in OpenTelemetry?

A friendly, ground-up guide to the small label on every span that says what role it plays in a conversation between programs: who is asking, who is answering, who is sending a message and who is picking it up, and why getting it right makes your traces, maps and metrics trustworthy.

Imagine watching a play. There are actors on the stage, and each has a role: one is the customer, one is the waiter, one is the cook. If the programme only listed the actors’ names and the times they spoke, you would not know who was asking and who was answering. But once each name carries a role, the whole scene makes sense. You can see that the customer ordered, the waiter carried the order, and the cook prepared it.

A span in OpenTelemetry is one timed step in the story of a request. By itself, it has a name, a start time and an end time. But it also carries a small label that tells you the role of that step in the conversation between programs. That label is called the span kind. In this guide, we will learn what span kind is, the five kinds that exist, how to choose the right one, and why this tiny label has such a big effect on service maps, metrics and troubleshooting, using simple words and everyday examples.

5
Kinds of span: internal, server, client, producer and consumer
2
Natural pairs: client with server, and producer with consumer
Internal
The default kind when you do not choose one

1The Problem: Spans Without Roles

Before learning the idea, let us feel the problem it solves.

A quick reminder about traces and spans

A trace is the complete story of one request as it travels through your system. A span is one timed step in that story. When a customer places an order, there may be a span for the web gateway, a span for the order service, a span for the database query, and a span for the payment call. The spans are joined into a tree by a shared trace ID and by parent and child links.

A tree is not enough

Suppose a tool shows you only a tree of spans, each with a name and a duration. You can see that “order service” took 400 milliseconds and that inside it, “charge card” took 300. But many important questions are still unanswered.

  • Which spans are the start of work in a new program? Where does one service end and the next begin?
  • Which spans are calls to someone else? A call to the payment service is very different from a calculation inside your own code.
  • How much of that 300 milliseconds was the network, and how much was the payment service thinking?
  • Which services depend on which? Can you draw a map of the whole system from the data?
  • What counts as a request that the service handled? This matters for counting requests per second and error rates.

The answer to all of these lies in a simple label on each span: its role in the conversation. That label is the span kind.

Everyday Analogy

Think of a phone call between two offices. The log of the call says “10:00 to 10:05.” But to understand the call, you must know who dialled and who answered. The caller’s log says “I made a call.” The receiver’s log says “I received a call.” Together, these two entries describe one conversation from both sides. Span kind is the “I made a call” or “I received a call” marker on each span.

2So, What Is Span Kind?

The short answer first, and then the pieces.

Span kind is a field on every span that describes the relationship between that span and its parent and children in the trace, especially whether the span is about a call that crosses from one program to another, and in which direction. It tells tools and people what role the span plays: asking, answering, sending, receiving, or just working internally.

Span kind says what role this step plays in the conversation, not what the step is about.

Three simple facts

Fact 1

Chosen at Creation

You pick the kind when the span is created. It cannot be changed afterwards, since it is part of how the span is first described.

Fact 2

One Kind per Span

Every span has exactly one kind. If none is chosen, the default is internal.

Fact 3

Not Sent Over the Network

The kind is not part of the trace headers that travel between services. Each side sets the kind of its own span independently.

What span kind is not

  • It is not the span name. The name says what the work is, such as “charge card.” The kind says what role the span plays.
  • It is not a status. Status says whether the work succeeded. Kind says nothing about success.
  • It is not an attribute you invent. It is a dedicated field with a fixed list of five values.
  • It is not about the technology. A span for a database call and a span for a web call are both “client” spans, because both are the asking side of a conversation.

3The Five Kinds at a Glance

There are only five. Once you learn them, you know the whole list.

flowchart TD
    A["Span kind"] --> B["Synchronous conversations"]
    A --> C["Asynchronous conversations"]
    A --> D["No conversation"]
    B --> B1["Client: I am asking"]
    B --> B2["Server: I am answering"]
    C --> C1["Producer: I am sending a message"]
    C --> C2["Consumer: I am processing a message"]
    D --> D1["Internal: I am working inside my own program"]
    
Figure 1: The five span kinds grouped by the kind of conversation they describe
KindPlain MeaningTypical Example
ClientThis span covers a request I am making to another program and waiting for the answerCalling the payment service, or running a database query
ServerThis span covers handling a request that came in from another programThe order service handling an incoming web request
ProducerThis span covers starting an asynchronous job by sending a message that someone will pick up laterPlacing an “email to send” message on a queue
ConsumerThis span covers processing a message that was sent earlier by a producerThe email worker reading and handling that message
InternalThis span covers work inside my own program that does not cross a boundaryCalculating a price, or rendering a template
Everyday Analogy

Think of a restaurant. The customer asking the waiter is the client. The waiter answering at the table is the server. The waiter pinning an order slip on the kitchen board and walking away is the producer. The cook who later takes the slip and cooks is the consumer. The cook chopping onions, with no one else involved, is internal.

4Client and Server: The Synchronous Pair

The most common pair. One side asks, the other answers, and the caller waits.

In a synchronous conversation, the caller sends a request and waits for the reply before moving on. Web requests, database queries and remote procedure calls are all like this. Each side writes down its own side of the story in its own span.

sequenceDiagram
    participant O as Order Service
    participant P as Payment Service
    O->>O: Start CLIENT span for the call to payment
    O->>P: Request with trace identity in headers
    P->>P: Start SERVER span as child of the client span
    P->>P: Do the work
    P-->>O: Response
    P->>P: End SERVER span
    O->>O: End CLIENT span after the reply arrives
    
Figure 2: A client span and a server span describe the same call from two sides

How the two spans are linked

When the order service makes its call, it starts a client span and passes that span’s identity in the request headers. The payment service reads those headers and starts a server span whose parent is the client span. So in the trace tree, the server span appears directly beneath the client span. The tree now clearly shows a call crossing from one program to another.

Why the two durations differ

The client span and the server span measure different things, and the difference is useful.

SpanWhat Its Duration Includes
Client spanTime to prepare the request, send it over the network, wait in any queues, wait for the server’s work, receive the reply and read it
Server spanOnly the time the server spent handling the request, from when it started handling it to when it finished
The gapRoughly the network time and queuing time. A large gap with a short server span means the delay is outside the server’s own code
Everyday Analogy

Think of ordering a parcel. The time from when you click “buy” to when the parcel arrives is the client’s view. The time the warehouse spent packing it is the server’s view. If packing took one hour but delivery took three days, you know the delay was in the road, not in the warehouse. Comparing client and server durations gives the same insight.

Production Example: Slow, But Not the Server’s Fault

An engineer sees that calls to the inventory service are taking 800 milliseconds. The client spans show 800, but the matching server spans show only 60. The 740-millisecond gap points to the network, a load balancer or a connection pool, not the inventory code. The team looks at the connection settings and finds that new connections were being opened for every call. A small fix brings the time down to 80.

Where each kind is used

  • Server spans: incoming web requests, incoming remote procedure calls, handling of an incoming network request of any kind.
  • Client spans: outgoing web requests, remote procedure call clients, database queries, cache calls, calls to cloud services, and calls to any third-party service.
i
Tip

Database and cache calls are client spans even though the database server may not send back any trace information. The span still describes your program acting as the asking side. Tools use these client spans to show the database as a dependency of your service.

5Internal: Work Inside Your Own Program

The quiet default kind, and the one most often misused.

An internal span describes work that happens entirely inside one program and does not cross a boundary to another program. Calculating a discount, validating a form, rendering a page, and sorting a list are all internal. This is also the default kind, which means that if you create a span and do not choose a kind, it will be internal.

Why the default is both helpful and dangerous

It is helpful because most spans you add by hand really are internal work. It is dangerous because if you manually create a span for something that really crosses a boundary, such as a call to another service or a database, and forget to set the kind, it quietly becomes internal. Nothing breaks. No error appears. But your service map loses an edge, your database disappears from the dependency list, and your latency analysis loses the client and server comparison.

Correct Uses of Internal

  • A function that calculates a price
  • A step that validates input
  • A block that renders a response from a template
  • A grouping span that wraps several calls, such as “process order”
  • A call to another module in the same program

Wrong Uses of Internal

  • A call to a database, left as the default by mistake
  • A call to another service over the network
  • Sending a message to a queue
  • Handling an incoming web request
  • Processing a message from a queue
!
Watch Out

A very common situation is a service whose manually created spans are all internal, including the ones that call other services. The traces look fine, but service maps and dependency views are empty or incomplete. If a map looks strangely empty, check the span kinds first.

6Producer and Consumer: The Asynchronous Pair

When the sender does not wait for the receiver, the story changes.

In an asynchronous conversation, the sender drops off a message and moves on. The receiver picks it up later, perhaps seconds, minutes or hours afterwards. Message queues, event streams and background job systems all work this way.

sequenceDiagram
    participant O as Order Service
    participant Q as Message Queue
    participant E as Email Worker
    O->>O: Start PRODUCER span for send message
    O->>Q: Put message with trace identity in message headers
    O->>O: End PRODUCER span and continue with other work
    Note over Q,E: Time passes. Seconds, minutes or hours.
    E->>Q: Pick up message
    E->>E: Start CONSUMER span using the trace identity from the message
    E->>E: Process the message
    E->>E: End CONSUMER span
    
Figure 3: The producer span usually ends long before the consumer span begins

The key difference from client and server

With client and server spans, the client span contains the server span in time, because the client waits. With producer and consumer spans, the producer span typically ends before the consumer span even starts. They are connected by the trace identity carried inside the message headers, but they do not overlap in time. This is why the two pairs are separate kinds: they tell tools whether to expect nesting in time or a gap.

Everyday Analogy

Think of posting a letter. The producer is you at the post box: you drop the letter in and walk away. The consumer is the person who opens it days later. Your act of posting is finished long before their act of reading starts. A phone call, by contrast, has both people on the line at once, like client and server.

Producer

A producer span covers the act of creating and handing off the message. It is usually short, since it ends when the message has been accepted by the queue or broker.

Consumer

A consumer span covers the processing of the message. Its parent may be the producer span, giving one connected trace across time, or it may be a fresh span connected through a link back to the producer, which is useful when one consumer processes many messages at once.

Production Example: One Order, Two Stories in Time

When a customer places an order, the web request finishes in 200 milliseconds, and the customer sees a confirmation. Behind the scenes, a message is sent to a queue. The email worker handles it two minutes later. Because the producer and consumer spans carry the same trace identity, an engineer can open one trace and see the quick web request, then a gap of two minutes, then the email being sent. The gap itself tells a story: the queue was slow, not the worker.

7Messaging in More Detail: Send, Receive and Process

Real messaging systems have several steps, and each can get its own kind.

Messaging is richer than a simple “send” and “receive.” A consumer program may wait for messages, fetch a batch, process each one, and then confirm to the broker that it has finished. The naming and kind guidance in the OpenTelemetry conventions separates these steps, and understanding them avoids confusion.

StepWhat HappensUsual Kind
Create or sendA message is created and handed to the brokerProducer, though a send that waits for a reply from the broker is sometimes treated as a client call
Receive or pollThe consumer asks the broker for messages and waits for the answerClient, since the program is asking the broker and waiting for the reply
ProcessThe consumer handles the content of a messageConsumer
Settle or acknowledgeThe consumer tells the broker the message is doneClient, since it is a call to the broker

Batch processing and links

When a consumer receives many messages in one go, each message may belong to a different trace. It is not possible for one span to have many parents. The solution is a link: a connection from the processing span to each producer’s span, without being a parent and child relationship.

flowchart TD
    P1["Producer span of message 1 in trace A"] --> Q["Queue"]
    P2["Producer span of message 2 in trace B"] --> Q
    P3["Producer span of message 3 in trace C"] --> Q
    Q --> R["Receive span of kind client fetches a batch"]
    R --> C["Consumer span processes the batch"]
    C -.->|"link"| P1
    C -.->|"link"| P2
    C -.->|"link"| P3
    
Figure 4: A batch consumer span uses links to connect back to many producer spans
i
Tip

Different messaging libraries and versions of the conventions may differ on exactly which step receives which kind. Follow the conventions for your library, and be consistent across your company, so that tools can build correct pictures.

8Choosing the Right Kind: A Simple Decision Guide

Five questions are enough to pick correctly almost every time.

flowchart TD
    Q["What does this span cover?"] --> A{"Does the work cross to or from another program?"}
    A -->|No| I["Internal"]
    A -->|Yes| B{"Is the conversation synchronous, meaning the caller waits?"}
    B -->|Yes| C{"Am I the one asking or the one answering?"}
    C -->|Asking| CL["Client"]
    C -->|Answering| SV["Server"]
    B -->|No| D{"Am I sending the message or processing it?"}
    D -->|Sending| PR["Producer"]
    D -->|Processing| CO["Consumer"]
    
Figure 5: A decision guide for choosing a span kind

Common situations and the right kind

SituationKindReason
Handling an incoming web requestServerAnother program asked, and you are answering
Calling another service by web requestClientYou are asking and waiting
Running a database queryClientYou are asking the database and waiting
Reading from or writing to a cache serverClientA call to another program
Calling a cloud storage serviceClientA call to a remote service
Publishing a message to a queueProducerYou send and move on
Handling a message from a queueConsumerYou process what was sent earlier
Calculating a price in your codeInternalNo boundary is crossed
A grouping span, such as “process order”InternalIt wraps other work and does not talk to anyone
Calling another function in the same programInternalNo boundary is crossed, even if the code is a “service” in your design
Everyday Analogy

It is like deciding whether you made a phone call, received one, posted a letter, read a letter, or just thought about something. The word depends on whether another person was involved, and whether they had to be on the line at the same time. Span kind uses the same simple test.

9Why Span Kind Matters: What Tools Do With It

A tiny label unlocks several of the most useful views in an observability tool.

Service maps and dependency graphs

A service map is a picture with boxes for services and arrows for calls between them. Tools build it by looking for pairs: a client span in one service and a server span in another, joined by parent and child. Producer and consumer pairs give arrows for message flows. A client span with no matching server span, such as one calling a database or a third-party service, becomes an arrow to an outside dependency. Without correct kinds, these maps are empty or wrong.

flowchart LR
    G["Gateway"] -->|"client to server"| O["Order Service"]
    O -->|"client to server"| P["Payment Service"]
    O -->|"client only"| D["Database"]
    O -->|"producer to consumer"| E["Email Worker"]
    P -->|"client only"| X["External Card Provider"]
    
Figure 6: A service map drawn from span kinds and their pairings

Request, error and duration metrics

The standard health measures for a service are the rate of requests, the number of errors and the duration of requests. To compute them from spans, tools usually count server spans, because each server span represents one request that the service handled. A tool that generates metrics from spans, such as the span metrics feature in the Collector, records the span kind as a label, so that you can look at server-side numbers and client-side numbers separately.

Latency breakdown

As we saw earlier, comparing the duration of a client span with its server span reveals how much time was spent in the network or queues. Tools can show this automatically, but only when the kinds are right.

Dependencies and external calls

Client spans to databases, caches and third-party services let you answer, “Which of my dependencies is slowest?” and “How much time do we spend waiting on that vendor?” This is vital for finding the real cause of slowness and for holding vendors to their promises.

Sampling and filtering

The kind is given to the sampler at span creation, so rules can treat server spans differently from internal ones. In the Collector, filters and rules can select data by kind, for example to drop noisy internal spans while keeping all server and client spans.

Map

Service Maps

Pairs of client and server, and of producer and consumer, become arrows between boxes.

Metrics

Request and Error Rates

Server spans define what counts as one handled request.

Latency

Network Versus Work

The gap between client and server duration shows where time went.

Depend

Outside Dependencies

Client spans reveal databases, caches and vendors your service relies on.

Control

Sampling and Filtering

Rules can keep or drop spans by kind to control cost.

Reading

Easier Trace Reading

Viewers show icons and groupings by kind, which makes long traces much quicker to understand.

10Span Kind and Status: The 404 Puzzle

The same event can mean “failure” on one side and “normal” on the other. The kind explains why.

Imagine a web client asks a server for a page that does not exist. The server replies with the standard “not found” code, which is in the 400 range. Is that an error?

  • From the point of view of the client, yes. It asked for something and did not get it. The client span should be marked as an error.
  • From the point of view of the server, no. The server did exactly its job: it correctly told the caller that the page does not exist. Nothing is wrong with the server. The server span should be left without an error status.

The OpenTelemetry conventions for web requests say precisely this. For codes in the 400 range, the status is set to error on client spans and left unset on server spans. For codes in the 500 range, both sides treat it as an error, since the server itself failed.

Response Code RangeClient Span StatusServer Span StatusReason
Success, 200 rangeUnsetUnsetEverything went normally
Caller’s mistake, 400 rangeErrorUnsetThe client did not get what it wanted, but the server behaved correctly
Server failure, 500 rangeErrorErrorThe server itself failed to do its job
Everyday Analogy

Think of a library. If you ask for a book that does not exist, you leave disappointed, so for you the visit failed. But the librarian did a perfect job by checking the shelves and telling you honestly that the book is not there. If the library catalogue is broken and the librarian cannot find anything at all, that is a real failure of the library.

!
Watch Out

Because of this rule, a server’s error rate will not count 404 responses as errors. If you want to track “not found” rates, count them with a specific attribute, such as the status code, and not with the error status. Mixing these up leads to dashboards that surprise people.

11Span Kind and Attributes: Different Details for Different Roles

The kind also guides which details a span should carry.

The semantic conventions, which are the shared naming rules of OpenTelemetry, describe different expected attributes for different kinds. The idea is natural: the asking side and the answering side know different things.

KindDetails It Naturally KnowsExamples
ClientWhere it is calling, what it asked, how it wentThe address of the server it called, the method and the full web address, the database name and the type of operation
ServerWho called it, what route handled it, what it returnedThe matched route pattern, the caller’s address and user agent, the response code
ProducerWhere the message went and how it was sentThe destination queue or topic, the messaging system, the message size
ConsumerWhere the message came from and how it was handledThe source queue or topic, the consumer group, the operation type
InternalLittle that is standardisedYour own business details, such as the customer tier or the order type

The route detail on server spans

A good example is the matched route. On a server span, the route pattern, such as “orders by ID”, is recorded, not the raw address with the actual ID. Using the pattern keeps names and groupings tidy, so that millions of different order IDs do not each become a separate group in your tools. Client spans cannot know this pattern, since it is decided by the server.

i
Tip

If you set the kind correctly, your instrumentation libraries and tools can recognise which attributes to expect. If the kind is wrong, tools may look for the wrong details and show blanks, even though the data is there.

12Internal Working: How Span Kind Is Stored and Used

A look behind the scenes at a very small piece of data.

Set at creation, fixed forever

When code starts a span, it can pass the kind as an option. The SDK stores it in the span and never lets it change. There is no way to alter it later, because other parts of the system, such as the sampler, may already have used it.

1

Code Starts a Span With a Kind

The instrumentation passes the kind, such as client, as part of the span’s starting options. If nothing is passed, internal is used.

2

The Sampler Sees the Kind

The sampler receives the kind along with the name and attributes, so rules can use it when deciding whether to keep the span.

3

The Span Is Recorded

The kind is stored as a fixed field. Attributes, events and status may be added while the span is open.

4

The Span Ends and Is Exported

The span, including its kind, travels through processors and exporters. In the standard protocol, the kind is a number from a small list of values.

5

Tools Read the Kind

Backends and the Collector use it to draw maps, compute metrics, filter spans and choose icons.

Not carried between services

An important detail is that the kind is not part of the trace headers that pass from service to service. Those headers carry only the trace identity: the trace ID, the span ID, the flags and optional notes. So the kind of the server span is decided entirely by the receiving service, and the kind of the client span is decided entirely by the calling service. This is why both services must follow the same conventions: they cannot tell each other what role to take.

The protocol values

In the standard OpenTelemetry protocol, the kind is a small number: one for internal, two for server, three for client, four for producer and five for consumer. There is also a value zero meaning “unspecified,” which most tools treat the same as internal. You rarely see these numbers, since tools translate them into words, but it is helpful to know that “unspecified” and “internal” are usually treated alike.

Everyday Analogy

Think of two people writing about the same phone call in their own diaries. The caller writes, “I phoned the shop.” The shopkeeper writes, “A customer phoned me.” The telephone line carries only the voices, not the diary entries. Each person decides how to describe their side. Trace headers are the telephone line, and span kinds are the diary entries.

13Data Flow and Lifecycle: One Request With All Five Kinds

Let us follow one customer order through a realistic system and watch each kind appear.

flowchart TD
    A["SERVER: Gateway handles the incoming web request"] --> B["CLIENT: Gateway calls the order service"]
    B --> C["SERVER: Order service handles the call"]
    C --> D["INTERNAL: Calculate total price"]
    C --> E["CLIENT: Order service runs a database insert"]
    C --> F["CLIENT: Order service calls the payment service"]
    F --> G["SERVER: Payment service handles the call"]
    G --> H["CLIENT: Payment service calls the card provider"]
    C --> I["PRODUCER: Order service sends an email message"]
    I -.->|"later"| J["CONSUMER: Email worker processes the message"]
    J --> K["CLIENT: Email worker calls the mail provider"]
    
Figure 7: A single order produces spans of every kind
1

Entry: Server

The gateway starts a server span for the customer’s web request. This is the root of the trace and the first entry point of the system.

2

First Hop: Client then Server

The gateway makes a client span to call the order service, and the order service starts a server span for it.

3

Inside: Internal

The order service does some calculation, which is internal work with no boundary crossed.

4

Dependencies: Client Spans

The database insert and the payment call are client spans. The payment call also leads to a server span on the other side. The database may not.

5

Handoff: Producer

The order service sends an email message, ending its producer span quickly, and carries on.

6

Later: Consumer

The email worker picks up the message and starts a consumer span, which joins the same trace some time later.

Notice how reading only the kinds gives a skeleton of the whole system: where requests enter, where services call each other, which outside dependencies exist, and where work is handed off for later. That skeleton is what service maps and request metrics are built from.

14Tricky Situations and How to Handle Them

Real systems have shapes that the simple examples do not show.

Gateways, proxies and meshes

A gateway or a proxy both receives and sends requests. It therefore has a server span for the request it receives, and a client span for the request it forwards. If a service mesh adds a small helper program, called a sidecar, beside each service, the sidecar may add its own server and client spans on each hop. A trace through a mesh can look long because each hop is described by several spans. This is normal.

Retries

If a client retries a failed call three times, there are two sensible shapes. The simplest is one client span for each attempt. A tidier shape wraps all attempts in one internal span, such as “call payment with retries,” and puts each attempt as a client span beneath it. This keeps the story readable and lets you see how many attempts were needed.

flowchart TD
    W["INTERNAL: Call payment with retries"] --> A1["CLIENT: Attempt 1 fails with a timeout"]
    W --> A2["CLIENT: Attempt 2 fails with a server error"]
    W --> A3["CLIENT: Attempt 3 succeeds"]
    A3 --> S["SERVER: Payment service handles attempt 3"]
    
Figure 8: Each attempt is a client span under one internal wrapper span

Serverless functions

A serverless function is started by a trigger. If the trigger is a web request, the invocation is normally described like a server span. If the trigger is a message from a queue, it is like a consumer span. The conventions for this area can be detailed, so follow the guidance for your platform.

Background jobs and scheduled tasks

A job started by a timer, with no remote caller, has no incoming request to be a server for. Many teams use an internal span for the root of such a job and add client spans for the calls it makes. Some prefer to treat each job run as a server-like entry point. The most important rule is to be consistent across all your jobs, so that your tools treat them the same way.

Calls inside one program that look like services

If you split a program into modules that call each other in memory, these are internal calls, not client and server. Do not create client and server spans for in-process calls just because the design diagram calls them “services.” Kinds describe real boundaries, not drawing-board boundaries.

Streaming and long-lived connections

For long-lived connections such as streaming calls, the span for the whole call is still a client or server span, depending on side. Individual messages inside the stream are usually recorded as events on that span, rather than as separate spans, to avoid overwhelming the trace.

Calls to systems that cannot join the trace

When you call a database or a third-party service that does not propagate trace headers, your client span will have no server child. This is fine. It shows as an outside dependency. The absence of a server child is not a bug.

15Automatic and Manual Instrumentation: Who Sets the Kind?

Most of the time, you do not have to think about it, until you write your own spans.

Auto-instrumentation chooses for you

Agents and instrumentation libraries for web frameworks, database drivers, message clients and similar tools set the kind automatically and follow the conventions. A web framework library creates server spans. An HTTP client library creates client spans. A database library creates client spans. This is one big reason auto-instrumentation gives clean maps with no effort.

Manual spans need your attention

When you create spans by hand, the default is internal. You must pass the kind yourself whenever your span represents a boundary-crossing operation. Doing so takes one extra option at span creation, but forgetting it silently produces incomplete maps.

Manual Span You CreateKind You Should Set
Wrapping a call to a custom internal API over the networkClient
Wrapping your own code that receives calls from a custom protocolServer
Wrapping a custom message publishProducer
Wrapping a custom message handlerConsumer
Wrapping business logic with no outside callInternal, which is the default

Beware of duplicates

If an auto-instrumentation library already creates a client span for a web call, and you wrap the same call in your own client span, you will have two client spans stacked for one call. This confuses counts and makes traces look cluttered. If you want a wrapper that groups several calls, make it an internal span. Reserve client for the span that actually covers the single call.

!
Watch Out

A “double client” pattern is a common sign that a manual wrapper was added around an already-instrumented call. Look at the trace. If two client spans with the same destination are nested directly, remove one or change the outer one to internal.

16A Little History: Where Span Kind Came From

The idea is older than OpenTelemetry. Knowing its origin explains some details.

Early tracing systems already needed to tell the two sides of a call apart. Zipkin, one of the first open-source tracing systems, recorded special timestamps for events such as “client sent,” “server received,” “server sent” and “client received.” It also used the same span ID for both sides of a call, so that a single span held both views. OpenTracing, an earlier standard for tracing code, introduced a tag called span.kind with values such as client and server, and later added producer and consumer for messaging.

When OpenTelemetry was formed from OpenTracing and OpenCensus, it kept the idea but made it a first-class field with five fixed values, and chose to give the client side and the server side separate spans with separate IDs, linked by parent and child. This made traces cleaner, because every span has exactly one owner and one timing view, and made it easier to combine data from many different programs.

1

Zipkin Era

Client and server shared one span, marked with four special timestamps for the two sides.

2

OpenTracing

Introduced a tag for the kind of span, with values for client, server, producer and consumer.

3

OpenTelemetry

Made kind a built-in field with five values, including internal, and gave each side its own span.

Because tools such as Jaeger and Zipkin still exist, you may see kinds written in their own ways, such as a “span kind” tag or a field named “kind.” OpenTelemetry’s exporters translate between these styles, so the idea is the same everywhere.

17Advantages, Disadvantages and Trade-offs

An honest look at what span kind gives and what it asks of you.

Advantages

  • Tiny cost: one small field per span
  • Powerful results: service maps, request metrics and latency breakdowns depend on it
  • Standard: works across languages, vendors and tools
  • Automatic in most cases: auto-instrumentation sets it correctly
  • Clarifies traces: a quick way to see where work crosses boundaries
  • Guides attributes and status: makes conventions precise

Disadvantages

  • Silent mistakes: a wrong or missing kind causes no error, only poorer maps
  • Default can mislead: forgotten kinds become internal
  • Cannot be changed later: must be right at creation
  • Some edge cases are unclear: background jobs and certain messaging steps vary by convention
  • Both sides must agree: kinds are not shared across the network
  • Duplicate spans: manual wrappers can double-count calls

Key trade-offs

DecisionOption AOption B
Instrumentation styleAutomatic, so kinds are set correctly for youManual, for business detail, with extra care for kinds
Retry shapeFlat client spans, simpleInternal wrapper plus client attempts, tidier and more informative
Message batchesOne consumer span with links, compactOne consumer span per message, detailed but larger
Background jobsInternal root, simple and consistentServer-like entry point, better for request-style metrics
Internal spansMany, for detail but noisier tracesFew, for clarity and lower cost

18Security and Privacy Considerations

The kind itself is harmless, but the details attached to each kind need care.

Server Spans

Caller Details

Server spans often record caller addresses and user agents. IP addresses can be personal data under privacy laws. Decide whether to keep, shorten or remove them.

Client Spans

Full Addresses and Queries

Client spans may include full web addresses, which can hold tokens in the query string, and database statements, which can hold real values. Clean or replace them.

Messaging

Message Details

Producer and consumer spans may record queue names, keys and sometimes message sizes. Avoid recording message bodies or personal data in attributes.

Maps

Maps Reveal Your Layout

Service maps show how your systems connect, including vendors. Limit who can see them, as they are useful to an attacker.

Edge

Incoming Trace Headers

A server span at a public edge may continue a trace from an untrusted caller. Decide whether to trust the incoming identity or start a fresh trace and link back.

Pipeline

Scrub in the Collector

Use Collector processors to remove or mask sensitive attributes, and select by kind, such as cleaning every client span’s statement attribute.

!
Watch Out

Because kinds decide which details are expected, they also decide where sensitive data tends to appear. A good habit is to review one sample span of each kind in a test environment and check what it contains before going to production.

19Monitoring, Logging and Troubleshooting

Because kind mistakes are silent, you must look for their symptoms.

  • Empty or tiny service maps: usually means client and server spans are missing or all spans are internal.
  • No dependencies shown for a database: database calls are probably not marked as client spans.
  • Mismatched counts: if client spans calling a service far outnumber server spans in that service, trace headers may be lost on the way, or the server is not instrumented.
  • Server spans with no client parent: normal at your public edge, but suspicious deep inside your system, where it may signal broken header passing.
  • Share of internal spans: a very high share may hint that boundary-crossing spans are mislabelled.
  • Duplicate client spans: two nested client spans for one call suggest a manual wrapper over an auto-instrumented call.
  • Request metrics look wrong: if counts come from server spans, a missing server kind will leave metrics empty.
  • Collector service graph health: if you generate graphs in the Collector, watch for unmatched or expired client and server pairs.

Common symptoms and likely causes

SymptomLikely CauseWhat to Try
Service map has no arrowsSpans are internal or headers are not passedCheck kinds on call spans, and check propagation across hops
Database missing from dependenciesDatabase spans created manually without a kindSet client kind, or use an instrumentation library
Requests per second shows zeroCounting server spans, but none existEnsure entry spans use the server kind
Error rate is much lower than expected400-range responses are not errors on server spans by designTrack those responses with the status code attribute
Each call appears twice in the traceA manual client span wraps an auto-instrumented oneRemove the wrapper or change it to internal
Consumer spans appear as separate tracesContext was not put into message headers or was not readInject on send and extract on receive, or use links for batches
Client durations far exceed server durationsTime is lost in network, queues or connection setupLook at connection pooling, load balancers and queue waits

20Design Patterns and Anti-Patterns

Habits worth copying and traps worth avoiding.

Good design patterns

Pattern

Boundary Spans

Create client, server, producer and consumer spans exactly where work crosses a boundary, and use internal spans for what lies between.

Pattern

Wrap Retries in Internal

Put all attempts under one internal span, with a client span for each attempt.

Pattern

Let Libraries Do It

Prefer instrumentation libraries for protocols, so the kinds and attributes follow the conventions automatically.

Pattern

One Helper for Custom Calls

If you must create client or server spans by hand, put the logic in one shared helper, so every team does it the same way.

Pattern

Links for Batches

Use a consumer span with links for batch processing, instead of forcing a single parent.

Pattern

Map Check in Review

As part of release testing, look at the service map and verify that expected arrows appear.

Anti-patterns

ANTI-PATTERN 01Avoid
The Mistake

Leaving manual spans for outgoing calls at the default internal kind.

Why It Hurts

Service maps lose edges, dependencies disappear and latency comparisons between client and server become impossible.

Better Approach

Set the client kind for calls to other programs, and use an instrumentation library where one exists.

ANTI-PATTERN 02Avoid
The Mistake

Using client and server kinds for calls between modules inside the same program.

Why It Hurts

It invents false boundaries, adds noise to maps, and inflates request counts.

Better Approach

Use internal spans for in-process work. Reserve the other kinds for real boundaries.

ANTI-PATTERN 03Avoid
The Mistake

Using the producer kind for ordinary web calls, or the server kind for message handlers.

Why It Hurts

Tools draw the wrong kind of arrow, expect nested timing that does not exist, and compute wrong metrics.

Better Approach

Match the kind to the type of conversation: synchronous calls use client and server, asynchronous messages use producer and consumer.

ANTI-PATTERN 04Avoid
The Mistake

Wrapping an auto-instrumented call in your own client span.

Why It Hurts

You get duplicate spans for one call, which clutters traces and double-counts dependencies.

Better Approach

If you need a wrapper, make it internal. Let the library own the client span.

ANTI-PATTERN 05Avoid
The Mistake

Treating all 400-range responses as server errors.

Why It Hurts

Server error rates rise with harmless “not found” and “bad request” responses, hiding real failures and causing false alarms.

Better Approach

Follow the conventions: leave the status unset on server spans for these, and watch them through the status code attribute.

ANTI-PATTERN 06Avoid
The Mistake

Inconsistent kinds for the same type of operation across teams, such as some background jobs as internal and others as server.

Why It Hurts

Metrics and maps treat similar things differently, and dashboards become hard to compare.

Better Approach

Write a short company guide for the less obvious cases, and share helper code that applies it.

21Best Practices and Common Mistakes

A practical checklist for span kinds you can trust.

Best practices

  • Ask the boundary question. Does this span cross to or from another program? If not, it is internal.
  • Ask the waiting question. If the caller waits, use client and server. If not, use producer and consumer.
  • Prefer automatic instrumentation for protocols. It sets kinds and attributes for you, following the conventions.
  • Set the kind explicitly on manual boundary spans. Never rely on the default for calls out of your program.
  • Make every entry point a server span. Every place where outside requests enter your service should start a server span.
  • Pass context across every hop. Kinds only produce good maps when trace identity is carried through requests and messages.
  • Use internal wrappers for grouping and retries. Keep client for the single call.
  • Follow the conventions for status. Know the difference between client and server handling of 400-range responses.
  • Check the map. After a release, look at the service map and make sure the expected arrows are present.
  • Write a short internal guide. Document decisions for background jobs, batches and serverless, and apply them consistently.

Common mistakes beginners make

  • Thinking span kind describes the technology, such as “database span,” when it describes the role in a conversation.
  • Leaving manual spans at the default and wondering why maps are empty.
  • Believing that the kind is sent in trace headers between services.
  • Using client and server for in-process calls.
  • Treating producer and consumer like client and server, and expecting them to nest in time.
  • Wrapping auto-instrumented calls in manual client spans.
  • Counting 404 responses as server errors.
  • Forgetting that the kind cannot be changed after the span is created.

22Real-World and Industry Examples

How span kind shows up in tools and platforms you may already know.

Jaeger

Jaeger, which began at Uber and is now a CNCF project, shows the kind of each span as a tag, and builds its system architecture and dependency views from the relationships between client and server spans. Teams use these views to find the busiest and slowest links between services.

Zipkin

Zipkin, one of the earliest tracing systems, built its dependency graph from client and server spans, and uses producer and consumer kinds for messaging. OpenTelemetry’s exporters translate span kinds into Zipkin’s own format, so teams moving from Zipkin find the idea familiar.

OpenTelemetry Collector Connectors

The Collector offers connectors that turn spans into metrics and into service graphs. The service graph connector pairs client and server spans, and producer and consumer spans, to produce metrics about calls between services. The span metrics connector records the kind as a dimension, so teams can chart server-side and client-side latency separately.

Cloud Monitoring Products

Cloud monitoring services from the large providers draw application maps from traces, and use kinds to decide what is a service, what is a dependency and what is an internal step. For example, products that group data by role often rely on server spans to define a service’s entry points.

Messaging Platforms

Teams using event streaming and queue systems rely on producer and consumer spans, with context carried in message headers, so that traces cross the time gap between sending and processing. Many messaging instrumentation libraries do this automatically, and teams add links when processing batches.

A typical journey for a growing company

1

Turn On Auto-Instrumentation

Get correct kinds on web, database and messaging spans with no code, and look at the first service map.

2

Fix the Gaps

Find missing arrows, often from proxies that drop trace headers or from services without instrumentation.

3

Add Manual Spans Carefully

Wrap business logic as internal spans, and set the proper kind for any custom boundary-crossing calls.

4

Build Metrics From Kinds

Create request, error and duration views from server spans, and dependency views from client spans.

5

Standardise and Review

Publish a short guide for edge cases, review maps after releases, and clean sensitive attributes by kind in the Collector.

23Frequently Asked Questions

Quick answers to questions beginners ask most.

Q1What is span kind in one sentence?
It is a field on every span that describes the span’s role in a conversation between programs: internal, server, client, producer or consumer.
Q2What are the five span kinds?
Internal for work inside one program, server for handling an incoming request, client for making an outgoing request, producer for sending an asynchronous message, and consumer for processing one.
Q3What is the default span kind?
Internal. If you create a span and do not choose a kind, it becomes internal.
Q4Can I change a span’s kind after creating it?
No. The kind is set when the span is created and cannot be changed afterwards.
Q5Is the span kind sent between services?
No. Trace headers carry only the trace identity. Each service decides the kind of its own spans.
Q6What is the difference between client and producer?
A client span covers a request where the caller waits for the answer. A producer span covers sending a message and moving on, with processing happening later by a consumer.
Q7Is a database call a client span?
Yes. Your program is the asking side of a conversation with the database, so the span is a client span, even though the database does not send back its own span.
Q8Which span kind should be the root of a trace?
Often server, at the entry point of your system, where an outside request arrives. For background jobs with no incoming request, the root may be internal. Whatever you choose, apply it consistently.
Q9Why does a 404 count as an error on the client span but not on the server span?
Because the client did not get what it asked for, while the server correctly told it that the page does not exist. The server did its job, so its status is left unset. Server failures in the 500 range are errors on both sides.
Q10Why does my service map show no connections?
Most likely your spans are internal, or trace headers are lost between services. Check that outgoing calls use the client kind and incoming requests use the server kind, and that context is passed on every hop.
Q11How are requests per second calculated from spans?
Usually by counting server spans, since each one represents a request your service handled. Metrics tools often record the span kind as a label, so you can separate server-side from client-side numbers.
Q12What should I use for a batch of messages processed together?
A consumer span for the processing, with links back to the producer spans of the messages in the batch, since one span cannot have many parents.
Q13Should I use internal spans for retries?
A good pattern is one internal span that wraps all the attempts, with a client span for each attempt beneath it. This keeps the trace clear and shows how many tries were needed.
Q14Does the span kind affect sampling?
It can. The kind is given to the sampler at span creation, so custom rules can treat server spans differently from internal ones. In the Collector, filters can also select spans by kind.

24Summary and Key Takeaways

If you remember only a few things, remember these.

Key Takeaways

  • Span kind is the role label of a span. It says whether the span is internal work, a server handling a request, a client making one, a producer sending a message or a consumer processing one.
  • Two natural pairs. Client pairs with server for synchronous calls, where the caller waits and the spans nest in time. Producer pairs with consumer for asynchronous messages, where the spans are connected by identity but separated in time.
  • Choose by asking two questions. Does the work cross to or from another program? If yes, does the caller wait? The answers lead to the right kind almost every time.
  • The default is internal, and it can mislead. Manual spans for outgoing calls must have the kind set explicitly, or service maps and dependency views quietly lose information.
  • Kinds power the big views. Service maps, request and error metrics, dependency lists and network-versus-work latency comparisons all rely on correct kinds.
  • Kind affects status and attributes. A 400-range response is an error on the client span but not on the server span, and each kind carries its own expected details.
  • It is fixed, local and silent. The kind is set at creation, never changes, is not sent between services, and mistakes cause no errors, so check your service map after each release.