> For the complete documentation index, see [llms.txt](https://triton.pharos.ai/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://triton.pharos.ai/end-to-end/spans-and-tracing.md).

# Spans & Distributed Tracing

How Triton builds a parent-child trace out of spans — one level deeper than the transaction id, recording which work happened inside which.

The [transaction id](/end-to-end/transactions-and-cache.md) answers one question: *which logs belong to the same operation?* **Spans** answer the next one: *how are they nested?* A span marks a single unit of work, and a parent span points at the work that contains it. Chain them together and a flat list of logs becomes a tree — the button click, the backend call it triggered, the step that call ran inside.

Every Triton log carries two span fields:

| Field               | Builder method          | Meaning                               |
| ------------------- | ----------------------- | ------------------------------------- |
| `Span_Id__c`        | `.spanId(String)`       | Identifies this unit of work.         |
| `Parent_Span_Id__c` | `.parentSpanId(String)` | Points at the enclosing unit of work. |

Set both across a set of logs and you've described a hierarchy.

## Spans in LWC — automatic hierarchy

The LWC logger builds the span tree for you. It keeps a **span context stack** whose root is the transaction id, and it stamps every log with a freshly generated `spanId` and a `parentSpanId` taken from the top of that stack.

You don't call `spanId()` by hand for ordinary logs — `makeBuilder()` (and therefore `debug()`, `info()`, `error()`, etc.) already does it:

{% code lineNumbers="true" %}

```javascript
// Each of these logs gets a unique spanId and, as its parent,
// the current top of the span stack (the transaction root by default).
this.triton.log(
    this.triton.info(TYPE.FRONTEND, AREA.ACCOUNTS)
        .summary('Loaded account panel')
);
```

{% endcode %}

### Logical steps

For multi-step UIs (wizards, multi-screen flows), `enterStep()` resets the stack back to the transaction root and pushes a new span for the step. Everything logged afterward hangs off that step:

{% code lineNumbers="true" %}

```javascript
const stepSpan = this.triton.enterStep();   // start "Step 2"
this.triton.log(
    this.triton.info(TYPE.FRONTEND, AREA.ACCOUNTS)
        .summary('Entered shipping details')
);
// → this log's parentSpanId is the step span
```

{% endcode %}

### Performance calls nest automatically

The profiling helpers — `timeBackendCall(...)`, `timeUserInteraction(...)`, and performance marks — push their own span while they run and pop it when they finish. Their start/complete/failure logs share that span, and any logs you emit *during* the call become children of it. That's how a backend call and everything it did end up neatly grouped. (See [Profiling](/beyond-apex/profiling.md) for the full API.)

### Surviving navigation

If a logical operation spans multiple page loads, call `enableSpanPersistence()` to back the span stack with `sessionStorage`, so the hierarchy survives navigation instead of resetting.

## Spans in Apex

Apex has the same two defaults, one for each situation.

**Outside a mark**, the shape is the simple one: if you don't set a span id, the builder fills `Span_Id__c` with the current request id at build time, and `Parent_Span_Id__c` stays null. Every log in one Apex request shares a span, without any effort and without any change to what you have today.

### Marks build the tree

**Inside a mark**, Apex behaves like LWC. [Performance marks](/common-apex-usage-patters/performance-marks.md) keep a span stack, and while a mark is open every log you emit gets its own span id and hangs off the innermost mark:

{% code lineNumbers="true" %}

```apex
Triton.startMark();                                        // OrderService.submit
try {
    Triton.info(Triton.t.summary('Validating orders'));    // ← child of the mark
    Triton.startMark('pricing');                           // ← child mark
    try {
        applyPricing(orders);
    } finally {
        Triton.endMark();
    }
} finally {
    Triton.endMark();
}
```

{% endcode %}

The rules are short:

* The **outermost** mark parents to the transaction span, so a top-level mark anchors to the same tree everything else in the transaction hangs from. Depth is a fact the framework reads, not something you declare — which is why there's no boundary-specific method.
* A **nested** mark parents to the mark enclosing it.
* An **ordinary log** inside a mark becomes a leaf under the innermost mark.
* An **id you set yourself always wins.** `.spanId(...)` / `.parentSpanId(...)` are never overwritten — that's what keeps LWC → Apex traces intact, because the ids forwarded from the browser arrive already set.
* A mark **filtered out by log levels** is transparent: its children re-parent to the nearest surviving ancestor rather than pointing at a span that was never published.

{% hint style="warning" %}
**`Span_Id__c` inside a mark is no longer the request id.** It's a fresh UUID. Any query or dashboard that used `Span_Id__c == requestId` to mean "this log belongs to request X" should read **`pharos__Request_Id_External__c`** instead.
{% endhint %}

### Setting ids by hand

When you want structure without marks — say, a parent log for an operation and child logs for its sub-steps — set the ids yourself:

{% code lineNumbers="true" %}

```apex
String parent = Triton.TRANSACTION_ID;   // or any id you choose as the root

Triton.info(Triton.t
    .type(TritonTypes.Type.Backend)
    .area(TritonTypes.Area.OpportunityManagement)
    .summary('Sync started')
    .spanId('sync-root')
    .parentSpanId(parent));

Triton.info(Triton.t
    .type(TritonTypes.Type.Backend)
    .area(TritonTypes.Area.OpportunityManagement)
    .summary('Pushed line items')
    .spanId('sync-line-items')
    .parentSpanId('sync-root'));   // ← nested under the sync root
```

{% endcode %}

## `traceable` and `tracePoint`

Beyond parent/child structure, Triton can mark a log as an explicit **trace point** for Pharos. Two builder methods control this, and they work as a pair:

* **`.traceable(Boolean)`** — opts this builder into tracing. Passing `true` also flags the log for issue creation.
* **`.tracePoint()`** — records a trace point on the underlying Pharos log builder — but **only if `traceable` is `true`**. If tracing wasn't enabled, it's a no-op.

{% code lineNumbers="true" %}

```apex
Triton.log(Triton.t
    .type(TritonTypes.Type.Backend)
    .area(TritonTypes.Area.RestAPI)
    .summary('Callout dispatched')
    .traceable(true)     // enable tracing for this log
    .tracePoint());      // now this actually records a trace point
```

{% endcode %}

{% hint style="info" %}
Triton calls `tracePoint()` for you as part of preparing every buffered/immediate log, so you generally only need to set `.traceable(true)` on the logs you want traced. `tracePoint()` stays inert on everything else, which keeps tracing strictly opt-in.
{% endhint %}

## How this maps to OpenTelemetry

This mapping is what people mean when they call a Triton 2.0 org **"OpenTelemetry-ready."** If you think in [OpenTelemetry](https://opentelemetry.io/docs/concepts/signals/traces/) terms, Triton's model lines up cleanly with the tracing vocabulary — the same trace/span/parent-span shape an observability stack expects. The mapping is **conceptual** — it helps you reason about the data, and it's a foundation to build an OTel integration on — not a wire-format integration that ships today:

| OpenTelemetry concept                    | Triton equivalent                                                                                            |
| ---------------------------------------- | ------------------------------------------------------------------------------------------------------------ |
| **Trace** (one end-to-end operation)     | The **transaction id** (`Triton.TRANSACTION_ID` / `this.triton.transactionId`) that correlates all the logs. |
| **Span** (a unit of work within a trace) | `Span_Id__c`, set via `.spanId(...)`.                                                                        |
| **Parent span** (the enclosing work)     | `Parent_Span_Id__c`, set via `.parentSpanId(...)`.                                                           |
| **Span nesting / trace tree**            | The span stacks — the LWC span-context stack and the Apex mark stack — plus any ids you set by hand.         |
| **Marking a span of interest**           | `.traceable(true)` + `.tracePoint()`.                                                                        |

{% hint style="warning" %}
**Readiness, not a shipped exporter.** Triton is **not** an OpenTelemetry SDK and doesn't export OTLP — there's no OTEL exporter in the package. These spans live on Pharos `Log__c` records and are visualized in Pharos. The table above is a mental model for anyone fluent in OTel — treat "trace / span / parent span" as an accurate alignment with Triton's transaction id and span fields, and as a foundation for a future OTel integration, not a drop-in pipeline that ships today.
{% endhint %}

### Getting this shape everywhere

The mapping only pays off if your logs actually carry span structure across Apex, LWC, and Flow. That's exactly what the [Instrumentation Skills](/instrumentation-skills.md) roll out for you: the Apex skill threads one transaction id through async hops, the LWC skill's `timeBackendCall` carries span context across a backend call, and the Flow skill stitches the Flow leg into the same transaction — so the trace/span/parent-span shape is applied consistently instead of by hand.

## Do / Don't

{% hint style="success" %}
**Do**

* **Lean on the LWC defaults** — the span stack builds a sensible hierarchy without any manual `spanId` calls.
* **Use** [**performance marks**](/common-apex-usage-patters/performance-marks.md) **in Apex** — `startMark()` / `endMark()` build the same hierarchy there, and every log inside the mark joins it automatically.
* **Use `enterStep()`** to give multi-step UIs clean, per-step subtrees.
* **Set explicit `spanId` / `parentSpanId` in Apex** only when you want a structure marks don't give you — a set value is always respected.
* **Set `.traceable(true)`** on the specific logs you want as trace points.
  {% endhint %}

{% hint style="danger" %}
**Don't**

* **Don't confuse spans with the transaction id.** The transaction id groups the whole operation; spans structure it. You need both for a useful trace.
* **Don't expect `.tracePoint()` to do anything without `.traceable(true)`** — it's intentionally inert otherwise.
* **Don't reuse the same `spanId` for unrelated units of work** — duplicate span ids blur the hierarchy.
  {% endhint %}

## Related reading

* [Transactions & Platform Cache](/end-to-end/transactions-and-cache.md) — the correlation id that spans hang off of.
* [End-to-End Logging](/end-to-end.md) — spans in the context of a full LWC → Apex → Flow trace.
* [Profiling](/beyond-apex/profiling.md) — performance helpers that create spans automatically.
* [Instrumentation Skills](/instrumentation-skills.md) — slash commands that roll this tracing shape out across Apex, LWC, and Flow.
