Skip to content

Commit 04cfc86

Browse files
authored
Adds trace_propagation_targets option (#1916)
Add an option trace_propagation_targets that defines to what targets the trace headers (sentry-trace and baggage) are added in outgoing HTTP requests.
1 parent 5306eab commit 04cfc86

File tree

8 files changed

+339
-23
lines changed

8 files changed

+339
-23
lines changed

sentry_sdk/consts.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,8 @@
4242
DEFAULT_QUEUE_SIZE = 100
4343
DEFAULT_MAX_BREADCRUMBS = 100
4444

45+
MATCH_ALL = r".*"
46+
4547

4648
class INSTRUMENTER:
4749
SENTRY = "sentry"
@@ -123,6 +125,9 @@ def __init__(
123125
before_send_transaction=None, # type: Optional[TransactionProcessor]
124126
project_root=None, # type: Optional[str]
125127
enable_tracing=None, # type: Optional[bool]
128+
trace_propagation_targets=[ # noqa: B006
129+
MATCH_ALL
130+
], # type: Optional[Sequence[str]]
126131
):
127132
# type: (...) -> None
128133
pass

sentry_sdk/integrations/httpx.py

Lines changed: 17 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
from sentry_sdk import Hub
22
from sentry_sdk.consts import OP
33
from sentry_sdk.integrations import Integration, DidNotEnable
4+
from sentry_sdk.tracing_utils import should_propagate_trace
45
from sentry_sdk.utils import logger, parse_url
56

67
from sentry_sdk._types import MYPY
@@ -52,13 +53,15 @@ def send(self, request, **kwargs):
5253
span.set_data("http.query", parsed_url.query)
5354
span.set_data("http.fragment", parsed_url.fragment)
5455

55-
for key, value in hub.iter_trace_propagation_headers():
56-
logger.debug(
57-
"[Tracing] Adding `{key}` header {value} to outgoing request to {url}.".format(
58-
key=key, value=value, url=request.url
56+
if should_propagate_trace(hub, str(request.url)):
57+
for key, value in hub.iter_trace_propagation_headers():
58+
logger.debug(
59+
"[Tracing] Adding `{key}` header {value} to outgoing request to {url}.".format(
60+
key=key, value=value, url=request.url
61+
)
5962
)
60-
)
61-
request.headers[key] = value
63+
request.headers[key] = value
64+
6265
rv = real_send(self, request, **kwargs)
6366

6467
span.set_data("status_code", rv.status_code)
@@ -91,13 +94,15 @@ async def send(self, request, **kwargs):
9194
span.set_data("http.query", parsed_url.query)
9295
span.set_data("http.fragment", parsed_url.fragment)
9396

94-
for key, value in hub.iter_trace_propagation_headers():
95-
logger.debug(
96-
"[Tracing] Adding `{key}` header {value} to outgoing request to {url}.".format(
97-
key=key, value=value, url=request.url
97+
if should_propagate_trace(hub, str(request.url)):
98+
for key, value in hub.iter_trace_propagation_headers():
99+
logger.debug(
100+
"[Tracing] Adding `{key}` header {value} to outgoing request to {url}.".format(
101+
key=key, value=value, url=request.url
102+
)
98103
)
99-
)
100-
request.headers[key] = value
104+
request.headers[key] = value
105+
101106
rv = await real_send(self, request, **kwargs)
102107

103108
span.set_data("status_code", rv.status_code)

sentry_sdk/integrations/stdlib.py

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
from sentry_sdk.hub import Hub
88
from sentry_sdk.integrations import Integration
99
from sentry_sdk.scope import add_global_event_processor
10-
from sentry_sdk.tracing_utils import EnvironHeaders
10+
from sentry_sdk.tracing_utils import EnvironHeaders, should_propagate_trace
1111
from sentry_sdk.utils import (
1212
capture_internal_exceptions,
1313
logger,
@@ -98,13 +98,14 @@ def putrequest(self, method, url, *args, **kwargs):
9898

9999
rv = real_putrequest(self, method, url, *args, **kwargs)
100100

101-
for key, value in hub.iter_trace_propagation_headers(span):
102-
logger.debug(
103-
"[Tracing] Adding `{key}` header {value} to outgoing request to {real_url}.".format(
104-
key=key, value=value, real_url=real_url
101+
if should_propagate_trace(hub, real_url):
102+
for key, value in hub.iter_trace_propagation_headers(span):
103+
logger.debug(
104+
"[Tracing] Adding `{key}` header {value} to outgoing request to {real_url}.".format(
105+
key=key, value=value, real_url=real_url
106+
)
105107
)
106-
)
107-
self.putheader(key, value)
108+
self.putheader(key, value)
108109

109110
self._sentrysdk_span = span
110111

sentry_sdk/tracing_utils.py

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,10 +27,10 @@
2727
if MYPY:
2828
import typing
2929

30-
from typing import Generator
31-
from typing import Optional
3230
from typing import Any
3331
from typing import Dict
32+
from typing import Generator
33+
from typing import Optional
3434
from typing import Union
3535

3636

@@ -376,6 +376,25 @@ def serialize(self, include_third_party=False):
376376
return ",".join(items)
377377

378378

379+
def should_propagate_trace(hub, url):
380+
# type: (sentry_sdk.Hub, str) -> bool
381+
"""
382+
Returns True if url matches trace_propagation_targets configured in the given hub. Otherwise, returns False.
383+
"""
384+
client = hub.client # type: Any
385+
trace_propagation_targets = client.options["trace_propagation_targets"]
386+
387+
if trace_propagation_targets is None:
388+
return False
389+
390+
for target in trace_propagation_targets:
391+
matched = re.search(target, url)
392+
if matched:
393+
return True
394+
395+
return False
396+
397+
379398
# Circular imports
380399
from sentry_sdk.tracing import LOW_QUALITY_TRANSACTION_SOURCES
381400

tests/integrations/httpx/test_httpx.py

Lines changed: 144 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import responses
66

77
from sentry_sdk import capture_message, start_transaction
8+
from sentry_sdk.consts import MATCH_ALL
89
from sentry_sdk.integrations.httpx import HttpxIntegration
910

1011

@@ -81,3 +82,146 @@ def test_outgoing_trace_headers(sentry_init, httpx_client):
8182
parent_span_id=request_span.span_id,
8283
sampled=1,
8384
)
85+
86+
87+
@pytest.mark.parametrize(
88+
"httpx_client,trace_propagation_targets,url,trace_propagated",
89+
[
90+
[
91+
httpx.Client(),
92+
None,
93+
"https://example.com/",
94+
False,
95+
],
96+
[
97+
httpx.Client(),
98+
[],
99+
"https://example.com/",
100+
False,
101+
],
102+
[
103+
httpx.Client(),
104+
[MATCH_ALL],
105+
"https://example.com/",
106+
True,
107+
],
108+
[
109+
httpx.Client(),
110+
["https://example.com/"],
111+
"https://example.com/",
112+
True,
113+
],
114+
[
115+
httpx.Client(),
116+
["https://example.com/"],
117+
"https://example.com",
118+
False,
119+
],
120+
[
121+
httpx.Client(),
122+
["https://example.com"],
123+
"https://example.com",
124+
True,
125+
],
126+
[
127+
httpx.Client(),
128+
["https://example.com", r"https?:\/\/[\w\-]+(\.[\w\-]+)+\.net"],
129+
"https://example.net",
130+
False,
131+
],
132+
[
133+
httpx.Client(),
134+
["https://example.com", r"https?:\/\/[\w\-]+(\.[\w\-]+)+\.net"],
135+
"https://good.example.net",
136+
True,
137+
],
138+
[
139+
httpx.Client(),
140+
["https://example.com", r"https?:\/\/[\w\-]+(\.[\w\-]+)+\.net"],
141+
"https://good.example.net/some/thing",
142+
True,
143+
],
144+
[
145+
httpx.AsyncClient(),
146+
None,
147+
"https://example.com/",
148+
False,
149+
],
150+
[
151+
httpx.AsyncClient(),
152+
[],
153+
"https://example.com/",
154+
False,
155+
],
156+
[
157+
httpx.AsyncClient(),
158+
[MATCH_ALL],
159+
"https://example.com/",
160+
True,
161+
],
162+
[
163+
httpx.AsyncClient(),
164+
["https://example.com/"],
165+
"https://example.com/",
166+
True,
167+
],
168+
[
169+
httpx.AsyncClient(),
170+
["https://example.com/"],
171+
"https://example.com",
172+
False,
173+
],
174+
[
175+
httpx.AsyncClient(),
176+
["https://example.com"],
177+
"https://example.com",
178+
True,
179+
],
180+
[
181+
httpx.AsyncClient(),
182+
["https://example.com", r"https?:\/\/[\w\-]+(\.[\w\-]+)+\.net"],
183+
"https://example.net",
184+
False,
185+
],
186+
[
187+
httpx.AsyncClient(),
188+
["https://example.com", r"https?:\/\/[\w\-]+(\.[\w\-]+)+\.net"],
189+
"https://good.example.net",
190+
True,
191+
],
192+
[
193+
httpx.AsyncClient(),
194+
["https://example.com", r"https?:\/\/[\w\-]+(\.[\w\-]+)+\.net"],
195+
"https://good.example.net/some/thing",
196+
True,
197+
],
198+
],
199+
)
200+
def test_option_trace_propagation_targets(
201+
sentry_init,
202+
httpx_client,
203+
httpx_mock, # this comes from pytest-httpx
204+
trace_propagation_targets,
205+
url,
206+
trace_propagated,
207+
):
208+
httpx_mock.add_response()
209+
210+
sentry_init(
211+
release="test",
212+
trace_propagation_targets=trace_propagation_targets,
213+
traces_sample_rate=1.0,
214+
integrations=[HttpxIntegration()],
215+
)
216+
217+
if asyncio.iscoroutinefunction(httpx_client.get):
218+
asyncio.get_event_loop().run_until_complete(httpx_client.get(url))
219+
else:
220+
httpx_client.get(url)
221+
222+
request_headers = httpx_mock.get_request().headers
223+
224+
if trace_propagated:
225+
assert "sentry-trace" in request_headers
226+
else:
227+
assert "sentry-trace" not in request_headers

0 commit comments

Comments
 (0)