Skip to content

Commit 678d1af

Browse files
committed
Add external_propagation_context support
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 678d1af

File tree

3 files changed

+58
-33
lines changed

3 files changed

+58
-33
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: 35 additions & 13 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,19 @@ 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 get_external_propagation_context():
156+
# type: () -> Optional[Tuple[str, str]]
157+
return (
158+
_external_propagation_context_fn() if _external_propagation_context_fn else None
159+
)
160+
161+
145162
def _attr_setter(fn):
146163
# type: (Any) -> Any
147164
return property(fset=fn, doc=fn.__doc__)
@@ -562,22 +579,30 @@ def get_baggage(self, *args, **kwargs):
562579
return self.get_isolation_scope().get_baggage()
563580

564581
def get_trace_context(self):
565-
# type: () -> Any
582+
# type: () -> Dict[str, Any]
566583
"""
567584
Returns the Sentry "trace" context from the Propagation Context.
568585
"""
569-
if self._propagation_context is None:
570-
return None
586+
if has_tracing_enabled(self.get_client().options) and self._span is not None:
587+
return self._span.get_trace_context()
588+
589+
# if we are tracing externally (otel), those values take precedence
590+
external_propagation_context = get_external_propagation_context()
591+
if external_propagation_context:
592+
trace_id, span_id = external_propagation_context
593+
return {"trace_id": trace_id, "span_id": span_id}
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,
595+
propagation_context = self.get_active_propagation_context()
596+
if propagation_context is None:
597+
return {}
598+
599+
return {
600+
"trace_id": propagation_context.trace_id,
601+
"span_id": propagation_context.span_id,
602+
"parent_span_id": propagation_context.parent_span_id,
576603
"dynamic_sampling_context": self.get_dynamic_sampling_context(),
577604
} # type: Dict[str, Any]
578605

579-
return trace_context
580-
581606
def trace_propagation_meta(self, *args, **kwargs):
582607
# type: (*Any, **Any) -> str
583608
"""
@@ -1438,10 +1463,7 @@ def _apply_contexts_to_event(self, event, hint, options):
14381463

14391464
# Add "trace" context
14401465
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()
1466+
contexts["trace"] = self.get_trace_context()
14451467

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

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",

0 commit comments

Comments
 (0)