Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Unpack WSGI environ into span attrs accessible in traces sampler #3775

Merged
merged 34 commits into from
Nov 15, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
18103bc
Remove custom_sampling_context
sentrivana Nov 7, 2024
68402ff
confusing wording
sentrivana Nov 7, 2024
70d02cc
Merge branch 'potel-base' into ivana/sampling-context
sentrivana Nov 7, 2024
fa5d0ba
keep old format (why not)
sentrivana Nov 7, 2024
5df9ccc
.
sentrivana Nov 7, 2024
472ab5c
add attrs
sentrivana Nov 7, 2024
f893f12
.
sentrivana Nov 7, 2024
fff9f16
.
sentrivana Nov 7, 2024
ce815e8
more removals
sentrivana Nov 7, 2024
d307fe1
Merge branch 'potel-base' into ivana/sampling-context
sentrivana Nov 12, 2024
747ecc6
more readme
sentrivana Nov 12, 2024
7ae6745
more info
sentrivana Nov 12, 2024
09d247e
comment
sentrivana Nov 12, 2024
1aeeb68
small change
sentrivana Nov 12, 2024
786bb9e
.
sentrivana Nov 12, 2024
80427c6
dont do everything in this pr
sentrivana Nov 12, 2024
2acef08
get rid of none attributes
sentrivana Nov 12, 2024
eb2df70
dropped result too
sentrivana Nov 12, 2024
c559669
Use attributes instead of custom_sampling_context in WSGI
sentrivana Nov 12, 2024
6e71d36
Merge branch 'potel-base' into ivana/custom-sampling-context-wsgi
sentrivana Nov 12, 2024
e0ddad5
Merge branch 'potel-base' into ivana/custom-sampling-context-wsgi
sentrivana Nov 13, 2024
ef68e44
.
sentrivana Nov 13, 2024
51ed959
Merge branch 'potel-base' into ivana/custom-sampling-context-wsgi
sentrivana Nov 14, 2024
e20dc06
Merge branch 'potel-base' into ivana/custom-sampling-context-wsgi
sentrivana Nov 14, 2024
be08540
todo
sentrivana Nov 14, 2024
126c3e3
Merge branch 'potel-base' into ivana/custom-sampling-context-wsgi
sentrivana Nov 14, 2024
c3fe091
url.full
sentrivana Nov 14, 2024
31505a5
migration guide
sentrivana Nov 14, 2024
5dff8d8
formatting
sentrivana Nov 15, 2024
f0ac52f
Merge branch 'potel-base' into ivana/custom-sampling-context-wsgi
sentrivana Nov 15, 2024
471ad40
missing keys
sentrivana Nov 15, 2024
1a38de3
also add query for asgi
sentrivana Nov 15, 2024
48ddc4f
fix test
sentrivana Nov 15, 2024
affbe59
reorg
sentrivana Nov 15, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 15 additions & 1 deletion MIGRATION_GUIDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,13 +21,27 @@ Looking to upgrade from Sentry SDK 2.x to 3.x? Here's a comprehensive list of wh
- clickhouse-driver integration: The query is now available under the `db.query.text` span attribute (only if `send_default_pii` is `True`).
- `sentry_sdk.init` now returns `None` instead of a context manager.
- The `sampling_context` argument of `traces_sampler` now additionally contains all span attributes known at span start.
- 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:
- 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:

| Env property | Sampling context key(s) |
| ----------------- | ------------------------------------------------- |
| `PATH_INFO` | `url.path` |
| `QUERY_STRING` | `url.query` |
| `REQUEST_METHOD` | `http.request.method` |
| `SERVER_NAME` | `server.address` |
| `SERVER_PORT` | `server.port` |
| `SERVER_PROTOCOL` | `server.protocol.name`, `server.protocol.version` |
| `wsgi.url_scheme` | `url.scheme` |
| full URL | `url.full` |

- 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:

| Scope property | Sampling context key(s) |
| -------------- | ------------------------------- |
| `type` | `network.protocol.name` |
| `scheme` | `url.scheme` |
| `path` | `url.path` |
| `query` | `url.query` |
| `http_version` | `network.protocol.version` |
| `method` | `http.request.method` |
| `server` | `server.address`, `server.port` |
Expand Down
1 change: 1 addition & 0 deletions sentry_sdk/integrations/asgi.py
Original file line number Diff line number Diff line change
Expand Up @@ -356,6 +356,7 @@ def _prepopulate_attributes(scope):
full_url = _get_url(scope)
query = _get_query(scope)
if query:
attributes["url.query"] = query
full_url = f"{full_url}?{query}"

