From 858cacdcfe9d702c9f7ef1ecde83b098c91b2891 Mon Sep 17 00:00:00 2001 From: Neel Shah Date: Wed, 18 Dec 2024 20:41:55 +0530 Subject: [PATCH] Implement span limits on span processor (#3881) --- sentry_sdk/integrations/arq.py | 2 -- sentry_sdk/integrations/logging.py | 7 ++++++- .../opentelemetry/span_processor.py | 19 ++++++++++++++++++- tests/tracing/test_misc.py | 19 +++++++++++++++++++ 4 files changed, 43 insertions(+), 4 deletions(-) diff --git a/sentry_sdk/integrations/arq.py b/sentry_sdk/integrations/arq.py index c26db4520c..0f42050cf2 100644 --- a/sentry_sdk/integrations/arq.py +++ b/sentry_sdk/integrations/arq.py @@ -1,7 +1,5 @@ import sys -from opentelemetry.trace.status import StatusCode - import sentry_sdk from sentry_sdk.consts import OP, SPANSTATUS from sentry_sdk.integrations import DidNotEnable, Integration diff --git a/sentry_sdk/integrations/logging.py b/sentry_sdk/integrations/logging.py index 314780cabd..52c56a8e60 100644 --- a/sentry_sdk/integrations/logging.py +++ b/sentry_sdk/integrations/logging.py @@ -40,7 +40,12 @@ # Note: Ignoring by logger name here is better than mucking with thread-locals. # We do not necessarily know whether thread-locals work 100% correctly in the user's environment. _IGNORED_LOGGERS = set( - ["sentry_sdk.errors", "urllib3.connectionpool", "urllib3.connection", "opentelemetry.*"] + [ + "sentry_sdk.errors", + "urllib3.connectionpool", + "urllib3.connection", + "opentelemetry.*", + ] ) diff --git a/sentry_sdk/integrations/opentelemetry/span_processor.py b/sentry_sdk/integrations/opentelemetry/span_processor.py index 0b4c3387df..42ad32a5ea 100644 --- a/sentry_sdk/integrations/opentelemetry/span_processor.py +++ b/sentry_sdk/integrations/opentelemetry/span_processor.py @@ -43,6 +43,9 @@ from sentry_sdk._types import Event +DEFAULT_MAX_SPANS = 1000 + + class SentrySpanProcessor(SpanProcessor): """ Converts OTel spans into Sentry spans so they can be sent to the Sentry backend. @@ -79,7 +82,7 @@ def on_end(self, span): # if have a root span ending, we build a transaction and send it self._flush_root_span(span) else: - self._children_spans[span.parent.span_id].append(span) + self._append_child_span(span) # TODO-neel-potel not sure we need a clear like JS def shutdown(self): @@ -150,6 +153,20 @@ def _flush_root_span(self, span): sentry_sdk.capture_event(transaction_event) + def _append_child_span(self, span): + # type: (ReadableSpan) -> None + if not span.parent: + return + + max_spans = ( + sentry_sdk.get_client().options["_experiments"].get("max_spans") + or DEFAULT_MAX_SPANS + ) + + children_spans = self._children_spans[span.parent.span_id] + if len(children_spans) < max_spans: + children_spans.append(span) + def _collect_children(self, span): # type: (ReadableSpan) -> List[ReadableSpan] if not span.context: diff --git a/tests/tracing/test_misc.py b/tests/tracing/test_misc.py index 0d12acc617..dc5754cfd2 100644 --- a/tests/tracing/test_misc.py +++ b/tests/tracing/test_misc.py @@ -8,6 +8,25 @@ from sentry_sdk.utils import Dsn +def test_span_trimming(sentry_init, capture_events): + sentry_init(traces_sample_rate=1.0, _experiments={"max_spans": 3}) + events = capture_events() + + with start_span(name="hi"): + for i in range(10): + with start_span(op="foo{}".format(i)): + pass + + (event,) = events + + assert len(event["spans"]) == 3 + + span1, span2, span3 = event["spans"] + assert span1["op"] == "foo0" + assert span2["op"] == "foo1" + assert span3["op"] == "foo2" + + def test_transaction_naming(sentry_init, capture_events): sentry_init(traces_sample_rate=1.0) events = capture_events()