Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit 571c5cd

Browse filesBrowse files
authored
Unpack WSGI environ into span attrs accessible in traces sampler (#3775)
1 parent e8c1813 commit 571c5cd
Copy full SHA for 571c5cd

File tree

Expand file treeCollapse file tree

5 files changed

+66
-17
lines changed
Filter options
Expand file treeCollapse file tree

5 files changed

+66
-17
lines changed

‎MIGRATION_GUIDE.md

Copy file name to clipboardExpand all lines: MIGRATION_GUIDE.md
+15-1Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,13 +21,27 @@ Looking to upgrade from Sentry SDK 2.x to 3.x? Here's a comprehensive list of wh
2121
- clickhouse-driver integration: The query is now available under the `db.query.text` span attribute (only if `send_default_pii` is `True`).
2222
- `sentry_sdk.init` now returns `None` instead of a context manager.
2323
- The `sampling_context` argument of `traces_sampler` now additionally contains all span attributes known at span start.
24-
- The `sampling_context` argument of `traces_sampler` doesn't contain the `asgi_scope` object anymore for ASGI frameworks. Instead, the individual properties on the scope, if available, are accessible as follows:
24+
- The `sampling_context` argument of `traces_sampler` doesn't contain the `wsgi_environ` object anymore for WSGI frameworks. Instead, the individual properties of the environment are accessible, if available, as follows:
25+
26+
| Env property | Sampling context key(s) |
27+
| ----------------- | ------------------------------------------------- |
28+
| `PATH_INFO` | `url.path` |
29+
| `QUERY_STRING` | `url.query` |
30+
| `REQUEST_METHOD` | `http.request.method` |
31+
| `SERVER_NAME` | `server.address` |
32+
| `SERVER_PORT` | `server.port` |
33+
| `SERVER_PROTOCOL` | `server.protocol.name`, `server.protocol.version` |
34+
| `wsgi.url_scheme` | `url.scheme` |
35+
| full URL | `url.full` |
36+
37+
- The `sampling_context` argument of `traces_sampler` doesn't contain the `asgi_scope` object anymore for ASGI frameworks. Instead, the individual properties of the scope, if available, are accessible as follows:
2538

2639
| Scope property | Sampling context key(s) |
2740
| -------------- | ------------------------------- |
2841
| `type` | `network.protocol.name` |
2942
| `scheme` | `url.scheme` |
3043
| `path` | `url.path` |
44+
| `query` | `url.query` |
3145
| `http_version` | `network.protocol.version` |
3246
| `method` | `http.request.method` |
3347
| `server` | `server.address`, `server.port` |

‎sentry_sdk/integrations/asgi.py

Copy file name to clipboardExpand all lines: sentry_sdk/integrations/asgi.py
+1Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -356,6 +356,7 @@ def _prepopulate_attributes(scope):
356356
full_url = _get_url(scope)
357357
query = _get_query(scope)
358358
if query:
359+
attributes["url.query"] = query
359360
full_url = f"{full_url}?{query}"
360361

361362
attributes["url.full"] = full_url

‎sentry_sdk/integrations/wsgi.py

Copy file name to clipboardExpand all lines: sentry_sdk/integrations/wsgi.py
+38-1Lines changed: 38 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,15 @@ def __call__(self, status, response_headers, exc_info=None): # type: ignore
4848

4949
DEFAULT_TRANSACTION_NAME = "generic WSGI request"
5050

51+
ENVIRON_TO_ATTRIBUTE = {
52+
"PATH_INFO": "url.path",
53+
"QUERY_STRING": "url.query",
54+
"REQUEST_METHOD": "http.request.method",
55+
"SERVER_NAME": "server.address",
56+
"SERVER_PORT": "server.port",
57+
"wsgi.url_scheme": "url.scheme",
58+
}
59+
5160

5261
def wsgi_decoding_dance(s, charset="utf-8", errors="replace"):
5362
# type: (str, str, str) -> str
@@ -120,7 +129,9 @@ def __call__(self, environ, start_response):
120129
name=DEFAULT_TRANSACTION_NAME,
121130
source=TRANSACTION_SOURCE_ROUTE,
122131
origin=self.span_origin,
123-
custom_sampling_context={"wsgi_environ": environ},
132+
attributes=_prepopulate_attributes(
133+
environ, self.use_x_forwarded_for
134+
),
124135
)
125136
if should_trace
126137
else nullcontext()
@@ -309,3 +320,29 @@ def event_processor(event, hint):
309320
return event
310321

