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

Sign up
Appearance settings

Commit 8965821

Browse files
Add external_propagation_context support (#5051)
If we are on an external tracing system like otel, we allow registering a new source of `trace_id/span_id` that takes precedence over the scope's propagation_context. * Also reworked logs and metrics to use `get_trace_context` * Cleaned up handling of `get_trace_context` that is still messy but a bit more clearer now regarding which underlying `propagation_context` is used
1 parent 2397b15 commit 8965821

File tree

7 files changed

+126
-43
lines changed

7 files changed

+126
-43
lines changed

‎sentry_sdk/client.py‎

Lines changed: 19 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -928,17 +928,18 @@ def _capture_log(self, log):
928928
if release is not None and "sentry.release" not in log["attributes"]:
929929
log["attributes"]["sentry.release"] = release
930930

931-
span = current_scope.span
932-
if span is not None and "sentry.trace.parent_span_id" not in log["attributes"]:
933-
log["attributes"]["sentry.trace.parent_span_id"] = span.span_id
934-
935-
if log.get("trace_id") is None:
936-
transaction = current_scope.transaction
937-
propagation_context = isolation_scope.get_active_propagation_context()
938-
if transaction is not None:
939-
log["trace_id"] = transaction.trace_id
940-
elif propagation_context is not None:
941-
log["trace_id"] = propagation_context.trace_id
931+
trace_context = current_scope.get_trace_context()
932+
trace_id = trace_context.get("trace_id")
933+
span_id = trace_context.get("span_id")
934+
935+
if trace_id is not None and log.get("trace_id") is None:
936+
log["trace_id"] = trace_id
937+
938+
if (
939+
span_id is not None
940+
and "sentry.trace.parent_span_id" not in log["attributes"]
941+
):
942+
log["attributes"]["sentry.trace.parent_span_id"] = span_id
942943

943944
# The user, if present, is always set on the isolation scope.
944945
if isolation_scope._user is not None:
@@ -977,6 +978,7 @@ def _capture_metric(self, metric):
977978
if metric is None:
978979
return
979980

981+
current_scope = sentry_sdk.get_current_scope()
980982
isolation_scope = sentry_sdk.get_isolation_scope()
981983

982984
metric["attributes"]["sentry.sdk.name"] = SDK_INFO["name"]
@@ -990,16 +992,13 @@ def _capture_metric(self, metric):
990992
if release is not None and "sentry.release" not in metric["attributes"]:
991993
metric["attributes"]["sentry.release"] = release
992994

993-
span = sentry_sdk.get_current_span()
994-
metric["trace_id"] = "00000000-0000-0000-0000-000000000000"
995+
trace_context = current_scope.get_trace_context()
996+
trace_id = trace_context.get("trace_id")
997+
span_id = trace_context.get("span_id")
995998

996-
if span:
997-
metric["trace_id"] = span.trace_id
998-
metric["span_id"] = span.span_id
999-
else:
1000-
propagation_context = isolation_scope.get_active_propagation_context()
1001-
if propagation_context and propagation_context.trace_id:
1002-
metric["trace_id"] = propagation_context.trace_id
999+
metric["trace_id"] = trace_id or "00000000-0000-0000-0000-000000000000"
1000+
if span_id is not None:
1001+
metric["span_id"] = span_id
10031002

10041003
if isolation_scope._user is not None:
10051004
for metric_attribute, user_attribute in (

‎sentry_sdk/scope.py‎

Lines changed: 42 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,10 @@
107107

108108
global_event_processors = [] # type: List[EventProcessor]
109109

110+
# A function returning a (trace_id, span_id) tuple
111+
# from an external tracing source (such as otel)
112+
_external_propagation_context_fn = None # type: Optional[Callable[[], Optional[Tuple[str, str]]]]
113+
110114

111115
class ScopeType(Enum):
112116
CURRENT = "current"
@@ -142,6 +146,25 @@ def add_global_event_processor(processor):
142146
global_event_processors.append(processor)
143147

144148

149+
def register_external_propagation_context(fn):
150+
# type: (Callable[[], Optional[Tuple[str, str]]]) -> None
151+
global _external_propagation_context_fn
152+
_external_propagation_context_fn = fn
153+
154+
155+
def remove_external_propagation_context():
156+
# type: () -> None
157+
global _external_propagation_context_fn
158+
_external_propagation_context_fn = None
159+
160+
161+
def get_external_propagation_context():
162+
# type: () -> Optional[Tuple[str, str]]
163+
return (
164+
_external_propagation_context_fn() if _external_propagation_context_fn else None
165+
)
166+
167+
145168
def _attr_setter(fn):
146169
# type: (Any) -> Any
147170
return property(fset=fn, doc=fn.__doc__)
@@ -562,21 +585,29 @@ def get_baggage(self, *args, **kwargs):
562585
return self.get_isolation_scope().get_baggage()
563586

564587
def get_trace_context(self):
565-
# type: () -> Any
588+
# type: () -> Dict[str, Any]
566589
"""
567590
Returns the Sentry "trace" context from the Propagation Context.
568591
"""
569-
if self._propagation_contextis None:
570-
return None
592+
if has_tracing_enabled(self.get_client().options) andself._spanisnot None:
593+
return self._span.get_trace_context()
571594

572-
trace_context = {
573-
"trace_id": self._propagation_context.trace_id,
574-
"span_id": self._propagation_context.span_id,
575-
"parent_span_id": self._propagation_context.parent_span_id,
576-
"dynamic_sampling_context": self.get_dynamic_sampling_context(),
577-
} # type: Dict[str, Any]
595+
# if we are tracing externally (otel), those values take precedence
596+
external_propagation_context = get_external_propagation_context()
597+
if external_propagation_context:
598+
trace_id, span_id = external_propagation_context
599+
return {"trace_id": trace_id, "span_id": span_id}
578600

579-
return trace_context
601+
propagation_context = self.get_active_propagation_context()
602+
if propagation_context is None:
603+
return {}
604+
605+
return {
606+
"trace_id": propagation_context.trace_id,
607+
"span_id": propagation_context.span_id,
608+
"parent_span_id": propagation_context.parent_span_id,
609+
"dynamic_sampling_context": self.get_dynamic_sampling_context(),
610+
}
580611

581612
def trace_propagation_meta(self, *args, **kwargs):
582613
# type: (*Any, **Any) -> str
@@ -1438,10 +1469,7 @@ def _apply_contexts_to_event(self, event, hint, options):
14381469

14391470
# Add "trace" context
14401471
if contexts.get("trace") is None:
1441-
if has_tracing_enabled(options) and self._span is not None:
1442-
contexts["trace"] = self._span.get_trace_context()
1443-
else:
1444-
contexts["trace"] = self.get_trace_context()
1472+
contexts["trace"] = self.get_trace_context()
14451473

14461474
def _apply_flags_to_event(self, event, hint, options):
14471475
# type: (Event, Hint, Optional[Dict[str, Any]]) -> None

‎tests/integrations/logging/test_logging.py‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -478,6 +478,10 @@ def test_logger_with_all_attributes(sentry_init, capture_envelopes):
478478

479479
assert attributes.pop("sentry.sdk.name").startswith("sentry.python")
480480

481+
assert "sentry.trace.parent_span_id" in attributes
482+
assert isinstance(attributes["sentry.trace.parent_span_id"], str)
483+
del attributes["sentry.trace.parent_span_id"]
484+
481485
# Assert on the remaining non-dynamic attributes.
482486
assert attributes == {
483487
"foo": "bar",

‎tests/integrations/loguru/test_loguru.py‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -458,6 +458,10 @@ def test_logger_with_all_attributes(
458458

459459
assert attributes.pop("sentry.sdk.name").startswith("sentry.python")
460460

461+
assert "sentry.trace.parent_span_id" in attributes
462+
assert isinstance(attributes["sentry.trace.parent_span_id"], str)
463+
del attributes["sentry.trace.parent_span_id"]
464+
461465
# Assert on the remaining non-dynamic attributes.
462466
assert attributes == {
463467
"logger.name": "tests.integrations.loguru.test_loguru",

‎tests/test_logs.py‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -310,7 +310,7 @@ def test_logs_tied_to_transactions(sentry_init, capture_envelopes):
310310
"""
311311
Log messages are also tied to transactions.
312312
"""
313-
sentry_init(enable_logs=True)
313+
sentry_init(enable_logs=True, traces_sample_rate=1.0)
314314
envelopes = capture_envelopes()
315315

316316
with sentry_sdk.start_transaction(name="test-transaction") as trx:
@@ -326,7 +326,7 @@ def test_logs_tied_to_spans(sentry_init, capture_envelopes):
326326
"""
327327
Log messages are also tied to spans.
328328
"""
329-
sentry_init(enable_logs=True)
329+
sentry_init(enable_logs=True, traces_sample_rate=1.0)
330330
envelopes = capture_envelopes()
331331

332332
with sentry_sdk.start_transaction(name="test-transaction"):

‎tests/test_metrics.py‎

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -122,7 +122,7 @@ def test_metrics_with_span(sentry_init, capture_envelopes):
122122
sentry_init(traces_sample_rate=1.0)
123123
envelopes = capture_envelopes()
124124

125-
with sentry_sdk.start_transaction(op="test", name="test-span"):
125+
with sentry_sdk.start_transaction(op="test", name="test-span")astransaction:
126126
sentry_sdk.metrics.count("test.span.counter", 1)
127127

128128
get_client().flush()
@@ -131,24 +131,26 @@ def test_metrics_with_span(sentry_init, capture_envelopes):
131131
assert len(metrics) == 1
132132

133133
assert metrics[0]["trace_id"] is not None
134-
assert metrics[0]["trace_id"] !="00000000-0000-0000-0000-000000000000"
135-
assert metrics[0]["span_id"] isnotNone
134+
assert metrics[0]["trace_id"] ==transaction.trace_id
135+
assert metrics[0]["span_id"] ==transaction.span_id
136136

137137

138138
def test_metrics_tracing_without_performance(sentry_init, capture_envelopes):
139139
sentry_init()
140140
envelopes = capture_envelopes()
141141

142-
sentry_sdk.metrics.count("test.span.counter", 1)
142+
with sentry_sdk.isolation_scope() as isolation_scope:
143+
sentry_sdk.metrics.count("test.span.counter", 1)
143144

144145
get_client().flush()
145146

146147
metrics = envelopes_to_metrics(envelopes)
147148
assert len(metrics) == 1
148149

149-
assert metrics[0]["trace_id"] is not None
150-
assert metrics[0]["trace_id"] != "00000000-0000-0000-0000-000000000000"
151-
assert metrics[0]["span_id"] is None
150+
propagation_context = isolation_scope._propagation_context
151+
assert propagation_context is not None
152+
assert metrics[0]["trace_id"] == propagation_context.trace_id
153+
assert metrics[0]["span_id"] == propagation_context.span_id
152154

153155

154156
def test_metrics_before_send(sentry_init, capture_envelopes):

‎tests/test_scope.py‎

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@
1616
use_isolation_scope,
1717
use_scope,
1818
should_send_default_pii,
19+
register_external_propagation_context,
20+
remove_external_propagation_context,
1921
)
2022

2123

@@ -971,3 +973,47 @@ def test_handle_error_on_token_reset_isolation_scope(error_cls, scope_manager):
971973

972974
mock_capture.assert_called_once()
973975
mock_current_scope.reset.assert_called_once_with(mock_current_token)
976+
977+
978+
def test_trace_context_tracing(sentry_init):
979+
sentry_init(traces_sample_rate=1.0)
980+
981+
with sentry_sdk.start_transaction(name="trx") as transaction:
982+
with sentry_sdk.start_span(op="span1"):
983+
with sentry_sdk.start_span(op="span2") as span:
984+
trace_context = sentry_sdk.get_current_scope().get_trace_context()
985+
986+
assert trace_context["trace_id"] == transaction.trace_id
987+
assert trace_context["span_id"] == span.span_id
988+
assert trace_context["parent_span_id"] == span.parent_span_id
989+
assert "dynamic_sampling_context" in trace_context
990+
991+
992+
def test_trace_context_external_tracing(sentry_init):
993+
sentry_init()
994+
995+
def external_propagation_context():
996+
return ("trace_id_foo", "span_id_bar")
997+
998+
register_external_propagation_context(external_propagation_context)
999+
1000+
trace_context = sentry_sdk.get_current_scope().get_trace_context()
1001+
1002+
assert trace_context["trace_id"] == "trace_id_foo"
1003+
assert trace_context["span_id"] == "span_id_bar"
1004+
1005+
remove_external_propagation_context()
1006+
1007+
1008+
def test_trace_context_without_performance(sentry_init):
1009+
sentry_init()
1010+
1011+
with sentry_sdk.isolation_scope() as isolation_scope:
1012+
trace_context = sentry_sdk.get_current_scope().get_trace_context()
1013+
1014+
propagation_context = isolation_scope._propagation_context
1015+
assert propagation_context is not None
1016+
assert trace_context["trace_id"] == propagation_context.trace_id
1017+
assert trace_context["span_id"] == propagation_context.span_id
1018+
assert trace_context["parent_span_id"] == propagation_context.parent_span_id
1019+
assert "dynamic_sampling_context" in trace_context

0 commit comments

Comments
(0)

AltStyle によって変換されたページ (->オリジナル) /