# Tracing your code (/docs/python/tracing-your-code)

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.

```python
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.

<Callout title="Safe to leave in">
Outside a trace, and outside development, these calls do nothing. You can keep
them in the code you deploy.
</Callout>

## Attributes [#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`:

```python
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() [#span]

```python
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.

```python
import tracepanel

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

## @traced [#traced]

```python
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.

```python
import tracepanel


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


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

## trace() [#trace]

```python
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.

```python
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() [#set_attr]

```python
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`.

```python
import tracepanel

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

## event() [#event]

```python
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.

```python
import tracepanel

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

## A full example [#a-full-example]

```python
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`:

```bash
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:

```text
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.