311322
return event_processor
323+
324+
325+
def _prepopulate_attributes(wsgi_environ, use_x_forwarded_for=False):
326+
"""Extract span attributes from the WSGI environment."""
327+
attributes = {}
328+
329+
for property, attr in ENVIRON_TO_ATTRIBUTE.items():
330+
if wsgi_environ.get(property) is not None:
331+
attributes[attr] = wsgi_environ[property]
332+
333+
if wsgi_environ.get("SERVER_PROTOCOL") is not None:
334+
try:
335+
proto, version = wsgi_environ["SERVER_PROTOCOL"].split("/")
336+
attributes["network.protocol.name"] = proto
337+
attributes["network.protocol.version"] = version
338+
except Exception:
339+
attributes["network.protocol.name"] = wsgi_environ["SERVER_PROTOCOL"]
340+
341+
try:
342+
url = get_request_url(wsgi_environ, use_x_forwarded_for)
343+
query = wsgi_environ.get("QUERY_STRING")
344+
attributes["url.full"] = f"{url}?{query}"
345+
except Exception:
346+
pass
347+
348+
return attributes

‎tests/integrations/asgi/test_asgi.py

Copy file name to clipboardExpand all lines: tests/integrations/asgi/test_asgi.py
+1Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -728,6 +728,7 @@ async def test_asgi_scope_in_traces_sampler(sentry_init, asgi3_app):
728728
def dummy_traces_sampler(sampling_context):
729729
assert sampling_context["url.path"] == "/test"
730730
assert sampling_context["url.scheme"] == "http"
731+
assert sampling_context["url.query"] == "hello=there"
731732
assert sampling_context["url.full"] == "/test?hello=there"
732733
assert sampling_context["http.request.method"] == "GET"
733734
assert sampling_context["network.protocol.version"] == "1.1"

‎tests/integrations/wsgi/test_wsgi.py

Copy file name to clipboardExpand all lines: tests/integrations/wsgi/test_wsgi.py
+11-15Lines changed: 11 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -334,25 +334,21 @@ def app(environ, start_response):
334334
start_response("200 OK", [])
335335
return ["Go get the ball! Good dog!"]
336336

337-
traces_sampler = mock.Mock(return_value=True)
337+
def traces_sampler(sampling_context):
338+
assert sampling_context["http.request.method"] == "GET"
339+
assert sampling_context["url.path"] == "/dogs/are/great/"
340+
assert sampling_context["url.query"] == "cats=too"
341+
assert sampling_context["url.scheme"] == "http"
342+
assert (
343+
sampling_context["url.full"] == "http://localhost/dogs/are/great/?cats=too"
344+
)
345+
return True
346+
338347
sentry_init(send_default_pii=True, traces_sampler=traces_sampler)
339348
app = SentryWsgiMiddleware(app)
340349
client = Client(app)
341350

342-
client.get("/dogs/are/great/")
343-
344-
traces_sampler.assert_any_call(
345-
DictionaryContaining(
346-
{
347-
"wsgi_environ": DictionaryContaining(
348-
{
349-
"PATH_INFO": "/dogs/are/great/",
350-
"REQUEST_METHOD": "GET",
351-
},
352-
),
353-
}
354-
)
355-
)
351+
client.get("/dogs/are/great/?cats=too")
356352

357353

358354
def test_session_mode_defaults_to_request_mode_in_wsgi_handler(

0 commit comments

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.