attributes["url.full"] = full_url
Expand Down
39 changes: 38 additions & 1 deletion sentry_sdk/integrations/wsgi.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,15 @@ def __call__(self, status, response_headers, exc_info=None): # type: ignore

DEFAULT_TRANSACTION_NAME = "generic WSGI request"

ENVIRON_TO_ATTRIBUTE = {
"PATH_INFO": "url.path",
"QUERY_STRING": "url.query",
"REQUEST_METHOD": "http.request.method",
"SERVER_NAME": "server.address",
"SERVER_PORT": "server.port",
"wsgi.url_scheme": "url.scheme",
}


def wsgi_decoding_dance(s, charset="utf-8", errors="replace"):
# type: (str, str, str) -> str
Expand Down Expand Up @@ -120,7 +129,9 @@ def __call__(self, environ, start_response):
name=DEFAULT_TRANSACTION_NAME,
source=TRANSACTION_SOURCE_ROUTE,
origin=self.span_origin,
custom_sampling_context={"wsgi_environ": environ},
attributes=_prepopulate_attributes(
environ, self.use_x_forwarded_for
),
)
if should_trace
else nullcontext()
Expand Down Expand Up @@ -309,3 +320,29 @@ def event_processor(event, hint):
return event

return event_processor


def _prepopulate_attributes(wsgi_environ, use_x_forwarded_for=False):
"""Extract span attributes from the WSGI environment."""
attributes = {}

for property, attr in ENVIRON_TO_ATTRIBUTE.items():
if wsgi_environ.get(property) is not None:
attributes[attr] = wsgi_environ[property]

if wsgi_environ.get("SERVER_PROTOCOL") is not None:
try:
proto, version = wsgi_environ["SERVER_PROTOCOL"].split("/")
attributes["network.protocol.name"] = proto
attributes["network.protocol.version"] = version
except Exception:
attributes["network.protocol.name"] = wsgi_environ["SERVER_PROTOCOL"]

try:
url = get_request_url(wsgi_environ, use_x_forwarded_for)
query = wsgi_environ.get("QUERY_STRING")
attributes["url.full"] = f"{url}?{query}"
except Exception:
pass

return attributes
1 change: 1 addition & 0 deletions tests/integrations/asgi/test_asgi.py
Original file line number Diff line number Diff line change
Expand Up @@ -728,6 +728,7 @@ async def test_asgi_scope_in_traces_sampler(sentry_init, asgi3_app):
def dummy_traces_sampler(sampling_context):
assert sampling_context["url.path"] == "/test"
assert sampling_context["url.scheme"] == "http"
assert sampling_context["url.query"] == "hello=there"
assert sampling_context["url.full"] == "/test?hello=there"
assert sampling_context["http.request.method"] == "GET"
assert sampling_context["network.protocol.version"] == "1.1"
Expand Down
26 changes: 11 additions & 15 deletions tests/integrations/wsgi/test_wsgi.py
Original file line number Diff line number Diff line change
Expand Up @@ -334,25 +334,21 @@ def app(environ, start_response):
start_response("200 OK", [])
return ["Go get the ball! Good dog!"]

traces_sampler = mock.Mock(return_value=True)
def traces_sampler(sampling_context):
assert sampling_context["http.request.method"] == "GET"
assert sampling_context["url.path"] == "/dogs/are/great/"
assert sampling_context["url.query"] == "cats=too"
assert sampling_context["url.scheme"] == "http"
assert (
sampling_context["url.full"] == "http://localhost/dogs/are/great/?cats=too"
)
return True

sentry_init(send_default_pii=True, traces_sampler=traces_sampler)
app = SentryWsgiMiddleware(app)
client = Client(app)

client.get("/dogs/are/great/")

traces_sampler.assert_any_call(
DictionaryContaining(
{
"wsgi_environ": DictionaryContaining(
{
"PATH_INFO": "/dogs/are/great/",
"REQUEST_METHOD": "GET",
},
),
}
)
)
client.get("/dogs/are/great/?cats=too")


def test_session_mode_defaults_to_request_mode_in_wsgi_handler(
Expand Down
Loading