---
title: "Custom Instrumentation"
description: "Learn how to capture performance data on any action in your app."
url: https://docs.sentry.io/platforms/python/tracing/instrumentation/custom-instrumentation/
---

# Custom Instrumentation | Sentry for Python

The Sentry SDK for Python does a very good job of auto instrumenting your application. If you use one of the popular frameworks, we've got you covered because well-known operations like HTTP calls and database queries will be instrumented out of the box. The Sentry SDK will also check your installed Python packages and auto-enable the matching SDK integrations. If you want to enable tracing in a piece of code that performs some other operations, add the `@sentry_sdk.trace` decorator.

## [Add a Transaction](https://docs.sentry.io/platforms/python/tracing/instrumentation/custom-instrumentation.md#add-a-transaction)

Adding transactions will allow you to instrument and capture certain regions of your code.

If you're using one of Sentry's SDK integrations, transactions will be created for you automatically.

The following example creates a transaction for an expensive operation (in this case, `eat_pizza`), and then sends the result to Sentry:

```python
import sentry_sdk

def eat_slice(slice):
    ...

def eat_pizza(pizza):
    with sentry_sdk.start_transaction(op="task", name="Eat Pizza"):
        while pizza.slices > 0:
            eat_slice(pizza.slices.pop())
```

The [API reference](https://getsentry.github.io/sentry-python/api.html#sentry_sdk.api.start_transaction) documents `start_transaction` and all its parameters.

Note that `sentry_sdk.start_transaction()` is meant be used as a context manager. This ensures that the transaction will be properly set as active and any spans created within will be attached to it.

## [Add Spans to a Transaction](https://docs.sentry.io/platforms/python/tracing/instrumentation/custom-instrumentation.md#add-spans-to-a-transaction)

If you want to have more fine-grained performance monitoring, you can add child spans to your transaction, which can be done by either:

* Using a context manager
* Using a decorator (this works on sync and async functions)
* Manually starting and finishing a span

Calling `sentry_sdk.start_span()` will find the current active transaction and attach the span to it.

### [Using a Context Manager](https://docs.sentry.io/platforms/python/tracing/instrumentation/custom-instrumentation.md#using-a-context-manager)

```python
import sentry_sdk

def eat_slice(slice):
    ...

def eat_pizza(pizza):
    with sentry_sdk.start_transaction(op="task", name="Eat Pizza"):
        while pizza.slices > 0:
            with sentry_sdk.start_span(name="Eat Slice"):
                eat_slice(pizza.slices.pop())
```

##### Changed in 2.15.0

The parameter `name` in `start_span()` used to be called `description`. In version 2.15.0 `description` was deprecated and from 2.15.0 on, only `name` should be used. `description` will be removed in `3.0.0`.

### [Using a Decorator](https://docs.sentry.io/platforms/python/tracing/instrumentation/custom-instrumentation.md#using-a-decorator)

```python
import sentry_sdk

@sentry_sdk.trace
def eat_slice(slice):
    ...

def eat_pizza(pizza):
    with sentry_sdk.start_transaction(op="task", name="Eat Pizza"):
        while pizza.slices > 0:
            eat_slice(pizza.slices.pop())
```

See the [@sentry\_sdk.trace decoration section](https://docs.sentry.io/platforms/python/tracing/instrumentation/custom-instrumentation.md#sentry_sdktrace-decorator) below for more details.

##### Static & Class Methods

When tracing a static or class method, you **must** add the `@sentry_sdk.trace` decorator **after** the `@staticmethod` or `@classmethod` decorator (i.e., **closer** to the function definition). Otherwise, your function will break!

### [Manually Starting and Finishing a Span](https://docs.sentry.io/platforms/python/tracing/instrumentation/custom-instrumentation.md#manually-starting-and-finishing-a-span)

```python
import sentry_sdk

def eat_slice(slice):
    ...

def eat_pizza(pizza):
    with sentry_sdk.start_transaction(op="task", name="Eat Pizza"):
        while pizza.slices > 0:
            span = sentry_sdk.start_span(name="Eat Slice")
            eat_slice(pizza.slices.pop())
            span.finish()
```

##### Changed in 2.15.0

The parameter `name` in `start_span()` used to be called `description`. In version 2.15.0 `description` was deprecated and from 2.15.0 on, only `name` should be used. `description` will be removed in `3.0.0`.

When you create your span manually, make sure to call `span.finish()` after the block of code you want to wrap in a span to finish the span. If you do not finish the span it will not be sent to Sentry.

## [Nested Spans](https://docs.sentry.io/platforms/python/tracing/instrumentation/custom-instrumentation.md#nested-spans)

Spans can be nested to form a span tree. If you'd like to learn more, read our [distributed tracing](https://docs.sentry.io/product/sentry-basics/tracing/distributed-tracing.md) documentation.

### [Using a Context Manager](https://docs.sentry.io/platforms/python/tracing/instrumentation/custom-instrumentation.md#using-a-context-manager-1)

```python
import sentry_sdk

def chew():
    ...

def eat_slice(slice):
    with sentry_sdk.start_span(name="Eat Slice"):
        with sentry_sdk.start_span(name="Chew"):
            chew()
```

##### Changed in 2.15.0

The parameter `name` in `start_span()` used to be called `description`. In version 2.15.0 `description` was deprecated and from 2.15.0 on, only `name` should be used. `description` will be removed in `3.0.0`.

### [Using a Decorator](https://docs.sentry.io/platforms/python/tracing/instrumentation/custom-instrumentation.md#using-a-decorator-1)

```python
import sentry_sdk

@sentry_sdk.trace
def chew():
    ...

@sentry_sdk.trace
def eat_slice(slice):
    chew()
```

See the [@sentry\_sdk.trace decoration section](https://docs.sentry.io/platforms/python/tracing/instrumentation/custom-instrumentation.md#sentry_sdktrace-decorator) below for more details.

### [Manually](https://docs.sentry.io/platforms/python/tracing/instrumentation/custom-instrumentation.md#manually)

```python
import sentry_sdk

def chew():
    ...

def eat_slice(slice):
    parent_span = sentry_sdk.start_span(name="Eat Slice")

    child_span = parent_span.start_child(name="Chew")
    chew()
    child_span.finish()

    parent_span.finish()
```

##### Changed in 2.15.0

The parameter `name` in `start_span()` used to be called `description`. In version 2.15.0 `description` was deprecated and from 2.15.0 on, only `name` should be used. `description` will be removed in `3.0.0`.

The parameters of `start_span()` and `start_child()` are the same. See the [API reference](https://getsentry.github.io/sentry-python/api.html#sentry_sdk.api.start_span) for more details.

When you create your span manually, make sure to call `span.finish()` after the block of code you want to wrap in a span to finish the span. If you do not finish the span it will not be sent to Sentry.

## [@sentry\_sdk.trace decorator](https://docs.sentry.io/platforms/python/tracing/instrumentation/custom-instrumentation.md#sentry_sdktrace-decorator)

You can set `op`, `name` and `attributes` parameters in the `@sentry_sdk.trace` decorator to customize your spans. Attribute values can only be primitive types (like `int`, `float`, `bool`, `str`) or a list of those types without mixing types.

When tracing a static or class method, you **must** add the `@sentry_sdk.trace` decorator **after** the `@staticmethod` or `@classmethod` decorator (i.e., **closer** to the function definition). Otherwise, your function will break.

##### New in 2.35.0

The parameters `op`, `name` and `attributes` were added to the `@sentry_sdk.trace` decorator in version 2.35.0.

```python
 import sentry_sdk

+@sentry_sdk.trace(op="my_op", name="Paul", attributes={"x": True})
 def my_function(i):
     ...

 @sentry_sdk.trace
 def root_function():
     for i in range(3):
         my_function(i)

 root_function()
```

The code above will customize the `my_function` spans like this:

```mermaid
gantt
    dateFormat DD
    todayMarker off
    axisFormat %
    SPAN(op=function, name=root_function)   :a1, 01, 7d
    SPAN(op=my_op, name=Paul, x=True)    :a2, 02, 2d
    SPAN(op=my_op, name=Paul, x=True)    :a3, 04, 2d
    SPAN(op=my_op, name=Paul, x=True)    :a4, 06, 2d
```

### [Span Templates](https://docs.sentry.io/platforms/python/tracing/instrumentation/custom-instrumentation.md#span-templates)

In the `@sentry_sdk.trace` decorator you can also specify a `template`. This helps create spans that follow a certain template. Currently this is only available for spans that are created for the [AI Agents instrumentation](https://docs.sentry.io/platforms/python/tracing/instrumentation/custom-instrumentation/ai-agents-module.md#spans) of Sentry.

Available templates are `AI_AGENT`, `AI_TOOL`, and `AI_CHAT`.

```python
 import sentry_sdk
 from sentry_sdk.consts import SPANTEMPLATE

+@sentry_sdk.trace(template=SPANTEMPLATE.AI_AGENT)
 def my_function(i):
     ...

 @sentry_sdk.trace
 def root_function():
     for i in range(3):
         my_function(i)

 root_function()
```

This will treat `my_function` as an AI agent and will create the following span tree that is compatible with the [OpenTelemetry Semantic Conventions](https://opentelemetry.io/docs/specs/semconv/gen-ai/gen-ai-agent-spans/) and the Sentry conventions for [AI Agents instrumentation](https://docs.sentry.io/platforms/python/tracing/instrumentation/custom-instrumentation/ai-agents-module.md#spans). Depending on the template, there will also be a couple of attributes set by default, but those are omitted in the graph below for readability reasons:

```mermaid
gantt
    dateFormat DD
    todayMarker off
    axisFormat %
    SPAN(op=function, name=root_function)   :a1, 01, 7d
    SPAN(op=gen_ai.invoke_agent, name="invoke_agent my_function")    :a2, 02, 2d
    SPAN(op=gen_ai.invoke_agent, name="invoke_agent my_function")    :a3, 04, 2d
    SPAN(op=gen_ai.invoke_agent, name="invoke_agent my_function")    :a4, 06, 2d
```

For the span attributes that are set for the different available templates, see the AI Agents instrumentation documentation:

* `SPANTEMPLATE.AI_AGENT` -> [Invoke Agent Span](https://docs.sentry.io/platforms/python/tracing/instrumentation/custom-instrumentation/ai-agents-module.md#invoke-agent-span)
* `SPANTEMPLATE.AI_CHAT` -> [AI Request span](https://docs.sentry.io/platforms/python/tracing/instrumentation/custom-instrumentation/ai-agents-module.md#ai-client-span)
* `SPANTEMPLATE.AI_TOOL` -> [Execute Tool Span](https://docs.sentry.io/platforms/python/tracing/instrumentation/custom-instrumentation/ai-agents-module.md#execute-tool-span)

Currently it is not possible to define custom span templates.

## [Define Span Creation in a Central Place](https://docs.sentry.io/platforms/python/tracing/instrumentation/custom-instrumentation.md#define-span-creation-in-a-central-place)

To avoid having custom performance instrumentation code scattered all over your code base, pass a parameter `functions_to_trace` to your `sentry_sdk.init()` call.

```python
import sentry_sdk

functions_to_trace = [
    {"qualified_name": "myrootmodule.eat_slice"},
    {"qualified_name": "myrootmodule.swallow"},
    {"qualified_name": "myrootmodule.chew"},
    {"qualified_name": "myrootmodule.someothermodule.another.some_function"},
    {"qualified_name": "myrootmodule.SomePizzaClass.some_method"},
]

sentry_sdk.init(
    dsn="___PUBLIC_DSN___",
    functions_to_trace=functions_to_trace,
)
```

Now, whenever a function specified in `functions_to_trace` will be executed, a span will be created and attached as a child to the currently running span.

##### Important

To enable performance monitoring for the functions specified in `functions_to_trace`, the SDK needs to load the function modules. Be aware, there may be code being executed in modules during module loading. To avoid this, use the method described above to trace your functions.

## [Accessing the Current Transaction](https://docs.sentry.io/platforms/python/tracing/instrumentation/custom-instrumentation.md#accessing-the-current-transaction)

The `sentry_sdk.get_current_scope().transaction` property returns the active transaction or `None` if no transaction is active. You can use this property to modify data on the transaction.

```python
import sentry_sdk

def eat_pizza(pizza):
    transaction = sentry_sdk.get_current_scope().transaction

    if transaction is not None:
        transaction.set_tag("num_of_slices", len(pizza.slices))

    while pizza.slices > 0:
        eat_slice(pizza.slices.pop())
```

## [Accessing the Current Span](https://docs.sentry.io/platforms/python/tracing/instrumentation/custom-instrumentation.md#accessing-the-current-span)

To change data in the current span, use `sentry_sdk.get_current_span()`. This function will return a span if there's one running, otherwise it will return `None`.

In this example, we'll set a tag in the span created by the `@sentry_sdk.trace` decorator.

```python
import sentry_sdk

@sentry_sdk.trace
def eat_slice(slice):
    span = sentry_sdk.get_current_span()

    if span is not None:
        span.set_tag("slice_id", slice.id)
```

## [Improving Data on Transactions and Spans](https://docs.sentry.io/platforms/python/tracing/instrumentation/custom-instrumentation.md#improving-data-on-transactions-and-spans)

You can add data attributes to your transactions. This data is visible in the trace explorer in Sentry. Data attributes can be of type `string`, `number` or `boolean`, as well as (non-mixed) arrays of these types:

```python
with sentry_sdk.start_transaction(name="my-transaction") as transaction:
    transaction.set_data("my-data-attribute-1", "value1")
    transaction.set_data("my-data-attribute-2", 42)
    transaction.set_data("my-data-attribute-3", True)

    transaction.set_data("my-data-attribute-4", ["value1", "value2", "value3"])
    transaction.set_data("my-data-attribute-5", [42, 43, 44])
    transaction.set_data("my-data-attribute-6", [True, False, True])
```

You can add data attributes to your spans the same way, with the same type restrictions as described above.

```python
with sentry_sdk.start_span(name="my-span") as span:
    span.set_data("my-data-attribute-1", "value1")
    span.set_data("my-data-attribute-2", 42)
    span.set_data("my-data-attribute-3", True)

    span.set_data("my-data-attribute-4", ["value1", "value2", "value3"])
    span.set_data("my-data-attribute-5", [42, 43, 44])
    span.set_data("my-data-attribute-6", [True, False, True])
```

To attach data attributes to the transaction and all its spans, you can use [`before_send_transaction`](https://docs.sentry.io/platforms/python/configuration/filtering.md#using-before-send-transaction):

```python
import sentry_sdk
from sentry_sdk.types import Event, Hint

def before_send_transaction(event: Event, hint: Hint) -> Event | None:
    # Set the data attribute "foo" to "bar" on every span belonging to this
    # transaction event
    for span in event["spans"]:
        span["data"]["foo"] = "bar"

    # Set the data on the transaction itself, too
    event["contexts"]["trace"]["data"]["foo"] = "bar"

    return event

sentry_sdk.init(
    traces_sample_rate=1.0,
    before_send_transaction=before_send_transaction,
)
```

## [Update Current Span Shortcut](https://docs.sentry.io/platforms/python/tracing/instrumentation/custom-instrumentation.md#update-current-span-shortcut)

You can update the data of the currently running span using the `sentry_sdk.update_current_span()` function. You can set `op`, `name` and `attributes` to update your span. Attribute values can only be primitive types (like `int`, `float`, `bool`, `str`) or a list of those types without mixing types.

```python
 import sentry_sdk

 @sentry_sdk.trace(op="my_op", name="Paul", attributes={"x": True})
 def my_function(i):
+    sentry_sdk.update_current_span(
+        op="myOp",
+        name=f"Paul{i}",
+        attributes={"y": i},
+    )
     ...

 @sentry_sdk.trace
 def root_function():
     for i in range(3):
         my_function(i)

 root_function()
```

The code above will update the `my_function` (now `my_op`) spans with custom data like this:

```mermaid
gantt
    dateFormat DD
    todayMarker off
    axisFormat %
    SPAN(op=function, name=root_function)   :a1, 01, 7d
    SPAN(op=myOp, name=Paul0, x=True, y=0)    :a2, 02, 2d
    SPAN(op=myOp, name=Paul1, x=True, y=1)    :a3, 04, 2d
    SPAN(op=myOp, name=Paul2, x=True, y=2)    :a4, 06, 2d
```

## Pages in this section

- [Instrument AI Agents](https://docs.sentry.io/platforms/python/tracing/instrumentation/custom-instrumentation/ai-agents-module.md)
- [Instrument MCP Servers](https://docs.sentry.io/platforms/python/tracing/instrumentation/custom-instrumentation/mcp-module.md)
- [Instrument Caches](https://docs.sentry.io/platforms/python/tracing/instrumentation/custom-instrumentation/caches-module.md)
- [Instrument HTTP Requests](https://docs.sentry.io/platforms/python/tracing/instrumentation/custom-instrumentation/requests-module.md)
- [Instrument Queues](https://docs.sentry.io/platforms/python/tracing/instrumentation/custom-instrumentation/queues-module.md)
