Zenaique

When should you use a span attribute versus a span event?

MCQ·Medium·4.0 · 0·~1 min·Asked atBrowserbaseRazorpayTogether Ai
Attempt it
TL;DR

Attribute = property of the span (indexed, filterable). Event = time-ordered occurrence inside the span (model name attribute; streamed-chunk event).

Memory aid
Sign in to see the mnemonic that makes this stick.
Easy to grasp

Think of a single page in a diary about one day. The date, the weather, and the place at the top of the page are attributes. They describe the whole day, and you can flip through your diary by date or weather to find similar days. The bullet points down the page that say what happened at 9am and 11am and 3pm are events. They are time-ordered things that happened during the day. You would not put 'lunch at noon' in the page header, and you would not write the date next to every event line. The two slots have different jobs. Span attributes and span events split the same way.

Concept explanation~2 min read

Everything you need to truly understand this topic: intuition, mechanics, step by step explanation, code, formulas, and worked example. Click to expand.

OpenTelemetry gives spans two ways to attach data: attributes and events. They are structurally similar (both are roughly key-value payloads on a span). But they exist for different purposes, and observability backends treat them differently. Confusing them is one of the most common instrumentation bugs in LLM applications. There, multi-message chats and streaming outputs make the choice feel ambiguous when it actually is not.

Mental model: attribute is an adjective describing the span. Event is a verb describing something that happened during the span. Backends index adjectives for fast filtering. They timestamp verbs for the span timeline view.

The definitions and the cardinality argument

Span attribute

It is a key-value property of the span as a whole. Each attribute has a key (gen_ai.request.model) and a value ('gpt-5.5'). The full set of attributes describes the span's static metadata: what it was, who triggered it, what it returned.

Attributes are indexed by the observability backend. That index is what makes filtering ('show me all chat spans where model=gpt-5.5') and grouping ('aggregate cost by user.tier') fast.

Span event

It is a time-ordered record nested inside the span timeline. Each event has its own timestamp, a name (gen_ai.client.inference.operation.details, retry.attempt), and an optional attribute payload. A span can have zero or many events, and their order is preserved.

Events are searchable but not pre-aggregated the same way attributes are. The span timeline view renders them as inline annotations.

The cardinality argument

Attribute keys live in the backend's index. The index has cost (memory, storage, query latency). Some backends cap or price by attribute-key cardinality.

  • Low-cardinality attribute keys (tens of distinct values across the system): cheap, fine.
  • High-cardinality attribute keys (millions of distinct values): expensive, often capped.
  • Unbounded attribute keys (new key per span, like message.1, message.2, ...): they break the index.

The rule that emerges: attributes should be span-wide and low to medium cardinality. Anything per-occurrence or unboundedly variable goes in events.

The indexability argument

In the other direction, events are not indexed for filtering the same way attributes are. Hiding model_name inside an event means you cannot filter dashboards by model. Putting everything in events is convenient ('it is more flexible'), but that convenience has a cost. The data is no longer queryable in the fast path.

How OTel GenAI semantic conventions apply the rule
Failure modes when the split is wrong
Practical patterns for tricky cases
Sign in to unlock the full deep dive.

Situations where this technique stops working.

Sign in to see when this approach fails.

2–4 min · Everything important, quickly.

Sign in to see the quick scan of the deep dive.

Real products, models, and research that use this idea.

  • OpenTelemetry GenAI semantic conventions use attributes for per-span metadata and record chat history as opt-in structured attributes (gen_ai.input.messages, gen_ai.output.messages).
  • Langfuse, Arize Phoenix, and Datadog LLM Observability all index on attributes and render events on the span timeline.
Sign in to see more production examples.

What an interviewer would ask next. Try answering before peeking at the approach.

QHow would you record retry attempts on an LLM call?
A

Each retry is a time-ordered occurrence inside the parent span. Use events with names like retry.attempt and payload {attempt_number, error_code, backoff_ms}. Aggregate retry count as a custom derived attribute such as app.retry_count for filtering.

2 more follow-ups an interviewer would ask next. Sign in to reveal them.

Red flags & common mistakes

The phrases that signal junior thinking. Click to expand.

Most common mistake

Recording per-message data as attributes. Cardinality explodes and the index breaks. Messages belong in events.

Sign in to see all red flags and common mistakes.

60 second bullets to scan on the way to the call.

  • Definition of span attribute vs span event

  • Cardinality and indexing as the reason for the split

Sign in to unlock the revision sheet.

Primary sources. Browse if you want the original framing.

Similar questions

Same topic, related formats. Practice these next.

4 curated
Next question
Explain why every Langfuse trace should carry a userId from day one
Flashcard·Easy