.md →
TraceView API#
TraceView shows an OpenTelemetry trace as a waterfall. Each span is a row,
indented under its parent, with its state (pending, running, ok, error), a bar on
a shared timeline, and its duration. Click a row to see its attributes and
events, fold a subtree with −/+, and press Replay (or drag the scrubber)
to watch the trace unfold. Span links — "caused by, but not part of", like a
queue consumer pointing at the producer — are drawn as dashed curves.
There are three ways to get spans in:
- Live, in-process —
view.span_processor()is an OpenTelemetrySpanProcessor. Add it to aTracerProvideronce; from then on every span made by that provider's tracers streams into the widget as it starts and ends, and running spans grow live. There is no widget-level context manager for this because OpenTelemetry already has one:with tracer.start_as_current_span(...)is the block, and the widget sees it. Needsopentelemetry-sdk(pip install wigglystuff[otel]). Anything built on OpenTelemetry, such as Logfire or Pydantic AI, can use the same processor. - From data —
TraceView.from_otlp(...)reads OTLP/JSON (a dict, a list of dicts, a file, or the JSON lines the Collector's file exporter writes), andTraceView.from_spans(...)takes SDK spans such asInMemorySpanExporter.get_finished_spans(). Loading OTLP needs no OpenTelemetry install. - From W&B Weave —
with view.capture_weave():shows the calls of any@weave.opfunction called inside the block, live, afterweave.init(...)(pip install wigglystuff[weave]). Weave has no processor hook, so this context manager is the Weave stand-in foradd_span_processor: it hooks the Weave client for the duration of the block and unhooks it afterwards. The spans themselves still come from your@weave.opcalls, just as they come fromstart_as_current_spanwith OpenTelemetry.TraceView.from_weave(...)loads stored calls fromclient.get_calls(...)(or the service API's call dicts).
from opentelemetry.sdk.trace import TracerProvider
from wigglystuff import TraceView
view = TraceView(title="agent run")
provider = TracerProvider()
provider.add_span_processor(view.span_processor())
tracer = provider.get_tracer("notebook")
with tracer.start_as_current_span("agent.run"): # this block is the trace
...
With Weave, call your op as usual inside the block:
import weave
from wigglystuff import TraceView
weave.init("my-project")
view = TraceView(title="support bot")
view # display it, then in another cell:
with view.capture_weave():
answer_ticket("Where is my order?")
A span with numeric progress.done and progress.total attributes gets a
progress bar. Updates are sent from the thread that starts or ends a span, so
spans finishing on background threads show up with the next update from the
notebook's own thread. asyncio needs nothing extra: spans started inside tasks
keep their parent through contextvars, so concurrent work shows as
overlapping bars under one parent.
Picking an older trace from the menu pins it on screen. While Follow latest
is on (the default, show_latest=True) the next trace that starts takes the
view back to following live. Click the button, or set show_latest=False, to
keep the picked trace pinned instead.
See also: AsyncFlow for a timeline of asyncio tasks without any instrumentation, and LiveEdit for a pass-by-pass trace of one Python function.
Bases: AnyWidget
Waterfall viewer for OpenTelemetry traces, live or loaded from a file.
Each span is one row: a tree gutter shows parent/child structure, followed by the span's state, an optional progress bar, a timeline bar, and its duration. Click a row for its attributes and events; drag the replay scrubber (or press Replay) to watch the trace unfold.
Live, in-process (requires opentelemetry-sdk). Attach the processor
once; after that every with tracer.start_as_current_span(...) block
made through that provider shows up, so no widget-level context manager
is needed::
view = TraceView(title="agent run")
provider.add_span_processor(view.span_processor())
view
From existing data (no OpenTelemetry install needed)::
TraceView.from_otlp("traces.json") # OTLP/JSON or JSON lines
TraceView.from_spans(exporter.get_finished_spans())
TraceView.from_weave(client.get_calls(...)) # W&B Weave calls
W&B Weave @weave.op calls, live (after weave.init(...)). Weave has
no processor hook, so this context manager plays the role of
add_span_processor for the duration of the block::
with view.capture_weave():
answer_ticket("Where is my order?")
A span with numeric progress.done and progress.total attributes gets
a progress bar.
Picking an older trace from the menu pins it. With show_latest=True (the
default, and the Follow latest button in the header) the next new trace
takes the view back to following live; turning it on jumps there right away.
Source code in wigglystuff/trace_view.py
selected_span
property
#
The span dict of the clicked row, or None.
add_spans #
Add (or update, by span_id) OpenTelemetry SDK spans or span dicts.
Source code in wigglystuff/trace_view.py
capture_weave #
Show the W&B Weave @weave.op calls made inside this with block, live.
Needs weave.init(...) first; the calls still go to W&B as usual.
Weave has no public call hook, so while the block runs this wraps the
client's create_call/finish_call.
Source code in wigglystuff/trace_view.py
clear #
Drop all spans and reset the selection, replay cursor, and collapsed rows.
Source code in wigglystuff/trace_view.py
from_otlp
classmethod
#
Load OTLP/JSON: a file path (a JSON document or JSON lines), a dict, or a list of dicts.
Source code in wigglystuff/trace_view.py
from_spans
classmethod
#
Build from OpenTelemetry SDK spans, e.g. InMemorySpanExporter.get_finished_spans().
from_weave
classmethod
#
Build from W&B Weave calls, e.g. client.get_calls(filter={"trace_ids": [...]}).
Accepts the SDK's call objects or plain dicts (service API JSON); no
weave install needed for the dict form.
Source code in wigglystuff/trace_view.py
span_processor #
An OpenTelemetry SpanProcessor that streams spans into this widget.
Add it to a TracerProvider with provider.add_span_processor(...).
Updates are sent from the thread that starts or ends a span; spans that
finish on background threads appear with the next update from the
notebook's own thread.
Source code in wigglystuff/trace_view.py
Synced traitlets#
| Traitlet | Type | Notes |
|---|---|---|
spans |
list[dict] |
Normalized spans: span_id, parent_id, trace_id, name, service, kind, start_ms, end_ms (epoch ms, None while running), status, status_message, attributes, events, links. |
trace_id |
str |
Trace on screen; "" follows the most recently started one. |
show_latest |
bool |
Follow latest button: a new trace clears a pinned trace_id; turning it on jumps to the latest trace. |
selected_span_id |
str \| None |
Set by clicking a row; read the span with selected_span. |
cursor_ms |
float \| None |
Replay position in ms since the trace started; None shows everything. |
collapsed |
list[str] |
Span ids whose subtrees are folded. |
title |
str |
Header title. |
height |
int |
Max height of the rows in pixels; 0 grows to fit. |
now_ms |
float |
Kernel clock at the last sync, so running bars grow correctly. |