From ab3b6eb896164291219e83ccd2d5cba8d6623714 Mon Sep 17 00:00:00 2001 From: Ivana Kellyer Date: Wed, 4 Dec 2024 13:22:22 +0100 Subject: [PATCH 1/6] improvements --- sentry_sdk/integrations/aiohttp.py | 4 +--- sentry_sdk/integrations/asgi.py | 8 ++++---- sentry_sdk/integrations/aws_lambda.py | 1 + sentry_sdk/integrations/celery/__init__.py | 1 + sentry_sdk/integrations/gcp.py | 1 + sentry_sdk/integrations/tornado.py | 4 +--- sentry_sdk/integrations/wsgi.py | 6 ++---- 7 files changed, 11 insertions(+), 14 deletions(-) diff --git a/sentry_sdk/integrations/aiohttp.py b/sentry_sdk/integrations/aiohttp.py index ccc4593606..8174071c4f 100644 --- a/sentry_sdk/integrations/aiohttp.py +++ b/sentry_sdk/integrations/aiohttp.py @@ -389,11 +389,9 @@ def _prepopulate_attributes(request): except ValueError: attributes["server.address"] = request.host - try: + with capture_internal_exceptions(): url = f"{request.scheme}://{request.host}{request.path}" # noqa: E231 if request.query_string: attributes["url.full"] = f"{url}?{request.query_string}" - except Exception: - pass return attributes diff --git a/sentry_sdk/integrations/asgi.py b/sentry_sdk/integrations/asgi.py index 80c24b8cb6..0a0787fc57 100644 --- a/sentry_sdk/integrations/asgi.py +++ b/sentry_sdk/integrations/asgi.py @@ -32,6 +32,7 @@ ) from sentry_sdk.utils import ( ContextVar, + capture_internal_exceptions, event_from_exception, HAS_REAL_CONTEXTVARS, CONTEXTVARS_ERROR_MESSAGE, @@ -348,11 +349,12 @@ def _prepopulate_attributes(scope): try: host, port = scope[attr] attributes[f"{attr}.address"] = host - attributes[f"{attr}.port"] = port + if port is not None: + attributes[f"{attr}.port"] = port except Exception: pass - try: + with capture_internal_exceptions(): full_url = _get_url(scope) query = _get_query(scope) if query: @@ -360,7 +362,5 @@ def _prepopulate_attributes(scope): full_url = f"{full_url}?{query}" attributes["url.full"] = full_url - except Exception: - pass return attributes diff --git a/sentry_sdk/integrations/aws_lambda.py b/sentry_sdk/integrations/aws_lambda.py index 177d73a638..7861b4e600 100644 --- a/sentry_sdk/integrations/aws_lambda.py +++ b/sentry_sdk/integrations/aws_lambda.py @@ -468,6 +468,7 @@ def _event_from_error_json(error_json): def _prepopulate_attributes(aws_event, aws_context): + # type: (Any, Any) -> dict[str, Any] attributes = { "cloud.provider": "aws", } diff --git a/sentry_sdk/integrations/celery/__init__.py b/sentry_sdk/integrations/celery/__init__.py index 0b66bbf05c..b38ae65f3d 100644 --- a/sentry_sdk/integrations/celery/__init__.py +++ b/sentry_sdk/integrations/celery/__init__.py @@ -514,6 +514,7 @@ def sentry_publish(self, *args, **kwargs): def _prepopulate_attributes(task, args, kwargs): + # type: (Any, *Any, **Any) -> dict[str, str] attributes = { "celery.job.task": task.name, "celery.job.args": _serialize_span_attribute(args), diff --git a/sentry_sdk/integrations/gcp.py b/sentry_sdk/integrations/gcp.py index 2f17464f70..338815f9b5 100644 --- a/sentry_sdk/integrations/gcp.py +++ b/sentry_sdk/integrations/gcp.py @@ -236,6 +236,7 @@ def _get_google_cloud_logs_url(final_time): def _prepopulate_attributes(gcp_event): + # type: (Any) -> dict[str, Any] attributes = { "cloud.provider": "gcp", } diff --git a/sentry_sdk/integrations/tornado.py b/sentry_sdk/integrations/tornado.py index 591f59ec03..a27b40fb90 100644 --- a/sentry_sdk/integrations/tornado.py +++ b/sentry_sdk/integrations/tornado.py @@ -254,9 +254,7 @@ def _prepopulate_attributes(request): except ValueError: attributes["server.address"] = request.host - try: + with capture_internal_exceptions(): attributes["url.full"] = request.full_url() - except Exception: - pass return attributes diff --git a/sentry_sdk/integrations/wsgi.py b/sentry_sdk/integrations/wsgi.py index 726a310482..edae70bc03 100644 --- a/sentry_sdk/integrations/wsgi.py +++ b/sentry_sdk/integrations/wsgi.py @@ -11,7 +11,6 @@ _filter_headers, ) from sentry_sdk.sessions import track_session -from sentry_sdk.scope import use_isolation_scope from sentry_sdk.tracing import Transaction, TRANSACTION_SOURCE_ROUTE from sentry_sdk.utils import ( ContextVar, @@ -324,6 +323,7 @@ def event_processor(event, hint): def _prepopulate_attributes(wsgi_environ, use_x_forwarded_for=False): + # type: (dict[str, str], bool) -> dict[str, str] """Extract span attributes from the WSGI environment.""" attributes = {} @@ -339,11 +339,9 @@ def _prepopulate_attributes(wsgi_environ, use_x_forwarded_for=False): except Exception: attributes["network.protocol.name"] = wsgi_environ["SERVER_PROTOCOL"] - try: + with capture_internal_exceptions(): 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 From 3154dbc5a01234732730db2c8d676214d45e016d Mon Sep 17 00:00:00 2001 From: Ivana Kellyer Date: Wed, 4 Dec 2024 14:59:39 +0100 Subject: [PATCH 2/6] adjust --- MIGRATION_GUIDE.md | 42 +++++++++++----------- sentry_sdk/integrations/celery/__init__.py | 12 +++++-- sentry_sdk/integrations/rq.py | 16 ++++++--- tests/integrations/celery/test_celery.py | 11 +++--- tests/integrations/rq/test_rq.py | 16 +++++++-- 5 files changed, 58 insertions(+), 39 deletions(-) diff --git a/MIGRATION_GUIDE.md b/MIGRATION_GUIDE.md index 1c0fa76fb0..1d49272f20 100644 --- a/MIGRATION_GUIDE.md +++ b/MIGRATION_GUIDE.md @@ -20,18 +20,18 @@ Looking to upgrade from Sentry SDK 2.x to 3.x? Here's a comprehensive list of wh - Redis integration: In Redis pipeline spans there is no `span["data"]["redis.commands"]` that contains a dict `{"count": 3, "first_ten": ["cmd1", "cmd2", ...]}` but instead `span["data"]["redis.commands.count"]` (containing `3`) and `span["data"]["redis.commands.first_ten"]` (containing `["cmd1", "cmd2", ...]`). - 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. -- If you're using the Celery integration, the `sampling_context` argument of `traces_sampler` doesn't contain the `celery_job` dictionary anymore. Instead, the individual keys are now available as: +- The `sampling_context` argument of `traces_sampler` and `profiles_sampler` now additionally contains all span attributes known at span start. +- If you're using the Celery integration and a custom traces/profiles sampler, the `sampling_context` argument of `traces_sampler` and `profiles_sampler` doesn't contain the `celery_job` dictionary anymore. Instead, the individual keys are now available as: - | Dictionary keys | Sampling context key | - | ---------------------- | -------------------- | - | `celery_job["args"]` | `celery.job.args` | - | `celery_job["kwargs"]` | `celery.job.kwargs` | - | `celery_job["task"]` | `celery.job.task` | + | Dictionary keys | Sampling context key | Example | + | ---------------------- | --------------------------- | ------------------------------ | + | `celery_job["args"]` | `celery.job.args.{index}` | `celery.job.args.0` | + | `celery_job["kwargs"]` | `celery.job.kwargs.{kwarg}` | `celery.job.kwargs.kwarg_name` | + | `celery_job["task"]` | `celery.job.task` | | Note that all of these are serialized, i.e., not the original `args` and `kwargs` but rather OpenTelemetry-friendly span attributes. -- If you're using the AIOHTTP integration, the `sampling_context` argument of `traces_sampler` doesn't contain the `aiohttp_request` object anymore. Instead, some of the individual properties of the request are accessible, if available, as follows: +- If you're using the AIOHTTP integration and a custom traces/profiles sampler, the `sampling_context` argument of `traces_sampler` and `profiles_sampler` doesn't contain the `aiohttp_request` object anymore. Instead, some of the individual properties of the request are accessible, if available, as follows: | Request property | Sampling context key(s) | | ---------------- | ------------------------------- | @@ -42,7 +42,7 @@ Looking to upgrade from Sentry SDK 2.x to 3.x? Here's a comprehensive list of wh | `scheme` | `url.scheme` | | full URL | `url.full` | -- If you're using the Tornado integration, the `sampling_context` argument of `traces_sampler` doesn't contain the `tornado_request` object anymore. Instead, some of the individual properties of the request are accessible, if available, as follows: +- If you're using the Tornado integration and a custom traces/profiles sampler, the `sampling_context` argument of `traces_sampler` and `profiles_sampler` doesn't contain the `tornado_request` object anymore. Instead, some of the individual properties of the request are accessible, if available, as follows: | Request property | Sampling context key(s) | | ---------------- | --------------------------------------------------- | @@ -54,7 +54,7 @@ Looking to upgrade from Sentry SDK 2.x to 3.x? Here's a comprehensive list of wh | `version` | `network.protocol.name`, `network.protocol.version` | | full URL | `url.full` | -- If you're using the generic WSGI integration, the `sampling_context` argument of `traces_sampler` doesn't contain the `wsgi_environ` object anymore. Instead, the individual properties of the environment are accessible, if available, as follows: +- If you're using the generic WSGI integration and a custom traces/profiles sampler, the `sampling_context` argument of `traces_sampler` and `profiles_sampler` doesn't contain the `wsgi_environ` object anymore. Instead, the individual properties of the environment are accessible, if available, as follows: | Env property | Sampling context key(s) | | ----------------- | ------------------------------------------------- | @@ -67,7 +67,7 @@ Looking to upgrade from Sentry SDK 2.x to 3.x? Here's a comprehensive list of wh | `wsgi.url_scheme` | `url.scheme` | | full URL | `url.full` | -- If you're using the generic ASGI integration, the `sampling_context` argument of `traces_sampler` doesn't contain the `asgi_scope` object anymore. Instead, the individual properties of the scope, if available, are accessible as follows: +- If you're using the generic ASGI integration and a custom traces/profiles sampler, the `sampling_context` argument of `traces_sampler` and `profiles_sampler` doesn't contain the `asgi_scope` object anymore. Instead, the individual properties of the scope, if available, are accessible as follows: | Scope property | Sampling context key(s) | | -------------- | ------------------------------- | @@ -81,19 +81,19 @@ Looking to upgrade from Sentry SDK 2.x to 3.x? Here's a comprehensive list of wh | `client` | `client.address`, `client.port` | | full URL | `url.full` | -- If you're using the RQ integration, the `sampling_context` argument of `traces_sampler` doesn't contain the `rq_job` object anymore. Instead, the individual properties of the job and the queue, if available, are accessible as follows: +- If you're using the RQ integration and a custom traces/profiles sampler, the `sampling_context` argument of `traces_sampler` and `profiles_sampler` doesn't contain the `rq_job` object anymore. Instead, the individual properties of the job and the queue, if available, are accessible as follows: - | RQ property | Sampling context key(s) | - | --------------- | ---------------------------- | - | `rq_job.args` | `rq.job.args` | - | `rq_job.kwargs` | `rq.job.kwargs` | - | `rq_job.func` | `rq.job.func` | - | `queue.name` | `messaging.destination.name` | - | `rq_job.id` | `messaging.message.id` | + | RQ property | Sampling context key | Example | + | --------------- | ---------------------------- | ---------------------- | + | `rq_job.args` | `rq.job.args.{index}` | `rq.job.args.0` | + | `rq_job.kwargs` | `rq.job.kwargs.{kwarg}` | `rq.job.args.my_kwarg` | + | `rq_job.func` | `rq.job.func` | | + | `queue.name` | `messaging.destination.name` | | + | `rq_job.id` | `messaging.message.id` | | Note that `rq.job.args`, `rq.job.kwargs`, and `rq.job.func` are serialized and not the actual objects on the job. -- If you're using the AWS Lambda integration, the `sampling_context` argument of `traces_sampler` doesn't contain the `aws_event` and `aws_context` objects anymore. Instead, the following, if available, is accessible: +- If you're using the AWS Lambda integration and a custom traces/profiles sampler, the `sampling_context` argument of `traces_sampler` and `profiles_sampler` doesn't contain the `aws_event` and `aws_context` objects anymore. Instead, the following, if available, is accessible: | AWS property | Sampling context key(s) | | ------------------------------------------- | ----------------------- | @@ -105,7 +105,7 @@ Looking to upgrade from Sentry SDK 2.x to 3.x? Here's a comprehensive list of wh | `aws_event["headers"]["Host"]` | `server.address` | | `aws_context["function_name"]` | `faas.name` | -- If you're using the GCP integration, the `sampling_context` argument of `traces_sampler` doesn't contain the `gcp_env` and `gcp_event` keys anymore. Instead, the following, if available, is accessible: +- If you're using the GCP integration and a custom traces/profiles sampler, the `sampling_context` argument of `traces_sampler` and `profiles_sampler` doesn't contain the `gcp_env` and `gcp_event` keys anymore. Instead, the following, if available, is accessible: | Old sampling context key | New sampling context key | | --------------------------------- | -------------------------- | diff --git a/sentry_sdk/integrations/celery/__init__.py b/sentry_sdk/integrations/celery/__init__.py index b38ae65f3d..a943871335 100644 --- a/sentry_sdk/integrations/celery/__init__.py +++ b/sentry_sdk/integrations/celery/__init__.py @@ -20,7 +20,6 @@ ensure_integration_enabled, event_from_exception, reraise, - _serialize_span_attribute, ) from typing import TYPE_CHECKING @@ -517,7 +516,14 @@ def _prepopulate_attributes(task, args, kwargs): # type: (Any, *Any, **Any) -> dict[str, str] attributes = { "celery.job.task": task.name, - "celery.job.args": _serialize_span_attribute(args), - "celery.job.kwargs": _serialize_span_attribute(kwargs), } + + for i, arg in enumerate(args): + with capture_internal_exceptions(): + attributes[f"celery.job.args.{i}"] = str(arg) + + for kwarg, value in kwargs.items(): + with capture_internal_exceptions(): + attributes[f"celery.job.kwargs.{kwarg}"] = str(value) + return attributes diff --git a/sentry_sdk/integrations/rq.py b/sentry_sdk/integrations/rq.py index b097b253ce..fb99fc1b89 100644 --- a/sentry_sdk/integrations/rq.py +++ b/sentry_sdk/integrations/rq.py @@ -6,7 +6,6 @@ from sentry_sdk.integrations.logging import ignore_logger from sentry_sdk.tracing import TRANSACTION_SOURCE_TASK from sentry_sdk.utils import ( - _serialize_span_attribute, capture_internal_exceptions, ensure_integration_enabled, event_from_exception, @@ -183,6 +182,7 @@ def _prepopulate_attributes(job, queue): # type: (Job, Queue) -> dict[str, Any] attributes = { "messaging.system": "rq", + "rq.job.id": job.id, } for prop, attr in JOB_PROPERTY_TO_ATTRIBUTE.items(): @@ -193,14 +193,20 @@ def _prepopulate_attributes(job, queue): if getattr(queue, prop, None) is not None: attributes[attr] = getattr(queue, prop) - for key in ("args", "kwargs"): - if getattr(job, key, None): - attributes[f"rq.job.{key}"] = _serialize_span_attribute(getattr(job, key)) + if getattr(job, "args", None): + for i, arg in enumerate(job.args): + with capture_internal_exceptions(): + attributes[f"rq.job.args.{i}"] = str(arg) + + if getattr(job, "kwargs", None): + for kwarg, value in job.kwargs.items(): + with capture_internal_exceptions(): + attributes[f"rq.job.kwargs.{kwarg}"] = str(value) func = job.func if callable(func): func = func.__name__ - attributes["rq.job.func"] = _serialize_span_attribute(func) + attributes["rq.job.func"] = str(func) return attributes diff --git a/tests/integrations/celery/test_celery.py b/tests/integrations/celery/test_celery.py index 119e0d0e39..1011429098 100644 --- a/tests/integrations/celery/test_celery.py +++ b/tests/integrations/celery/test_celery.py @@ -13,7 +13,6 @@ _wrap_task_run, ) from sentry_sdk.integrations.celery.beat import _get_headers -from sentry_sdk.utils import _serialize_span_attribute from tests.conftest import ApproxDict @@ -448,12 +447,10 @@ def walk_dogs(x, y): sampling_context = traces_sampler.call_args_list[1][0][0] assert sampling_context["celery.job.task"] == "dog_walk" - assert sampling_context["celery.job.args"] == _serialize_span_attribute( - args_kwargs["args"] - ) - assert sampling_context["celery.job.kwargs"] == _serialize_span_attribute( - args_kwargs["kwargs"] - ) + for i, arg in enumerate(args_kwargs["args"]): + assert sampling_context[f"celery.job.args.{i}"] == str(arg) + for kwarg, value in args_kwargs["kwargs"].items(): + assert sampling_context[f"celery.job.kwargs.{kwarg}"] == str(value) def test_abstract_task(capture_events, celery, celery_invocation): diff --git a/tests/integrations/rq/test_rq.py b/tests/integrations/rq/test_rq.py index fbe5a521d3..c7eeb377e6 100644 --- a/tests/integrations/rq/test_rq.py +++ b/tests/integrations/rq/test_rq.py @@ -227,13 +227,23 @@ def test_traces_sampler_gets_correct_values_in_sampling_context(sentry_init): queue = rq.Queue(connection=FakeStrictRedis()) worker = rq.SimpleWorker([queue], connection=queue.connection) - queue.enqueue(do_trick, "Bodhi", trick="roll over") + queue.enqueue( + do_trick, + "Bodhi", + {"age": 5}, + trick="roll over", + times=2, + followup=["fetch", "give paw"], + ) worker.work(burst=True) sampling_context = traces_sampler.call_args_list[0][0][0] assert sampling_context["messaging.system"] == "rq" - assert sampling_context["rq.job.args"] == ["Bodhi"] - assert sampling_context["rq.job.kwargs"] == '{"trick": "roll over"}' + assert sampling_context["rq.job.args.0"] == "Bodhi" + assert sampling_context["rq.job.args.1"] == "{'age': 5}" + assert sampling_context["rq.job.kwargs.trick"] == "roll over" + assert sampling_context["rq.job.kwargs.times"] == "2" + assert sampling_context["rq.job.kwargs.followup"] == "['fetch', 'give paw']" assert sampling_context["rq.job.func"] == "do_trick" assert sampling_context["messaging.message.id"] assert sampling_context["messaging.destination.name"] == "default" From 8624bd8b9976d822de8126624d6af96358a605c2 Mon Sep 17 00:00:00 2001 From: Ivana Kellyer Date: Thu, 5 Dec 2024 12:46:06 +0100 Subject: [PATCH 3/6] add headers --- sentry_sdk/integrations/_wsgi_common.py | 18 +++++++++++++++++- sentry_sdk/integrations/aiohttp.py | 3 +++ sentry_sdk/integrations/asgi.py | 3 +++ sentry_sdk/integrations/aws_lambda.py | 14 +++++++++++--- sentry_sdk/integrations/gcp.py | 9 ++++++++- sentry_sdk/integrations/tornado.py | 3 +++ sentry_sdk/integrations/wsgi.py | 3 +++ 7 files changed, 48 insertions(+), 5 deletions(-) diff --git a/sentry_sdk/integrations/_wsgi_common.py b/sentry_sdk/integrations/_wsgi_common.py index 072a102b7c..4c65916f80 100644 --- a/sentry_sdk/integrations/_wsgi_common.py +++ b/sentry_sdk/integrations/_wsgi_common.py @@ -3,7 +3,7 @@ import sentry_sdk from sentry_sdk.scope import should_send_default_pii -from sentry_sdk.utils import AnnotatedValue, logger +from sentry_sdk.utils import AnnotatedValue, logger, SENSITIVE_DATA_SUBSTITUTE try: from django.http.request import RawPostDataException @@ -221,6 +221,22 @@ def _filter_headers(headers): } +def _request_headers_to_span_attributes(headers): + # type: (dict[str, str]) -> dict[str, str] + attributes = {} + + headers = _filter_headers(headers) + + for header, value in headers.items(): + if isinstance(value, AnnotatedValue): + value = SENSITIVE_DATA_SUBSTITUTE + else: + value = value.lower() + attributes[f"http.request.header.{header}"] = value + + return attributes + + def _in_http_status_code_range(code, code_ranges): # type: (object, list[HttpStatusCodeRange]) -> bool for target in code_ranges: diff --git a/sentry_sdk/integrations/aiohttp.py b/sentry_sdk/integrations/aiohttp.py index 8174071c4f..59bc70e5d4 100644 --- a/sentry_sdk/integrations/aiohttp.py +++ b/sentry_sdk/integrations/aiohttp.py @@ -13,6 +13,7 @@ from sentry_sdk.sessions import track_session from sentry_sdk.integrations._wsgi_common import ( _filter_headers, + _request_headers_to_span_attributes, request_body_within_bounds, ) from sentry_sdk.tracing import ( @@ -394,4 +395,6 @@ def _prepopulate_attributes(request): if request.query_string: attributes["url.full"] = f"{url}?{request.query_string}" + attributes.update(_request_headers_to_span_attributes(dict(request.headers))) + return attributes diff --git a/sentry_sdk/integrations/asgi.py b/sentry_sdk/integrations/asgi.py index 0a0787fc57..4a3fe830eb 100644 --- a/sentry_sdk/integrations/asgi.py +++ b/sentry_sdk/integrations/asgi.py @@ -21,6 +21,7 @@ ) from sentry_sdk.integrations._wsgi_common import ( DEFAULT_HTTP_METHODS_TO_CAPTURE, + _request_headers_to_span_attributes, ) from sentry_sdk.sessions import track_session from sentry_sdk.tracing import ( @@ -363,4 +364,6 @@ def _prepopulate_attributes(scope): attributes["url.full"] = full_url + attributes.update(_request_headers_to_span_attributes(_get_headers(scope))) + return attributes diff --git a/sentry_sdk/integrations/aws_lambda.py b/sentry_sdk/integrations/aws_lambda.py index 7861b4e600..cd4db6d185 100644 --- a/sentry_sdk/integrations/aws_lambda.py +++ b/sentry_sdk/integrations/aws_lambda.py @@ -20,7 +20,10 @@ reraise, ) from sentry_sdk.integrations import Integration -from sentry_sdk.integrations._wsgi_common import _filter_headers +from sentry_sdk.integrations._wsgi_common import ( + _filter_headers, + _request_headers_to_span_attributes, +) from typing import TYPE_CHECKING @@ -162,7 +165,7 @@ def sentry_handler(aws_event, aws_context, *args, **kwargs): name=aws_context.function_name, source=TRANSACTION_SOURCE_COMPONENT, origin=AwsLambdaIntegration.origin, - attributes=_prepopulate_attributes(aws_event, aws_context), + attributes=_prepopulate_attributes(aws_event, aws_context, headers), ): try: return handler(aws_event, aws_context, *args, **kwargs) @@ -487,10 +490,15 @@ def _prepopulate_attributes(aws_event, aws_context): url += f"?{aws_event['queryStringParameters']}" attributes["url.full"] = url - headers = aws_event.get("headers") or {} + headers = {} + if aws_event.get("headers") and isinstance(aws_event["headers"], dict): + headers = aws_event["headers"] + if headers.get("X-Forwarded-Proto"): attributes["network.protocol.name"] = headers["X-Forwarded-Proto"] if headers.get("Host"): attributes["server.address"] = headers["Host"] + attributes.update(_request_headers_to_span_attributes(headers)) + return attributes diff --git a/sentry_sdk/integrations/gcp.py b/sentry_sdk/integrations/gcp.py index 338815f9b5..dd23ad1e0a 100644 --- a/sentry_sdk/integrations/gcp.py +++ b/sentry_sdk/integrations/gcp.py @@ -7,7 +7,10 @@ import sentry_sdk from sentry_sdk.consts import OP from sentry_sdk.integrations import Integration -from sentry_sdk.integrations._wsgi_common import _filter_headers +from sentry_sdk.integrations._wsgi_common import ( + _filter_headers, + _request_headers_to_span_attributes, +) from sentry_sdk.scope import should_send_default_pii from sentry_sdk.tracing import TRANSACTION_SOURCE_COMPONENT from sentry_sdk.utils import ( @@ -249,4 +252,8 @@ def _prepopulate_attributes(gcp_event): if getattr(gcp_event, key, None): attributes[attr] = getattr(gcp_event, key) + if hasattr(gcp_event, "headers"): + headers = gcp_event.headers + attributes.update(_request_headers_to_span_attributes(headers)) + return attributes diff --git a/sentry_sdk/integrations/tornado.py b/sentry_sdk/integrations/tornado.py index a27b40fb90..da25d38db0 100644 --- a/sentry_sdk/integrations/tornado.py +++ b/sentry_sdk/integrations/tornado.py @@ -22,6 +22,7 @@ RequestExtractor, _filter_headers, _is_json_content_type, + _request_headers_to_span_attributes, ) from sentry_sdk.integrations.logging import ignore_logger @@ -257,4 +258,6 @@ def _prepopulate_attributes(request): with capture_internal_exceptions(): attributes["url.full"] = request.full_url() + attributes.update(_request_headers_to_span_attributes(request.headers)) + return attributes diff --git a/sentry_sdk/integrations/wsgi.py b/sentry_sdk/integrations/wsgi.py index edae70bc03..84b440fe7b 100644 --- a/sentry_sdk/integrations/wsgi.py +++ b/sentry_sdk/integrations/wsgi.py @@ -9,6 +9,7 @@ from sentry_sdk.integrations._wsgi_common import ( DEFAULT_HTTP_METHODS_TO_CAPTURE, _filter_headers, + _request_headers_to_span_attributes, ) from sentry_sdk.sessions import track_session from sentry_sdk.tracing import Transaction, TRANSACTION_SOURCE_ROUTE @@ -344,4 +345,6 @@ def _prepopulate_attributes(wsgi_environ, use_x_forwarded_for=False): query = wsgi_environ.get("QUERY_STRING") attributes["url.full"] = f"{url}?{query}" + attributes.update(_request_headers_to_span_attributes(_get_headers(wsgi_environ))) + return attributes From 5dd1a1445f81c3479aa68e1bd16c075d97a78163 Mon Sep 17 00:00:00 2001 From: Ivana Kellyer Date: Thu, 5 Dec 2024 13:20:34 +0100 Subject: [PATCH 4/6] . --- MIGRATION_GUIDE.md | 197 +++++++++++++------------- sentry_sdk/integrations/aws_lambda.py | 4 +- sentry_sdk/integrations/tornado.py | 2 +- 3 files changed, 106 insertions(+), 97 deletions(-) diff --git a/MIGRATION_GUIDE.md b/MIGRATION_GUIDE.md index 1d49272f20..15134fcd72 100644 --- a/MIGRATION_GUIDE.md +++ b/MIGRATION_GUIDE.md @@ -21,101 +21,108 @@ 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` and `profiles_sampler` now additionally contains all span attributes known at span start. -- If you're using the Celery integration and a custom traces/profiles sampler, the `sampling_context` argument of `traces_sampler` and `profiles_sampler` doesn't contain the `celery_job` dictionary anymore. Instead, the individual keys are now available as: - - | Dictionary keys | Sampling context key | Example | - | ---------------------- | --------------------------- | ------------------------------ | - | `celery_job["args"]` | `celery.job.args.{index}` | `celery.job.args.0` | - | `celery_job["kwargs"]` | `celery.job.kwargs.{kwarg}` | `celery.job.kwargs.kwarg_name` | - | `celery_job["task"]` | `celery.job.task` | | - - Note that all of these are serialized, i.e., not the original `args` and `kwargs` but rather OpenTelemetry-friendly span attributes. - -- If you're using the AIOHTTP integration and a custom traces/profiles sampler, the `sampling_context` argument of `traces_sampler` and `profiles_sampler` doesn't contain the `aiohttp_request` object anymore. Instead, some of the individual properties of the request are accessible, if available, as follows: - - | Request property | Sampling context key(s) | - | ---------------- | ------------------------------- | - | `path` | `url.path` | - | `query_string` | `url.query` | - | `method` | `http.request.method` | - | `host` | `server.address`, `server.port` | - | `scheme` | `url.scheme` | - | full URL | `url.full` | - -- If you're using the Tornado integration and a custom traces/profiles sampler, the `sampling_context` argument of `traces_sampler` and `profiles_sampler` doesn't contain the `tornado_request` object anymore. Instead, some of the individual properties of the request are accessible, if available, as follows: - - | Request property | Sampling context key(s) | - | ---------------- | --------------------------------------------------- | - | `path` | `url.path` | - | `query` | `url.query` | - | `protocol` | `url.scheme` | - | `method` | `http.request.method` | - | `host` | `server.address`, `server.port` | - | `version` | `network.protocol.name`, `network.protocol.version` | - | full URL | `url.full` | - -- If you're using the generic WSGI integration and a custom traces/profiles sampler, the `sampling_context` argument of `traces_sampler` and `profiles_sampler` doesn't contain the `wsgi_environ` object anymore. 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` | - -- If you're using the generic ASGI integration and a custom traces/profiles sampler, the `sampling_context` argument of `traces_sampler` and `profiles_sampler` doesn't contain the `asgi_scope` object anymore. 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` | - | `client` | `client.address`, `client.port` | - | full URL | `url.full` | - -- If you're using the RQ integration and a custom traces/profiles sampler, the `sampling_context` argument of `traces_sampler` and `profiles_sampler` doesn't contain the `rq_job` object anymore. Instead, the individual properties of the job and the queue, if available, are accessible as follows: - - | RQ property | Sampling context key | Example | - | --------------- | ---------------------------- | ---------------------- | - | `rq_job.args` | `rq.job.args.{index}` | `rq.job.args.0` | - | `rq_job.kwargs` | `rq.job.kwargs.{kwarg}` | `rq.job.args.my_kwarg` | - | `rq_job.func` | `rq.job.func` | | - | `queue.name` | `messaging.destination.name` | | - | `rq_job.id` | `messaging.message.id` | | - - Note that `rq.job.args`, `rq.job.kwargs`, and `rq.job.func` are serialized and not the actual objects on the job. - -- If you're using the AWS Lambda integration and a custom traces/profiles sampler, the `sampling_context` argument of `traces_sampler` and `profiles_sampler` doesn't contain the `aws_event` and `aws_context` objects anymore. Instead, the following, if available, is accessible: - - | AWS property | Sampling context key(s) | - | ------------------------------------------- | ----------------------- | - | `aws_event["httpMethod"]` | `http.request.method` | - | `aws_event["queryStringParameters"]` | `url.query` | - | `aws_event["path"]` | `url.path` | - | full URL | `url.full` | - | `aws_event["headers"]["X-Forwarded-Proto"]` | `network.protocol.name` | - | `aws_event["headers"]["Host"]` | `server.address` | - | `aws_context["function_name"]` | `faas.name` | - -- If you're using the GCP integration and a custom traces/profiles sampler, the `sampling_context` argument of `traces_sampler` and `profiles_sampler` doesn't contain the `gcp_env` and `gcp_event` keys anymore. Instead, the following, if available, is accessible: - - | Old sampling context key | New sampling context key | - | --------------------------------- | -------------------------- | - | `gcp_env["function_name"]` | `faas.name` | - | `gcp_env["function_region"]` | `faas.region` | - | `gcp_env["function_project"]` | `gcp.function.project` | - | `gcp_env["function_identity"]` | `gcp.function.identity` | - | `gcp_env["function_entry_point"]` | `gcp.function.entry_point` | - | `gcp_event.method` | `http.request.method` | - | `gcp_event.query_string` | `url.query` | +- The integration-specific content of the `sampling_context` argument of `traces_sampler` and `profiles_sampler` now looks different. + - The Celery integration doesn't add the `celery_job` dictionary anymore. Instead, the individual keys are now available as: + + | Dictionary keys | Sampling context key | Example | + | ---------------------- | --------------------------- | ------------------------------ | + | `celery_job["args"]` | `celery.job.args.{index}` | `celery.job.args.0` | + | `celery_job["kwargs"]` | `celery.job.kwargs.{kwarg}` | `celery.job.kwargs.kwarg_name` | + | `celery_job["task"]` | `celery.job.task` | | + + Note that all of these are serialized, i.e., not the original `args` and `kwargs` but rather OpenTelemetry-friendly span attributes. + + - The AIOHTTP integration doesn't add the `aiohttp_request` object anymore. Instead, some of the individual properties of the request are accessible, if available, as follows: + + | Request property | Sampling context key(s) | + | ----------------- | ------------------------------- | + | `path` | `url.path` | + | `query_string` | `url.query` | + | `method` | `http.request.method` | + | `host` | `server.address`, `server.port` | + | `scheme` | `url.scheme` | + | full URL | `url.full` | + | `request.headers` | `http.request.header.{header}` | + + - The Tornado integration doesn't add the `tornado_request` object anymore. Instead, some of the individual properties of the request are accessible, if available, as follows: + + | Request property | Sampling context key(s) | + | ----------------- | --------------------------------------------------- | + | `path` | `url.path` | + | `query` | `url.query` | + | `protocol` | `url.scheme` | + | `method` | `http.request.method` | + | `host` | `server.address`, `server.port` | + | `version` | `network.protocol.name`, `network.protocol.version` | + | full URL | `url.full` | + | `request.headers` | `http.request.header.{header}` | + + - The WSGI integration doesn't add the `wsgi_environ` object anymore. 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` | + | `HTTP_*` | `http.request.header.{header}` | + + - The ASGI integration doesn't add the `asgi_scope` object anymore. 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` | + | `client` | `client.address`, `client.port` | + | full URL | `url.full` | + | `headers` | `http.request.header.{header}` | + + -The RQ integration doesn't add the `rq_job` object anymore. Instead, the individual properties of the job and the queue, if available, are accessible as follows: + + | RQ property | Sampling context key | Example | + | --------------- | ---------------------------- | ---------------------- | + | `rq_job.args` | `rq.job.args.{index}` | `rq.job.args.0` | + | `rq_job.kwargs` | `rq.job.kwargs.{kwarg}` | `rq.job.args.my_kwarg` | + | `rq_job.func` | `rq.job.func` | | + | `queue.name` | `messaging.destination.name` | | + | `rq_job.id` | `messaging.message.id` | | + + Note that `rq.job.args`, `rq.job.kwargs`, and `rq.job.func` are serialized and not the actual objects on the job. + + - The AWS Lambda integration doesn't add the `aws_event` and `aws_context` objects anymore. Instead, the following, if available, is accessible: + + | AWS property | Sampling context key(s) | + | ------------------------------------------- | ------------------------------- | + | `aws_event["httpMethod"]` | `http.request.method` | + | `aws_event["queryStringParameters"]` | `url.query` | + | `aws_event["path"]` | `url.path` | + | full URL | `url.full` | + | `aws_event["headers"]["X-Forwarded-Proto"]` | `network.protocol.name` | + | `aws_event["headers"]["Host"]` | `server.address` | + | `aws_context["function_name"]` | `faas.name` | + | `aws_event["headers"]` | `http.request.headers.{header}` | + + - The GCP integration doesn't add the `gcp_env` and `gcp_event` keys anymore. Instead, the following, if available, is accessible: + + | Old sampling context key | New sampling context key | + | --------------------------------- | ------------------------------ | + | `gcp_env["function_name"]` | `faas.name` | + | `gcp_env["function_region"]` | `faas.region` | + | `gcp_env["function_project"]` | `gcp.function.project` | + | `gcp_env["function_identity"]` | `gcp.function.identity` | + | `gcp_env["function_entry_point"]` | `gcp.function.entry_point` | + | `gcp_event.method` | `http.request.method` | + | `gcp_event.query_string` | `url.query` | + | `gcp_event.headers` | `http.request.header.{header}` | ### Removed diff --git a/sentry_sdk/integrations/aws_lambda.py b/sentry_sdk/integrations/aws_lambda.py index cd4db6d185..5c4bccdfbc 100644 --- a/sentry_sdk/integrations/aws_lambda.py +++ b/sentry_sdk/integrations/aws_lambda.py @@ -165,7 +165,9 @@ def sentry_handler(aws_event, aws_context, *args, **kwargs): name=aws_context.function_name, source=TRANSACTION_SOURCE_COMPONENT, origin=AwsLambdaIntegration.origin, - attributes=_prepopulate_attributes(aws_event, aws_context, headers), + attributes=_prepopulate_attributes( + request_data, aws_context, headers + ), ): try: return handler(aws_event, aws_context, *args, **kwargs) diff --git a/sentry_sdk/integrations/tornado.py b/sentry_sdk/integrations/tornado.py index da25d38db0..bb40fbf625 100644 --- a/sentry_sdk/integrations/tornado.py +++ b/sentry_sdk/integrations/tornado.py @@ -247,7 +247,7 @@ def _prepopulate_attributes(request): except ValueError: attributes["network.protocol.name"] = request.version - if getattr(request, "host", None) is not None: + if getattr(request, "host", None): try: address, port = request.host.split(":") attributes["server.address"] = address From 59eb7ee698f7718a89cd6804d3e0a749432d3df1 Mon Sep 17 00:00:00 2001 From: Ivana Kellyer Date: Thu, 5 Dec 2024 16:50:02 +0100 Subject: [PATCH 5/6] . --- sentry_sdk/integrations/_wsgi_common.py | 4 +--- sentry_sdk/integrations/wsgi.py | 4 +++- tests/integrations/aiohttp/test_aiohttp.py | 5 ++++- tests/integrations/asgi/test_asgi.py | 3 ++- tests/integrations/aws_lambda/test_aws.py | 3 ++- tests/integrations/gcp/test_gcp.py | 15 ++++++++------- tests/integrations/tornado/test_tornado.py | 3 ++- tests/integrations/wsgi/test_wsgi.py | 9 +++------ 8 files changed, 25 insertions(+), 21 deletions(-) diff --git a/sentry_sdk/integrations/_wsgi_common.py b/sentry_sdk/integrations/_wsgi_common.py index 4c65916f80..8adbe47224 100644 --- a/sentry_sdk/integrations/_wsgi_common.py +++ b/sentry_sdk/integrations/_wsgi_common.py @@ -230,9 +230,7 @@ def _request_headers_to_span_attributes(headers): for header, value in headers.items(): if isinstance(value, AnnotatedValue): value = SENSITIVE_DATA_SUBSTITUTE - else: - value = value.lower() - attributes[f"http.request.header.{header}"] = value + attributes[f"http.request.header.{header.lower()}"] = value return attributes diff --git a/sentry_sdk/integrations/wsgi.py b/sentry_sdk/integrations/wsgi.py index 84b440fe7b..7f7360a341 100644 --- a/sentry_sdk/integrations/wsgi.py +++ b/sentry_sdk/integrations/wsgi.py @@ -345,6 +345,8 @@ def _prepopulate_attributes(wsgi_environ, use_x_forwarded_for=False): query = wsgi_environ.get("QUERY_STRING") attributes["url.full"] = f"{url}?{query}" - attributes.update(_request_headers_to_span_attributes(_get_headers(wsgi_environ))) + attributes.update( + _request_headers_to_span_attributes(dict(_get_headers(wsgi_environ))) + ) return attributes diff --git a/tests/integrations/aiohttp/test_aiohttp.py b/tests/integrations/aiohttp/test_aiohttp.py index 8327832acc..4b491b152e 100644 --- a/tests/integrations/aiohttp/test_aiohttp.py +++ b/tests/integrations/aiohttp/test_aiohttp.py @@ -309,7 +309,9 @@ async def kangaroo_handler(request): app.router.add_get("/tricks/kangaroo", kangaroo_handler) client = await aiohttp_client(app) - await client.get("/tricks/kangaroo?jump=high") + await client.get( + "/tricks/kangaroo?jump=high", headers={"Custom-Header": "Custom Value"} + ) assert traces_sampler.call_count == 1 sampling_context = traces_sampler.call_args_list[0][0][0] @@ -324,6 +326,7 @@ async def kangaroo_handler(request): assert sampling_context["http.request.method"] == "GET" assert sampling_context["server.address"] == "127.0.0.1" assert sampling_context["server.port"].isnumeric() + assert sampling_context["http.request.header.custom-header"] == "Custom Value" @pytest.mark.asyncio diff --git a/tests/integrations/asgi/test_asgi.py b/tests/integrations/asgi/test_asgi.py index adfd798c72..153117f8ee 100644 --- a/tests/integrations/asgi/test_asgi.py +++ b/tests/integrations/asgi/test_asgi.py @@ -733,6 +733,7 @@ def dummy_traces_sampler(sampling_context): assert sampling_context["http.request.method"] == "GET" assert sampling_context["network.protocol.version"] == "1.1" assert sampling_context["network.protocol.name"] == "http" + assert sampling_context["http.request.header.custom-header"] == "Custom Value" sentry_init( traces_sampler=dummy_traces_sampler, @@ -742,4 +743,4 @@ def dummy_traces_sampler(sampling_context): app = SentryAsgiMiddleware(asgi3_app) async with TestClient(app) as client: - await client.get("/test?hello=there") + await client.get("/test?hello=there", headers={"Custom-Header": "Custom Value"}) diff --git a/tests/integrations/aws_lambda/test_aws.py b/tests/integrations/aws_lambda/test_aws.py index c1235ae0a0..e58fab292d 100644 --- a/tests/integrations/aws_lambda/test_aws.py +++ b/tests/integrations/aws_lambda/test_aws.py @@ -625,6 +625,7 @@ def test_handler(event, context): "url.full": "http://x.io/sit/stay/rollover?repeat=twice", "network.protocol.name": "http", "server.address": "x.io", + "http.request.header.custom-header": "Custom Value", } ) ) @@ -643,7 +644,7 @@ def test_handler(event, context): ) """ ), - b'{"httpMethod": "GET", "path": "/sit/stay/rollover", "query_string": {"repeat": "again"}, "headers": {"Host": "x.io", "X-Forwarded-Proto": "http"}}', + b'{"httpMethod": "GET", "path": "/sit/stay/rollover", "query_string": {"repeat": "again"}, "headers": {"Host": "x.io", "X-Forwarded-Proto": "http", "Custom-Header": "Custom Value"}}', ) assert response["Payload"]["AssertionError raised"] is False diff --git a/tests/integrations/gcp/test_gcp.py b/tests/integrations/gcp/test_gcp.py index f33c1b35d7..3ea97cf0e6 100644 --- a/tests/integrations/gcp/test_gcp.py +++ b/tests/integrations/gcp/test_gcp.py @@ -293,11 +293,11 @@ def test_traces_sampler_gets_correct_values_in_sampling_context( dedent( """ functionhandler = None - event = { - "type": "chase", - "chasers": ["Maisey", "Charlie"], - "num_squirrels": 2, - } + + from collections import namedtuple + GCPEvent = namedtuple("GCPEvent", ["headers"]) + event = GCPEvent(headers={"Custom-Header": "Custom Value"}) + def cloud_function(functionhandler, event): # this runs after the transaction has started, which means we # can make assertions about traces_sampler @@ -310,14 +310,15 @@ def cloud_function(functionhandler, event): "gcp.function.entry_point": "cloud_function", "gcp.function.project": "SquirrelChasing", "cloud.provider": "gcp", + "http.request.header.custom-header": "Custom Value", }) ) except AssertionError: # catch the error and return it because the error itself will # get swallowed by the SDK as an "internal exception" - return {"AssertionError raised": True,} + return {"AssertionError raised": True} - return {"AssertionError raised": False,} + return {"AssertionError raised": False} """ ) + FUNCTIONS_PRELUDE diff --git a/tests/integrations/tornado/test_tornado.py b/tests/integrations/tornado/test_tornado.py index 7ad974c535..837da07434 100644 --- a/tests/integrations/tornado/test_tornado.py +++ b/tests/integrations/tornado/test_tornado.py @@ -467,6 +467,7 @@ def traces_sampler(sampling_context): assert sampling_context["server.port"].isnumeric() assert sampling_context["network.protocol.name"] == "HTTP" assert sampling_context["network.protocol.version"] == "1.1" + assert sampling_context["http.request.header.custom-header"] == "Custom Value" return True @@ -476,4 +477,4 @@ def traces_sampler(sampling_context): ) client = tornado_testcase(Application([(r"/hi", HelloHandler)])) - client.fetch("/hi?foo=bar") + client.fetch("/hi?foo=bar", headers={"Custom-Header": "Custom Value"}) diff --git a/tests/integrations/wsgi/test_wsgi.py b/tests/integrations/wsgi/test_wsgi.py index 5aad355277..487ccbfd69 100644 --- a/tests/integrations/wsgi/test_wsgi.py +++ b/tests/integrations/wsgi/test_wsgi.py @@ -1,5 +1,4 @@ from collections import Counter -from datetime import datetime from unittest import mock import pytest @@ -327,10 +326,7 @@ def dogpark(environ, start_response): assert error_event["contexts"]["trace"]["trace_id"] == trace_id -def test_traces_sampler_gets_correct_values_in_sampling_context( - sentry_init, - DictionaryContaining, # noqa:N803 -): +def test_traces_sampler_gets_correct_values_in_sampling_context(sentry_init): def app(environ, start_response): start_response("200 OK", []) return ["Go get the ball! Good dog!"] @@ -343,13 +339,14 @@ def traces_sampler(sampling_context): assert ( sampling_context["url.full"] == "http://localhost/dogs/are/great/?cats=too" ) + assert sampling_context["http.request.header.custom-header"] == "Custom Value" return True sentry_init(send_default_pii=True, traces_sampler=traces_sampler) app = SentryWsgiMiddleware(app) client = Client(app) - client.get("/dogs/are/great/?cats=too") + client.get("/dogs/are/great/?cats=too", headers={"Custom-Header": "Custom Value"}) def test_session_mode_defaults_to_request_mode_in_wsgi_handler( From 3522dceb7ebab43c41ca3ccbfd13660b51023c51 Mon Sep 17 00:00:00 2001 From: Ivana Kellyer Date: Thu, 5 Dec 2024 16:52:56 +0100 Subject: [PATCH 6/6] . --- sentry_sdk/integrations/aws_lambda.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/sentry_sdk/integrations/aws_lambda.py b/sentry_sdk/integrations/aws_lambda.py index 5c4bccdfbc..cd2b3cc417 100644 --- a/sentry_sdk/integrations/aws_lambda.py +++ b/sentry_sdk/integrations/aws_lambda.py @@ -165,9 +165,7 @@ def sentry_handler(aws_event, aws_context, *args, **kwargs): name=aws_context.function_name, source=TRANSACTION_SOURCE_COMPONENT, origin=AwsLambdaIntegration.origin, - attributes=_prepopulate_attributes( - request_data, aws_context, headers - ), + attributes=_prepopulate_attributes(request_data, aws_context), ): try: return handler(aws_event, aws_context, *args, **kwargs)