Skip to content

Commit 21a01f0

Browse files
authored
Merge branch 'potel-base' into potel-base-run-all-tests
2 parents 7401e67 + 5e822de commit 21a01f0

File tree

7 files changed

+111
-52
lines changed

7 files changed

+111
-52
lines changed

MIGRATION_GUIDE.md

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,16 @@ 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+
- 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:
25+
26+
| Dictionary keys | Sampling context key |
27+
| ---------------------- | -------------------- |
28+
| `celery_job["args"]` | `celery.job.args` |
29+
| `celery_job["kwargs"]` | `celery.job.kwargs` |
30+
| `celery_job["task"]` | `celery.job.task` |
31+
32+
Note that all of these are serialized, i.e., not the original `args` and `kwargs` but rather OpenTelemetry-friendly span attributes.
33+
2434
- 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:
2535

2636
| Request property | Sampling context key(s) |
@@ -71,18 +81,30 @@ Looking to upgrade from Sentry SDK 2.x to 3.x? Here's a comprehensive list of wh
7181
| `client` | `client.address`, `client.port` |
7282
| full URL | `url.full` |
7383

74-
- 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 scope, if available, are accessible as follows:
84+
- 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:
7585

7686
| RQ property | Sampling context key(s) |
7787
| --------------- | ---------------------------- |
7888
| `rq_job.args` | `rq.job.args` |
7989
| `rq_job.kwargs` | `rq.job.kwargs` |
8090
| `rq_job.func` | `rq.job.func` |
8191
| `queue.name` | `messaging.destination.name` |
82-
| `job.id` | `messaging.message.id` |
92+
| `rq_job.id` | `messaging.message.id` |
8393

8494
Note that `rq.job.args`, `rq.job.kwargs`, and `rq.job.func` are serialized and not the actual objects on the job.
8595

96+
- 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:
97+
98+
| AWS property | Sampling context key(s) |
99+
| ------------------------------------------- | ----------------------- |
100+
| `aws_event["httpMethod"]` | `http.request.method` |
101+
| `aws_event["queryStringParameters"]` | `url.query` |
102+
| `aws_event["path"]` | `url.path` |
103+
| full URL | `url.full` |
104+
| `aws_event["headers"]["X-Forwarded-Proto"]` | `network.protocol.name` |
105+
| `aws_event["headers"]["Host"]` | `server.address` |
106+
| `aws_context["function_name"]` | `faas.name` |
107+
86108
### Removed
87109

88110
- Spans no longer have a `description`. Use `name` instead.

sentry_sdk/integrations/aws_lambda.py

Lines changed: 38 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,17 @@
3939
MILLIS_TO_SECONDS = 1000.0
4040

4141

42+
EVENT_TO_ATTRIBUTES = {
43+
"httpMethod": "http.request.method",
44+
"queryStringParameters": "url.query",
45+
"path": "url.path",
46+
}
47+
48+
CONTEXT_TO_ATTRIBUTES = {
49+
"function_name": "faas.name",
50+
}
51+
52+
4253
def _wrap_init_error(init_error):
4354
# type: (F) -> F
4455
@ensure_integration_enabled(AwsLambdaIntegration, init_error)
@@ -151,10 +162,7 @@ def sentry_handler(aws_event, aws_context, *args, **kwargs):
151162
name=aws_context.function_name,
152163
source=TRANSACTION_SOURCE_COMPONENT,
153164
origin=AwsLambdaIntegration.origin,
154-
custom_sampling_context={
155-
"aws_event": aws_event,
156-
"aws_context": aws_context,
157-
},
165+
attributes=_prepopulate_attributes(aws_event, aws_context),
158166
):
159167
try:
160168
return handler(aws_event, aws_context, *args, **kwargs)
@@ -457,3 +465,29 @@ def _event_from_error_json(error_json):
457465
} # type: Event
458466

