Custom Instrumentation

Learn how to capture performance data on any action in your app.

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"

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

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

Copied
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 documents start_transaction and all its parameters.

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 a sentry_sdk.start_span() will find the current active transaction and attach the span to it.

Copied
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())

Copied
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())

Copied
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()

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.

Spans can be nested to form a span tree. If you'd like to learn more, read our distributed tracing documentation.

Copied
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()

Copied
import sentry_sdk

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

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

Copied
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()

The parameters of start_span() and start_child() are the same. See the API reference 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.

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.

Copied
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="https://examplePublicKey@o0.ingest.sentry.io/0",
    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.

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.

Copied
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())

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.

Copied
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)
Help improve this content
Our documentation is open source and available on GitHub. Your contributions are welcome, whether fixing a typo (drat!) or suggesting an update ("yeah, this would be better").