Skip to content

Commit ccda603

Browse files
authored
Minor optimization: move tweaking of ASGI send/receive span level (#1629)
1 parent 2ec0a54 commit ccda603

File tree

3 files changed

+41
-34
lines changed

3 files changed

+41
-34
lines changed

.claude/settings.local.json

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
{
2+
"permissions": {
3+
"allow": [
4+
"Bash(pytest:*)"
5+
]
6+
}
7+
}

logfire/_internal/exporters/processor_wrapper.py

Lines changed: 0 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -67,14 +67,6 @@ class MainSpanProcessorWrapper(WrapperSpanProcessor):
6767

6868
scrubber: BaseScrubber
6969

70-
def on_start(
71-
self,
72-
span: Span,
73-
parent_context: context.Context | None = None,
74-
) -> None:
75-
_set_log_level_on_asgi_send_receive_spans(span)
76-
super().on_start(span, parent_context)
77-
7870
def on_end(self, span: ReadableSpan) -> None:
7971
with handle_internal_errors:
8072
span_dict = span_to_dict(span)
@@ -119,16 +111,6 @@ def _set_error_level_and_status(span: ReadableSpanDict) -> None:
119111
span['attributes'] = {**attributes, **log_level_attributes('warning')}
120112

121113

122-
def _set_log_level_on_asgi_send_receive_spans(span: Span) -> None:
123-
"""Set the log level of ASGI send/receive spans to debug.
124-
125-
If a span doesn't have a level set, it defaults to 'info'. This is too high for ASGI send/receive spans,
126-
which are generated for every request and are not particularly interesting.
127-
"""
128-
if _is_asgi_send_receive_span(span.name, span.instrumentation_scope):
129-
span.set_attributes(log_level_attributes('debug'))
130-
131-
132114
def _tweak_sqlalchemy_connect_spans(span: ReadableSpanDict) -> None:
133115
# Set the sqlalchemy 'connect' span to debug level so that it's hidden by default.
134116
# https://pydanticlogfire.slack.com/archives/C06EDRBSAH3/p1720205732316029

logfire/_internal/integrations/asgi.py

Lines changed: 34 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,11 @@
66
from opentelemetry.context import Context
77
from opentelemetry.instrumentation.asgi import OpenTelemetryMiddleware
88
from opentelemetry.sdk.trace import Tracer as SDKTracer
9-
from opentelemetry.trace import NonRecordingSpan, Span, Tracer, TracerProvider
9+
from opentelemetry.trace import NonRecordingSpan, SpanKind, Tracer, TracerProvider
1010
from opentelemetry.trace.propagation import get_current_span
11+
from opentelemetry.util import types as otel_types
1112

13+
from logfire._internal.constants import log_level_attributes
1214
from logfire._internal.utils import is_asgi_send_receive_span_name, maybe_capture_server_headers
1315

1416
if TYPE_CHECKING:
@@ -41,35 +43,51 @@ class ASGIInstrumentKwargs(TypedDict, total=False):
4143

4244

4345
def tweak_asgi_spans_tracer_provider(logfire_instance: Logfire, record_send_receive: bool) -> TracerProvider:
44-
"""If record_send_receive is False, return a TracerProvider that skips spans for ASGI send and receive events."""
46+
"""Return a TracerProvider that customizes ASGI send/receive spans.
47+
48+
If record_send_receive is False, spans are filtered out.
49+
If record_send_receive is True, spans are created with debug log level.
50+
"""
4551
tracer_provider = logfire_instance.config.get_tracer_provider()
46-
if record_send_receive:
47-
return tracer_provider
48-
else:
49-
return TweakAsgiTracerProvider(tracer_provider)
52+
return TweakAsgiTracerProvider(tracer_provider, record_send_receive)
5053

5154

5255
@dataclass
5356
class TweakAsgiTracerProvider(TracerProvider):
5457
tracer_provider: TracerProvider
58+
record_send_receive: bool
5559

5660
def get_tracer(self, *args: Any, **kwargs: Any) -> Tracer:
57-
return TweakAsgiSpansTracer(self.tracer_provider.get_tracer(*args, **kwargs))
61+
return TweakAsgiSpansTracer(self.tracer_provider.get_tracer(*args, **kwargs), self.record_send_receive)
5862

5963

6064
@dataclass
6165
class TweakAsgiSpansTracer(Tracer):
6266
tracer: Tracer
63-
64-
def start_span(self, name: str, context: Context | None = None, *args: Any, **kwargs: Any) -> Span:
67+
record_send_receive: bool
68+
69+
def start_span(
70+
self,
71+
name: str,
72+
context: Context | None = None,
73+
kind: SpanKind = SpanKind.INTERNAL,
74+
attributes: otel_types.Attributes = None,
75+
*args: Any,
76+
**kwargs: Any,
77+
) -> Span:
6578
if is_asgi_send_receive_span_name(name):
66-
# These are the noisy spans we want to skip.
67-
# Create a no-op span with the same SpanContext as the current span.
68-
# This means that any spans created within will have the current span as their parent,
69-
# as if this span didn't exist at all.
70-
return NonRecordingSpan(get_current_span(context).get_span_context())
71-
72-
return self.tracer.start_span(name, context, *args, **kwargs)
79+
if not self.record_send_receive:
80+
# These are the noisy spans we want to skip.
81+
# Create a no-op span with the same SpanContext as the current span.
82+
# This means that any spans created within will have the current span as their parent,
83+
# as if this span didn't exist at all.
84+
return NonRecordingSpan(get_current_span(context).get_span_context())
85+
86+
# If we're recording send/receive spans, set the log level to debug
87+
attributes = {**(attributes or {}), **log_level_attributes('debug')}
88+
return self.tracer.start_span(name, context, kind, attributes, *args, **kwargs)
89+
90+
return self.tracer.start_span(name, context, kind, attributes, *args, **kwargs)
7391

7492
# This means that `with start_as_current_span(...):`
7593
# is roughly equivalent to `with use_span(start_span(...)):`

0 commit comments

Comments
 (0)