> ## Documentation Index
> Fetch the complete documentation index at: https://docs.vexlo.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Logs, spans, and execution graphs

> A row-by-row comparison of what each one records, and why logging every function's values is not the same thing.

The three record different things, so each one answers a different question. Logs hold the
values someone formatted into a string. Spans hold the boundaries someone marked out, with
metadata attached. An execution graph holds the call tree that ran, with the inputs and outputs
each call saw.

| | Logs | Spans (distributed tracing) | Execution graph |
| - | - | - | - |
| What is recorded | the lines someone wrote | the boundaries instrumentation was added at | every marked function that ran |
| Structure | none, you rebuild the story | a tree of chosen boundaries | the call tree as it ran |
| Content | values formatted into strings | metadata someone attached | the inputs and outputs each call saw |
| Retries | repeated lines | sibling spans with nothing relating them | attempts of one step |
| Reading less | all or nothing per level | sampling | ask for shape, fetch values where you are looking |
| Debugging | search, then correlate | correlate timestamps, then search logs | walk the call tree directly |

With spans you ask what was slow. With an execution graph you ask why this list came back
empty, and the answer sits on a step's recorded output.

Two rows are worth reading twice. A retry is several sibling spans with nothing in the data
relating them, while an execution graph keeps the attempts under the one step they
belong to. It labels the group `declared` where the step was marked `retries=True`, and
`observed_retry` where the SDK inferred the grouping itself. And sampling here applies to
values rather than to requests: an attempt that raised keeps its input and error values at any
depth, while one roll per trace decides whether an `ok` trace's values are kept at all.
Sampling never touches the record of what ran.

## Why not log every function's inputs and outputs

Because you would have all of the values and none of the structure. Nothing in a log stream
says which call caused which, so you rebuild the tree from request ids, timestamps, and thread
ids, and that reconstruction is the work that costs incident time. The values themselves arrive
flattened into strings by whatever formatter wrote them, so a list of five objects and a list
of none are two strings you have to parse back.

You would also pay the full price on every request, because a log line is written whether
anyone reads it or not, and there is no way to ask for less of one. An execution graph is the
same information arrived at already assembled, and separable: what ran is one thing to read and
the captured bytes are another, each fetched on its own.

## What reading less actually costs

Those costs are measured rather than asserted. The MCP server's tool results were counted with
`@anthropic-ai/tokenizer` 0.0.4, Anthropic's published BPE, over the text an AI agent receives, on
a server seeded with the execution graph spec's 17-step conformance trace, cloned to 60 traces.
`vexlo_search_traces` over its default page of 10 traces measures 1,473 tokens. The same search at
the API's own default of 50 measures 7,127, which is why the tool asks for 10. That tokenizer is
the Claude 2 generation's and no current model's is published, so the error of these counts
against a current model is unmeasured in both size and direction. The full table is on
[token costs](https://docs.vexlo.ai/mcp/token-costs.md); the per-rung costs of one trace are on
[the execution graph](https://docs.vexlo.ai/concepts/execution-graph.md).

## Where the vocabulary comes from

Where OpenTelemetry already has a word for something, Vexlo uses that word: the service name,
`trace_id`, and `status` with `ok` and `error`, with tags carrying what span attributes carry.
Where the concept has no equivalent there, it gets its own name on purpose. A step is a
[marked function](https://docs.vexlo.ai/sdk/mark-functions.md) rather than a span, and an attempt, one execution of a
step, is Vexlo's own addition. The borrowing is vocabulary only, so the data stays legible to
anyone who knows OpenTelemetry's terms without being mistaken for its data.

[The execution graph](https://docs.vexlo.ai/concepts/execution-graph.md) defines the primitive those words name, and reads
one real trace down to its captured values.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.