459467
return event
468+
469+
470+
def _prepopulate_attributes(aws_event, aws_context):
471+
attributes = {}
472+
473+
for prop, attr in EVENT_TO_ATTRIBUTES.items():
474+
if aws_event.get(prop) is not None:
475+
attributes[attr] = aws_event[prop]
476+
477+
for prop, attr in CONTEXT_TO_ATTRIBUTES.items():
478+
if getattr(aws_context, prop, None) is not None:
479+
attributes[attr] = getattr(aws_context, prop)
480+
481+
url = _get_url(aws_event, aws_context)
482+
if url:
483+
if aws_event.get("queryStringParameters"):
484+
url += f"?{aws_event['queryStringParameters']}"
485+
attributes["url.full"] = url
486+
487+
headers = aws_event.get("headers") or {}
488+
if headers.get("X-Forwarded-Proto"):
489+
attributes["network.protocol.name"] = headers["X-Forwarded-Proto"]
490+
if headers.get("Host"):
491+
attributes["server.address"] = headers["Host"]
492+
493+
return attributes

sentry_sdk/integrations/celery/__init__.py

Lines changed: 13 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
ensure_integration_enabled,
2121
event_from_exception,
2222
reraise,
23+
_serialize_span_attribute,
2324
)
2425

2526
from typing import TYPE_CHECKING
@@ -318,15 +319,9 @@ def _inner(*args, **kwargs):
318319
name=task.name,
319320
source=TRANSACTION_SOURCE_TASK,
320321
origin=CeleryIntegration.origin,
321-
custom_sampling_context={
322-
"celery_job": {
323-
"task": task.name,
324-
# for some reason, args[1] is a list if non-empty but a
325-
# tuple if empty
326-
"args": list(args[1]),
327-
"kwargs": args[2],
328-
}
329-
},
322+
# for some reason, args[1] is a list if non-empty but a
323+
# tuple if empty
324+
attributes=_prepopulate_attributes(task, list(args[1]), args[2]),
330325
) as transaction:
331326
transaction.set_status(SPANSTATUS.OK)
332327
return f(*args, **kwargs)
@@ -516,3 +511,12 @@ def sentry_publish(self, *args, **kwargs):
516511
return original_publish(self, *args, **kwargs)
517512

518513
Producer.publish = sentry_publish
514+
515+
516+
def _prepopulate_attributes(task, args, kwargs):
517+
attributes = {
518+
"celery.job.task": task.name,
519+
"celery.job.args": _serialize_span_attribute(args),
520+
"celery.job.kwargs": _serialize_span_attribute(kwargs),
521+
}
522+
return attributes

