Span Lifecycle for Python
Learn how to add attributes to spans in Sentry to monitor performance and debug applications.
To capture transactions and spans customized to your organization's needs, you must first set up tracing.
To add custom performance data to your application, you need to add custom instrumentation in the form of spans. Spans are a way to measure the time it takes for a specific action to occur. For example, you can create a span to measure the time it takes for a function to execute.
This page covers both transaction mode (default) and stream mode. See Streamed Spans to learn more.
In Python, spans are typically created using a context manager, which automatically manages the span's lifecycle. When you create a span using a context manager, the span automatically starts when entering the context and ends when exiting it. This is the recommended approach for most scenarios.
import sentry_sdk
# Start a span for a task
with sentry_sdk.start_span(op="task", name="Create User"):
# Your code here
# The span will automatically end when exiting this block
user = create_user(email="user@example.com")
send_welcome_email(user)
# The span automatically ends here when the 'with' block exits
import sentry_sdk
# Start a span for a task
with sentry_sdk.start_span(op="task", name="Create User"):
# Your code here
# The span will automatically end when exiting this block
user = create_user(email="user@example.com")
send_welcome_email(user)
# The span automatically ends here when the 'with' block exits
import sentry_sdk
# Start a span for a task
with sentry_sdk.traces.start_span(
name="Create User",
attributes={"sentry.op": "task"},
):
# Your code here
# The span will automatically end when exiting this block
user = create_user(email="user@example.com")
send_welcome_email(user)
# The span automatically ends here when the 'with' block exits
You can call the context manager's __enter__ and __exit__ methods to more explicitly control the span's lifecycle.
When you create a span, it becomes the child of the current active span. This allows you to build a hierarchy of spans that represent the execution path of your application:
import sentry_sdk
with sentry_sdk.start_span(op="process", name="Process Data"):
# This code is tracked in the "Process Data" span
with sentry_sdk.start_span(op="task", name="Validate Input"):
# This is now a child span of "Process Data"
validate_data()
with sentry_sdk.start_span(op="task", name="Transform Data"):
# Another child span
transform_data()
import sentry_sdk
with sentry_sdk.start_span(op="process", name="Process Data"):
# This code is tracked in the "Process Data" span
with sentry_sdk.start_span(op="task", name="Validate Input"):
# This is now a child span of "Process Data"
validate_data()
with sentry_sdk.start_span(op="task", name="Transform Data"):
# Another child span
transform_data()
import sentry_sdk
with sentry_sdk.traces.start_span(
name="Process Data",
attributes={"sentry.op": "process"},
):
# This code is tracked in the "Process Data" span
with sentry_sdk.traces.start_span(
name="Validate Input",
attributes={"sentry.op": "task"},
):
# This is now a child span of "Process Data"
validate_data()
with sentry_sdk.traces.start_span(
name="Transform Data",
attributes={"sentry.op": "task"},
):
# Another child span
transform_data()
The following options can be used when creating spans:
| Option | Type | Description |
|---|---|---|
op | string | The operation of the span. |
name | string | The name of the span. |
start_timestamp | datetime/float | The start time of the span. |
| Option | Type | Description |
|---|---|---|
name | string | The name of the span. |
attributes | dict | Structured metadata for the span, including sentry.op for the operation. |
start_timestamp | datetime/float | The start time of the span. |
parent_span | StreamedSpan/None | The parent to attach to. Pass None to force a service span. |
op is not a dedicated argument in stream mode. Set it as the sentry.op attribute instead.
For most scenarios, we recommend using the context manager approach. This creates a new span that automatically starts when entering the context and ends when exiting it.
import sentry_sdk
with sentry_sdk.start_span(op="db", name="Query Users") as span:
# Perform a database query
users = db.query("SELECT * FROM users")
# You can set data on the span
span.set_data("user_count", len(users))
import sentry_sdk
with sentry_sdk.start_span(op="db", name="Query Users") as span:
# Perform a database query
users = db.query("SELECT * FROM users")
# You can set data on the span
span.set_data("user_count", len(users))
import sentry_sdk
with sentry_sdk.traces.start_span(
name="Query Users",
attributes={"sentry.op": "db"},
) as span:
# Perform a database query
users = db.query("SELECT * FROM users")
# You can set attributes on the span
span.set_attribute("user_count", len(users))
The context manager also correctly handles exceptions, marking the span as failed if an exception occurs:
import sentry_sdk
try:
with sentry_sdk.start_span(op="http", name="Call External API"):
# If this raises an exception, the span will be marked as failed
response = requests.get("https://api.example.com/data")
response.raise_for_status()
except Exception:
# The span is already marked as failed and has ended
pass
import sentry_sdk
try:
with sentry_sdk.start_span(op="http", name="Call External API"):
# If this raises an exception, the span will be marked as failed
response = requests.get("https://api.example.com/data")
response.raise_for_status()
except Exception:
# The span is already marked as failed and has ended
pass
import sentry_sdk
try:
with sentry_sdk.traces.start_span(
name="Call External API",
attributes={"sentry.op": "http"},
):
# If this raises an exception, the span will be marked as failed
response = requests.get("https://api.example.com/data")
response.raise_for_status()
except Exception:
# The span is already marked as failed and has ended
pass
You can access the currently active span:
import sentry_sdk
# Get the current active span
current_span = sentry_sdk.get_current_span()
if current_span:
current_span.set_data("key", "value")
import sentry_sdk
# Get the current active span
current_span = sentry_sdk.get_current_span()
if current_span:
current_span.set_data("key", "value")
import sentry_sdk
# Get the current active span
current_span = sentry_sdk.traces.get_current_span()
if current_span:
current_span.set_attribute("key", "value")
start_transaction is only available in transaction mode.Transactions are a special type of span that represent a complete operation in your application, such as a web request. You can create transactions explicitly:
import sentry_sdk
with sentry_sdk.start_transaction(name="Background Task", op="task") as transaction:
# Your code here
# You can add child spans to the transaction
with sentry_sdk.start_span(op="subtask", name="Data Processing"):
# Process data
pass
import sentry_sdk
with sentry_sdk.start_transaction(name="Background Task", op="task") as transaction:
# Your code here
# You can add child spans to the transaction
with sentry_sdk.start_span(op="subtask", name="Data Processing"):
# Process data
pass
In stream mode, a service span (the equivalent of a transaction) is just a span started with no parent in the current application. Pass parent_span=None to force this, even if a span is currently active:
import sentry_sdk
with sentry_sdk.traces.start_span(
name="Background Task",
attributes={"sentry.op": "task"},
parent_span=None,
) as service_span:
# Your code here
# You can add child spans to the service span
with sentry_sdk.traces.start_span(
name="Data Processing",
attributes={"sentry.op": "subtask"},
):
# Process data
pass
import sentry_sdk
with sentry_sdk.traces.start_span(
name="Background Task",
attributes={"sentry.op": "task"},
parent_span=None,
) as service_span:
# Your code here
# You can add child spans to the service span
with sentry_sdk.traces.start_span(
name="Data Processing",
attributes={"sentry.op": "subtask"},
):
# Process data
pass
Span attributes customize information you can get through tracing. This information can be found in the traces views in Sentry, once you drill into a span. You can capture additional context with span attributes. These can be key-value pairs of various Python types.
import sentry_sdk
with sentry_sdk.start_span(op="db", name="Query Users") as span:
# Execute the query
users = db.query("SELECT * FROM users WHERE active = true")
# You can add more data during execution
span.set_data("result_count", len(users))
import sentry_sdk
with sentry_sdk.start_span(op="db", name="Query Users") as span:
# Execute the query
users = db.query("SELECT * FROM users WHERE active = true")
# You can add more data during execution
span.set_data("result_count", len(users))
import sentry_sdk
with sentry_sdk.traces.start_span(
name="Query Users",
attributes={"sentry.op": "db"},
) as span:
# Execute the query
users = db.query("SELECT * FROM users WHERE active = true")
# You can add more attributes during execution
span.set_attribute("result_count", len(users))
You can also add attributes to an existing span:
import sentry_sdk
# Get the current span
span = sentry_sdk.get_current_span()
if span:
# Set individual data points
span.set_data("user_id", user.id)
span.set_data("request_size", len(request.body))
import sentry_sdk
# Get the current span
span = sentry_sdk.get_current_span()
if span:
# Set individual data points
span.set_data("user_id", user.id)
span.set_data("request_size", len(request.body))
import sentry_sdk
# Get the current span
span = sentry_sdk.traces.get_current_span()
if span:
# Set individual attributes
span.set_attribute("user_id", user.id)
span.set_attribute("request_size", len(request.body))
How you add data to every span depends on your tracing mode:
import sentry_sdk
from sentry_sdk.types import Event, Hint
def before_send_transaction(event: Event, hint: Hint) -> Event | None:
# Add attributes to the root span (transaction)
if "trace" in event.get("contexts", {}):
if "data" not in event["contexts"]["trace"]:
event["contexts"]["trace"]["data"] = {}
event["contexts"]["trace"]["data"].update({
"deployment.cell": "us-west-2a",
"config.variant": "new-search"
})
# Add attributes to all child spans
for span in event.get("spans", []):
if "data" not in span:
span["data"] = {}
span["data"].update({
"deployment.cell": "us-west-2a",
"config.variant": "new-search"
})
return event
sentry_sdk.init(
# ...
before_send_transaction=before_send_transaction
)
import sentry_sdk
from sentry_sdk.types import Event, Hint
def before_send_transaction(event: Event, hint: Hint) -> Event | None:
# Add attributes to the root span (transaction)
if "trace" in event.get("contexts", {}):
if "data" not in event["contexts"]["trace"]:
event["contexts"]["trace"]["data"] = {}
event["contexts"]["trace"]["data"].update({
"deployment.cell": "us-west-2a",
"config.variant": "new-search"
})
# Add attributes to all child spans
for span in event.get("spans", []):
if "data" not in span:
span["data"] = {}
span["data"].update({
"deployment.cell": "us-west-2a",
"config.variant": "new-search"
})
return event
sentry_sdk.init(
# ...
before_send_transaction=before_send_transaction
)
import sentry_sdk
def before_send_span(span, hint):
# Runs once per span, as it's flushed.
if "attributes" not in span:
span["attributes"] = {}
span["attributes"].update({
"deployment.cell": "us-west-2a",
"config.variant": "new-search",
})
return span
sentry_sdk.init(
# ...
trace_lifecycle="stream",
before_send_span=before_send_span,
)
Note that before_send_span can only modify span data and not drop spans – use ignore_spans instead.
Spans can have an operation associated with them, which helps Sentry understand the context of the span. For example, database related spans have the db operation, while HTTP requests use http.client.
Sentry maintains a list of well-known span operations that you should use when applicable:
import sentry_sdk
# HTTP client operation
with sentry_sdk.start_span(op="http.client", name="Fetch User Data"):
response = requests.get("https://api.example.com/users")
# Database operation
with sentry_sdk.start_span(op="db", name="Save User"):
db.execute(
"INSERT INTO users (name, email) VALUES (%s, %s)",
(user.name, user.email),
)
# File I/O operation
with sentry_sdk.start_span(op="file.read", name="Read Config"):
with open("config.json", "r") as f:
config = json.load(f)
import sentry_sdk
# HTTP client operation
with sentry_sdk.start_span(op="http.client", name="Fetch User Data"):
response = requests.get("https://api.example.com/users")
# Database operation
with sentry_sdk.start_span(op="db", name="Save User"):
db.execute(
"INSERT INTO users (name, email) VALUES (%s, %s)",
(user.name, user.email),
)
# File I/O operation
with sentry_sdk.start_span(op="file.read", name="Read Config"):
with open("config.json", "r") as f:
config = json.load(f)
import sentry_sdk
# HTTP client operation
with sentry_sdk.traces.start_span(
name="Fetch User Data",
attributes={"sentry.op": "http.client"},
):
response = requests.get("https://api.example.com/users")
# Database operation
with sentry_sdk.traces.start_span(
name="Save User",
attributes={"sentry.op": "db"},
):
db.execute(
"INSERT INTO users (name, email) VALUES (%s, %s)",
(user.name, user.email),
)
# File I/O operation
with sentry_sdk.traces.start_span(
name="Read Config",
attributes={"sentry.op": "file.read"},
):
with open("config.json", "r") as f:
config = json.load(f)
You can update the status of a span to indicate whether it succeeded or failed. In stream mode, status can only be ok (default) or error.
import sentry_sdk
with sentry_sdk.start_span(op="task", name="Process Payment") as span:
try:
result = process_payment(payment_id)
if result.success:
# Mark the span as successful
span.set_status("ok")
else:
# Mark the span as failed
span.set_status("error")
span.set_data("error_reason", result.error)
except Exception:
# Span will automatically be marked as failed when an exception occurs
raise
import sentry_sdk
with sentry_sdk.start_span(op="task", name="Process Payment") as span:
try:
result = process_payment(payment_id)
if result.success:
# Mark the span as successful
span.set_status("ok")
else:
# Mark the span as failed
span.set_status("error")
span.set_data("error_reason", result.error)
except Exception:
# Span will automatically be marked as failed when an exception occurs
raise
import sentry_sdk
with sentry_sdk.traces.start_span(
name="Process Payment",
attributes={"sentry.op": "task"},
) as span:
try:
result = process_payment(payment_id)
if result.success:
# Mark the span as successful (this is also the default)
span.status = "ok"
else:
# Mark the span as failed
span.status = "error"
span.set_attribute("error_reason", result.error)
except Exception:
# Span will automatically be marked as failed when an exception occurs
raise
ignore_spans is only available in stream mode.In stream mode, you can prevent specific spans from being created using ignore_spans. It evaluates rules at span start, which can be strings, compiled regexes, or dictionaries with name and/or attributes conditions. If the dropped span is a service span, its children are dropped too. If it's a child span, only that span is dropped and its children are reparented to the nearest ancestor.
import re
import sentry_sdk
sentry_sdk.init(
trace_lifecycle="stream",
ignore_spans=[
# String match against span name
"/health",
# Regex match against span name
re.compile(r"/flow/.*"),
# Match by attributes (all must match)
{
"attributes": {
"service.id": "15def9a",
"flow.pipeline": "legacy",
}
},
# Match by name and attributes
{
"name": re.compile(r"/flow/.*"),
"attributes": {
"service.id": re.compile(r".*\.facade"),
},
},
],
)
import re
import sentry_sdk
sentry_sdk.init(
trace_lifecycle="stream",
ignore_spans=[
# String match against span name
"/health",
# Regex match against span name
re.compile(r"/flow/.*"),
# Match by attributes (all must match)
{
"attributes": {
"service.id": "15def9a",
"flow.pipeline": "legacy",
}
},
# Match by name and attributes
{
"name": re.compile(r"/flow/.*"),
"attributes": {
"service.id": re.compile(r".*\.facade"),
},
},
],
)
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").