Tracepanel

Tracing your code

In Python, add your own steps, traces, attributes and events with span(), @traced, trace(), set_attr() and event().

The call tree shows what your framework and libraries do. To see the steps of your own code, wrap them in a span. To trace code that runs outside a request or a task, such as a script or a scheduled job, start a trace.

import tracepanel


def checkout(cart):
    with tracepanel.span("price the cart", items=len(cart)):
        total = price(cart)
    with tracepanel.span("charge"):
        charge(cart.owner, total)

Tracepanel attaches the SQL, logs and errors inside a span to that span.

Safe to leave in

Outside a trace, and outside development, these calls do nothing. You can keep them in the code you deploy.

Attributes

Keyword arguments become attributes. Tracepanel stores a name without a dot under app., so items becomes app.items. To choose the full name, such as order.id, pass a dotted name in attrs:

import tracepanel

order_id = 42
with tracepanel.span("load the order", attrs={"order.id": order_id}):
    ...

Tracepanel keeps strings, numbers, booleans, None and lists of them as they are. It stores any other value as its repr().

span()

tracepanel.span(name: str, *, attrs: Mapping[str, object] | None = None, **values: object)

span() is a context manager. It times the block as a step of the current trace. It yields a handle with set(key, value), which adds an attribute while the block runs. The handle's recording property is False when there is no trace to record into.

import tracepanel

with tracepanel.span("index.load", source="db") as span:
    rows = list(range(120))
    span.set("rows", len(rows))

@traced

tracepanel.traced(name: str | None = None)

@traced runs the whole function in a span. By default it names the span after the function's module and name, such as shop.pricing.price. Pass a name to choose another one. It works on regular functions and on coroutines.

import tracepanel


@tracepanel.traced
def price(cart): ...


@tracepanel.traced("sync prices")
async def sync_prices(): ...

trace()

tracepanel.trace(
    name: str,
    *,
    kind: str = "command",
    attrs: Mapping[str, object] | None = None,
    **values: object,
)

trace() starts a new trace in code that has none, such as a script, a scheduled job or a consumer of your own. When the block ends, the agent analyzes the trace and stores it, as it does for a request. An exception that leaves the block becomes the trace's error, and Tracepanel raises it again.

import tracepanel


def run_import(): ...


with tracepanel.trace("nightly import", kind="command"):
    run_import()

kind says what the trace is: "command", "task" or "http". Any other value makes a trace whose root is a plain step. A "command" or "task" trace ends with a line in the terminal and a link to it.

When trace() starts a trace of its own, the handle's set() adds attributes to the trace. Inside a trace that already exists, trace() adds a span instead, like span().

set_attr()

tracepanel.set_attr(key: str, value: object, *, on_trace: bool = False)

set_attr() adds an attribute to the current span. With on_trace=True, it adds the attribute to the whole trace. The same app. rule applies to key.

import tracepanel

tracepanel.set_attr("order.id", 42)
tracepanel.set_attr("tenant", "acme", on_trace=True)

event()

tracepanel.event(name: str, *, attrs: Mapping[str, object] | None = None, **values: object)

event() marks a moment on the timeline. It records a step with no duration.

import tracepanel

tracepanel.event("cache warmed", keys=120)

A full example

import time

import tracepanel


@tracepanel.traced
def rebuild_index() -> None:
    with tracepanel.span("index.load", source="db") as span:
        time.sleep(0.01)
        span.set("rows", 120)
    tracepanel.event("index rebuilt", rows=120)


with tracepanel.trace("rebuild-index"):
    tracepanel.set_attr("tenant", "acme", on_trace=True)
    rebuild_index()

A script has no framework to turn Tracepanel on, so run it with tracepanel run:

tracepanel run python rebuild.py

This records one trace named rebuild-index. It holds the span of rebuild_index, the index.load span inside it and the index rebuilt event. In a script, @traced names the span after the __main__ module, so it shows as __main__.rebuild_index. The script ends with a line in the terminal and a link to the trace:

tracepanel ▸ command rebuild-index · 13 ms · 0 queries → http://127.0.0.1:52811/#/t/01M4…

Under tracepanel run, the libraries your script uses are traced as in an app. A query with sqlite3 or psycopg, an HTTP call with requests or a Redis command shows inside the span that made it.

On this page