tests/integrations/asyncio/test_asyncio.py

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ async def test_create_task(
7474

7575
events = capture_events()
7676

77-
with sentry_sdk.start_transaction(name="test_transaction_for_create_task"):
77+
with sentry_sdk.start_span(name="test_transaction_for_create_task"):
7878
with sentry_sdk.start_span(op="root", name="not so important"):
7979
tasks = [event_loop.create_task(foo()), event_loop.create_task(bar())]
8080
await asyncio.wait(tasks, return_when=asyncio.FIRST_EXCEPTION)
@@ -117,7 +117,7 @@ async def test_gather(
117117

118118
events = capture_events()
119119

120-
with sentry_sdk.start_transaction(name="test_transaction_for_gather"):
120+
with sentry_sdk.start_span(name="test_transaction_for_gather"):
121121
with sentry_sdk.start_span(op="root", name="not so important"):
122122
await asyncio.gather(foo(), bar(), return_exceptions=True)
123123

@@ -160,7 +160,8 @@ async def test_exception(
160160

161161
events = capture_events()
162162

163-
with sentry_sdk.start_transaction(name="test_exception"):
163+
with sentry_sdk.start_span(name="test_exception"):
164+
sentry_sdk.get_isolation_scope().set_transaction_name("test_exception")
164165
with sentry_sdk.start_span(op="root", name="not so important"):
165166
tasks = [event_loop.create_task(boom()), event_loop.create_task(bar())]
166167
await asyncio.wait(tasks, return_when=asyncio.FIRST_EXCEPTION)
@@ -375,7 +376,7 @@ async def test_span_origin(
375376

376377
events = capture_events()
377378

378-
with sentry_sdk.start_transaction(name="something"):
379+
with sentry_sdk.start_span(name="something"):
379380
tasks = [
380381
event_loop.create_task(foo()),
381382
]

tests/integrations/aws_lambda/test_aws.py

Lines changed: 7 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -619,18 +619,12 @@ def test_handler(event, context):
619619
traces_sampler.assert_any_call(
620620
DictionaryContaining(
621621
{
622-
"aws_event": DictionaryContaining({
623-
"httpMethod": "GET",
624-
"path": "/sit/stay/rollover",
625-
"headers": {"Host": "x.io", "X-Forwarded-Proto": "http"},
626-
}),
627-
"aws_context": ObjectDescribedBy(
628-
type=get_lambda_bootstrap().LambdaContext,
629-
attrs={
630-
'function_name': StringContaining("test_"),
631-
'function_version': '$LATEST',
632-
}
633-
)
622+
"http.request.method": "GET",
623+
"url.path": "/sit/stay/rollover",
624+
"url.query": "repeat=again",
625+
"url.full": "http://x.io/sit/stay/rollover?repeat=twice",
626+
"network.protocol.name": "http",
627+
"server.address": "x.io",
634628
}
635629
)
636630
)
@@ -649,7 +643,7 @@ def test_handler(event, context):
649643
)
650644
"""
651645
),
652-
b'{"httpMethod": "GET", "path": "/sit/stay/rollover", "headers": {"Host": "x.io", "X-Forwarded-Proto": "http"}}',
646+
b'{"httpMethod": "GET", "path": "/sit/stay/rollover", "query_string": {"repeat": "again"}, "headers": {"Host": "x.io", "X-Forwarded-Proto": "http"}}',
653647
)
654648

655649
assert response["Payload"]["AssertionError raised"] is False

tests/integrations/celery/test_celery.py

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
_wrap_task_run,
1414
)
1515
from sentry_sdk.integrations.celery.beat import _get_headers
16+
from sentry_sdk.utils import _serialize_span_attribute
1617
from tests.conftest import ApproxDict
1718

1819

@@ -430,7 +431,7 @@ def dummy_task(self, x, y):
430431

431432

432433
def test_traces_sampler_gets_task_info_in_sampling_context(
433-
init_celery, celery_invocation, DictionaryContaining # noqa:N803
434+
init_celery, celery_invocation
434435
):
435436
traces_sampler = mock.Mock()
436437
celery = init_celery(traces_sampler=traces_sampler)
@@ -445,10 +446,13 @@ def walk_dogs(x, y):
445446
walk_dogs, [["Maisey", "Charlie", "Bodhi", "Cory"], "Dog park round trip"], 1
446447
)
447448

448-
traces_sampler.assert_any_call(
449-
# depending on the iteration of celery_invocation, the data might be
450-
# passed as args or as kwargs, so make this generic
451-
DictionaryContaining({"celery_job": dict(task="dog_walk", **args_kwargs)})
449+
sampling_context = traces_sampler.call_args_list[1][0][0]
450+
assert sampling_context["celery.job.task"] == "dog_walk"
451+
assert sampling_context["celery.job.args"] == _serialize_span_attribute(
452+
args_kwargs["args"]
453+
)
454+
assert sampling_context["celery.job.kwargs"] == _serialize_span_attribute(
455+
args_kwargs["kwargs"]
452456
)
453457

454458

tests/integrations/httpx/test_httpx.py

Lines changed: 15 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
import responses
77

88
import sentry_sdk
9-
from sentry_sdk import capture_message, start_transaction
9+
from sentry_sdk import capture_message, start_span
1010
from sentry_sdk.consts import MATCH_ALL, SPANDATA
1111
from sentry_sdk.integrations.httpx import HttpxIntegration
1212
from tests.conftest import ApproxDict
@@ -26,7 +26,7 @@ def before_breadcrumb(crumb, hint):
2626
url = "http://example.com/"
2727
responses.add(responses.GET, url, status=200)
2828

29-
with start_transaction():
29+
with start_span():
3030
events = capture_events()
3131

3232
if asyncio.iscoroutinefunction(httpx_client.get):
@@ -72,11 +72,10 @@ def test_outgoing_trace_headers(sentry_init, httpx_client, capture_envelopes):
7272
url = "http://example.com/"
7373
responses.add(responses.GET, url, status=200)
7474

75-
with start_transaction(
75+
with start_span(
7676
name="/interactions/other-dogs/new-dog",
7777
op="greeting.sniff",
78-
trace_id="01234567890123456789012345678901",
79-
) as transaction:
78+
) as span:
8079
if asyncio.iscoroutinefunction(httpx_client.get):
8180
response = asyncio.get_event_loop().run_until_complete(
8281
httpx_client.get(url)
@@ -102,7 +101,7 @@ def test_outgoing_trace_headers(sentry_init, httpx_client, capture_envelopes):
102101
(httpx.Client(), httpx.AsyncClient()),
103102
)
104103
def test_outgoing_trace_headers_append_to_baggage(
105-
sentry_init, httpx_client, capture_envelopes
104+
sentry_init, httpx_client, capture_envelopes, SortedBaggage, # noqa: N803
106105
):
107106
sentry_init(
108107
traces_sample_rate=1.0,
@@ -115,11 +114,10 @@ def test_outgoing_trace_headers_append_to_baggage(
115114
url = "http://example.com/"
116115
responses.add(responses.GET, url, status=200)
117116

118-
with start_transaction(
117+
with start_span(
119118
name="/interactions/other-dogs/new-dog",
120119
op="greeting.sniff",
121-
trace_id="01234567890123456789012345678901",
122-
) as transaction:
120+
):
123121
if asyncio.iscoroutinefunction(httpx_client.get):
124122
response = asyncio.get_event_loop().run_until_complete(
125123
httpx_client.get(url, headers={"baGGage": "custom=data"})
@@ -130,17 +128,18 @@ def test_outgoing_trace_headers_append_to_baggage(
130128
(envelope,) = envelopes
131129
transaction = envelope.get_transaction_event()
132130
request_span = transaction["spans"][-1]
131+
trace_id = transaction["contexts"]["trace"]["trace_id"]
133132

134133
assert response.request.headers[
135134
"sentry-trace"
136135
] == "{trace_id}-{parent_span_id}-{sampled}".format(
137-
trace_id=transaction["contexts"]["trace"]["trace_id"],
136+
trace_id=trace_id,
138137
parent_span_id=request_span["span_id"],
139138
sampled=1,
140139
)
141140
assert (
142141
response.request.headers["baggage"]
143-
== "custom=data,sentry-trace_id=01234567890123456789012345678901,sentry-environment=production,sentry-release=d08ebdb9309e1b004c6f52202de58a09c2268e42,sentry-transaction=/interactions/other-dogs/new-dog,sentry-sample_rate=1.0,sentry-sampled=true"
142+
== SortedBaggage(f"custom=data,sentry-trace_id={trace_id},sentry-environment=production,sentry-release=d08ebdb9309e1b004c6f52202de58a09c2268e42,sentry-transaction=/interactions/other-dogs/new-dog,sentry-sample_rate=1.0,sentry-sampled=true")
144143
)
145144

146145

@@ -274,7 +273,7 @@ def test_option_trace_propagation_targets(
274273
integrations=[HttpxIntegration()],
275274
)
276275

277-
with sentry_sdk.start_transaction(): # Must be in a transaction to propagate headers
276+
with sentry_sdk.start_span(): # Must be in a root span to propagate headers
278277
if asyncio.iscoroutinefunction(httpx_client.get):
279278
asyncio.get_event_loop().run_until_complete(httpx_client.get(url))
280279
else:
@@ -288,7 +287,7 @@ def test_option_trace_propagation_targets(
288287
assert "sentry-trace" not in request_headers
289288

290289

291-
def test_do_not_propagate_outside_transaction(sentry_init, httpx_mock):
290+
def test_propagates_twp_outside_root_span(sentry_init, httpx_mock):
292291
httpx_mock.add_response()
293292

294293
sentry_init(
@@ -301,7 +300,8 @@ def test_do_not_propagate_outside_transaction(sentry_init, httpx_mock):
301300
httpx_client.get("http://example.com/")
302301

303302
request_headers = httpx_mock.get_request().headers
304-
assert "sentry-trace" not in request_headers
303+
assert "sentry-trace" in request_headers
304+
assert request_headers["sentry-trace"] == sentry_sdk.get_traceparent()
305305

306306

307307
@pytest.mark.tests_internal_exceptions
@@ -352,7 +352,7 @@ def test_span_origin(sentry_init, capture_events, httpx_client):
352352
url = "http://example.com/"
353353
responses.add(responses.GET, url, status=200)
354354

355-
with start_transaction(name="test_transaction"):
355+
with start_span(name="test_root_span"):
356356
if asyncio.iscoroutinefunction(httpx_client.get):
357357
asyncio.get_event_loop().run_until_complete(httpx_client.get(url))
358358
else:

0 commit comments

Comments
 (0)