|
| 1 | +--- |
| 2 | +title: Span Lifecycle |
| 3 | +description: "Learn how to add attributes to spans in Sentry to monitor performance and debug applications." |
| 4 | +sidebar_order: 10 |
| 5 | +--- |
| 6 | + |
| 7 | +<Alert> |
| 8 | + |
| 9 | +To capture transactions and spans customized to your organization's needs, you must first <PlatformLink to="/tracing/">set up tracing.</PlatformLink> |
| 10 | + |
| 11 | +</Alert> |
| 12 | + |
| 13 | +To add custom performance data to your application, you need to add custom instrumentation in the form of [spans](/concepts/key-terms/tracing/distributed-tracing/#traces-transactions-and-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. |
| 14 | + |
| 15 | +<PlatformContent includePath="enriching-events/import" /> |
| 16 | + |
| 17 | +## Span Lifecycle |
| 18 | + |
| 19 | +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. |
| 20 | + |
| 21 | +```python |
| 22 | +import sentry_sdk |
| 23 | + |
| 24 | +# Start a span for a task |
| 25 | +with sentry_sdk.start_span(op="task", name="Create User"): |
| 26 | + # The span will automatically end when exiting this block |
| 27 | + user = create_user( email="[email protected]") |
| 28 | + send_welcome_email(user) |
| 29 | + # The span automatically ends here when the 'with' block exits |
| 30 | +``` |
| 31 | + |
| 32 | +You can call the context manager's `__enter__` and `__exit__` methods to more explicitly control the span's lifecycle. |
| 33 | + |
| 34 | +## Span Context and Nesting |
| 35 | + |
| 36 | +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: |
| 37 | + |
| 38 | +```python |
| 39 | +import sentry_sdk |
| 40 | + |
| 41 | +with sentry_sdk.start_span(op="process", name="Process Data"): |
| 42 | + # This code is tracked in the "Process Data" span |
| 43 | + |
| 44 | + with sentry_sdk.start_span(op="task", name="Validate Input"): |
| 45 | + # This is now a child span of "Process Data" |
| 46 | + validate_data() |
| 47 | + |
| 48 | + with sentry_sdk.start_span(op="task", name="Transform Data"): |
| 49 | + # Another child span |
| 50 | + transform_data() |
| 51 | +``` |
| 52 | + |
| 53 | +## Span Starting Options |
| 54 | + |
| 55 | +The following options can be used when creating spans: |
| 56 | + |
| 57 | +| Option | Type | Description | |
| 58 | +| ------------- | --------------- | ----------------------------------------------- | |
| 59 | +| `op` | `string` | The operation of the span. | |
| 60 | +| `name` | `string` | The name of the span. | |
| 61 | +| `start_timestamp` | `datetime/float`| The start time of the span. | |
| 62 | + |
| 63 | +## Using the Context Manager |
| 64 | + |
| 65 | +For most scenarios, we recommend using the context manager approach with `sentry_sdk.start_span()`. This creates a new span that automatically starts when entering the context and ends when exiting it. |
| 66 | + |
| 67 | +```python |
| 68 | +import sentry_sdk |
| 69 | + |
| 70 | +with sentry_sdk.start_span(op="db", name="Query Users") as span: |
| 71 | + # Perform a database query |
| 72 | + users = db.query("SELECT * FROM users") |
| 73 | + |
| 74 | + # You can set an attribute on the span |
| 75 | + span.set_attribute("user_count", len(users)) |
| 76 | +``` |
| 77 | + |
| 78 | +The context manager also correctly handles exceptions, marking the span as failed if an exception occurs: |
| 79 | + |
| 80 | +```python |
| 81 | +import sentry_sdk |
| 82 | + |
| 83 | +try: |
| 84 | + with sentry_sdk.start_span(op="http", name="Call External API"): |
| 85 | + # If this raises an exception, the span will be marked as failed |
| 86 | + response = requests.get("https://api.example.com/data") |
| 87 | + response.raise_for_status() |
| 88 | +except Exception: |
| 89 | + # The span is already marked as failed and has ended |
| 90 | + pass |
| 91 | +``` |
| 92 | + |
| 93 | +## Getting the Current Span |
| 94 | + |
| 95 | +You can access the currently active span using `sentry_sdk.get_current_span()`: |
| 96 | + |
| 97 | +```python |
| 98 | +import sentry_sdk |
| 99 | + |
| 100 | +# Get the current active span |
| 101 | +current_span = sentry_sdk.get_current_span() |
| 102 | +if current_span: |
| 103 | + current_span.set_attribute("key", "value") |
| 104 | +``` |
| 105 | + |
| 106 | +## Working with Transactions |
| 107 | + |
| 108 | +[Transactions](/product/insights/overview/transaction-summary/#what-is-a-transaction), also known as root spans, are a special type of span that represent a complete operation in your application, such as a web request. A top-level span (without any parent spans in the current service) will automatically become a transaction: |
| 109 | + |
| 110 | +```python |
| 111 | +import sentry_sdk |
| 112 | + |
| 113 | +with sentry_sdk.start_span(name="Background Task", op="task") as transaction: |
| 114 | + # Your code here |
| 115 | + |
| 116 | + # You can add child spans to the transaction |
| 117 | + with sentry_sdk.start_span(op="subtask", name="Data Processing"): |
| 118 | + # Process data |
| 119 | + pass |
| 120 | +``` |
| 121 | + |
| 122 | +If you want to prevent a span from becoming a transaction, you can use |
| 123 | +the `only_as_child_span` argument: |
| 124 | + |
| 125 | +```python |
| 126 | +import sentry_sdk |
| 127 | + |
| 128 | +# This span will never be promoted to a transaction, but if there is a running |
| 129 | +# span when this span starts, it'll be attached to it as a child span |
| 130 | +with sentry_sdk.start_span(name="Background Task", op="task", only_as_child_span=True): |
| 131 | + # Your code here |
| 132 | +``` |
| 133 | + |
| 134 | +## Improving Span Data |
| 135 | + |
| 136 | +### Adding Span Attributes |
| 137 | + |
| 138 | +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 are key-value pairs where the keys are non-empty strings and the values are either primitive Python types (excluding `None`), or a list of a single primitive Python type. |
| 139 | + |
| 140 | +```python |
| 141 | +import sentry_sdk |
| 142 | + |
| 143 | +with sentry_sdk.start_span(op="db", name="Query Users") as span: |
| 144 | + # Execute the query |
| 145 | + users = db.query("SELECT * FROM users WHERE active = true") |
| 146 | + |
| 147 | + # You can add more data during execution |
| 148 | + span.set_attribute("result_count", len(users)) |
| 149 | +``` |
| 150 | + |
| 151 | +You can also add attributes to an existing span: |
| 152 | + |
| 153 | +```python |
| 154 | +import sentry_sdk |
| 155 | + |
| 156 | +# Get the current span |
| 157 | +span = sentry_sdk.get_current_span() |
| 158 | +if span: |
| 159 | + # Set individual data points |
| 160 | + span.set_attribute("user_id", user.id) |
| 161 | + span.set_attribute("request_size", len(request.body)) |
| 162 | +``` |
| 163 | + |
| 164 | +### Adding Attributes to All Spans |
| 165 | + |
| 166 | +To add attributes to all spans, use the `before_send_transaction` callback: |
| 167 | + |
| 168 | +```python |
| 169 | +import sentry_sdk |
| 170 | +from sentry_sdk.types import Event, Hint |
| 171 | + |
| 172 | +def before_send_transaction(event: Event, hint: Hint) -> Event | None: |
| 173 | + # Add attributes to the root span (transaction) |
| 174 | + if "trace" in event.get("contexts", {}): |
| 175 | + if "data" not in event["contexts"]["trace"]: |
| 176 | + event["contexts"]["trace"]["data"] = {} |
| 177 | + |
| 178 | + event["contexts"]["trace"]["data"].update({ |
| 179 | + "app_version": "1.2.3", |
| 180 | + "environment_region": "us-west-2" |
| 181 | + }) |
| 182 | + |
| 183 | + # Add attributes to all child spans |
| 184 | + for span in event.get("spans", []): |
| 185 | + if "data" not in span: |
| 186 | + span["data"] = {} |
| 187 | + |
| 188 | + span["data"].update({ |
| 189 | + "component_version": "2.0.0", |
| 190 | + "deployment_stage": "production" |
| 191 | + }) |
| 192 | + |
| 193 | + return event |
| 194 | + |
| 195 | +sentry_sdk.init( |
| 196 | + # ... |
| 197 | + before_send_transaction=before_send_transaction |
| 198 | +) |
| 199 | +``` |
| 200 | + |
| 201 | +### Adding Span Operations ("op") |
| 202 | + |
| 203 | +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`. |
| 204 | + |
| 205 | +Sentry maintains a [list of well-known span operations](https://develop.sentry.dev/sdk/performance/span-operations/#list-of-operations) that you should use when applicable: |
| 206 | + |
| 207 | +```python |
| 208 | +import sentry_sdk |
| 209 | + |
| 210 | +# HTTP client operation |
| 211 | +with sentry_sdk.start_span(op="http.client", name="Fetch User Data"): |
| 212 | + response = requests.get("https://api.example.com/users") |
| 213 | + |
| 214 | +# Database operation |
| 215 | +with sentry_sdk.start_span(op="db", name="Save User"): |
| 216 | + db.execute( |
| 217 | + "INSERT INTO users (name, email) VALUES (%s, %s)", |
| 218 | + (user.name, user.email), |
| 219 | + ) |
| 220 | + |
| 221 | +# File I/O operation |
| 222 | +with sentry_sdk.start_span(op="file.read", name="Read Config"): |
| 223 | + with open("config.json", "r") as f: |
| 224 | + config = json.load(f) |
| 225 | +``` |
| 226 | + |
| 227 | +### Updating the Span Status |
| 228 | + |
| 229 | +You can update the status of a span to indicate whether it succeeded or failed: |
| 230 | + |
| 231 | +```python |
| 232 | +import sentry_sdk |
| 233 | + |
| 234 | +with sentry_sdk.start_span(op="task", name="Process Payment") as span: |
| 235 | + try: |
| 236 | + result = process_payment(payment_id) |
| 237 | + if result.success: |
| 238 | + # Mark the span as successful |
| 239 | + span.set_status("ok") |
| 240 | + else: |
| 241 | + # Mark the span as failed |
| 242 | + span.set_status("error") |
| 243 | + span.set_attribute("error_reason", str(result.error)) |
| 244 | + except Exception: |
| 245 | + # Span will automatically be marked as failed when an exception occurs |
| 246 | + raise |
| 247 | +``` |
0 commit comments