Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

chore: NoneSpan #9549

Closed
wants to merge 6 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions ddtrace/_trace/span.py
Original file line number Diff line number Diff line change
Expand Up @@ -660,6 +660,22 @@ def __repr__(self):
)


class NoneSpan(Span):
def __init__(self):
super(NoneSpan, self).__init__("NoneSpan", trace_id=0, span_id=0, parent_id=0, start=0)

def __bool__(self):
return False

@property
def sampled(self):
return True

@sampled.setter
def sampled(self, value):
pass


def _is_top_level(span):
# type: (Span) -> bool
"""Return whether the span is a "top level" span.
Expand Down
13 changes: 8 additions & 5 deletions ddtrace/_trace/tracer.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
from ddtrace._trace.processor import TraceSamplingProcessor
from ddtrace._trace.processor import TraceTagsProcessor
from ddtrace._trace.provider import DefaultContextProvider
from ddtrace._trace.span import NoneSpan
from ddtrace._trace.span import Span
from ddtrace.appsec._constants import APPSEC
from ddtrace.constants import ENV_KEY
Expand Down Expand Up @@ -941,7 +942,7 @@ def trace(
span_api=span_api,
)

def current_root_span(self) -> Optional[Span]:
def current_root_span(self) -> Span:
"""Returns the root span of the current execution.

This is useful for attaching information related to the trace as a
Expand All @@ -956,19 +957,21 @@ def current_root_span(self) -> Optional[Span]:
root_span.set_tag('host', '127.0.0.1')
"""
span = self.current_span()
if span is None:
return None
if isinstance(span, NoneSpan):
return span
if span._local_root is None:
return NoneSpan()
return span._local_root

def current_span(self) -> Optional[Span]:
def current_span(self) -> Span:
"""Return the active span in the current execution context.

Note that there may be an active span represented by a context object
(like from a distributed trace) which will not be returned by this
method.
"""
active = self.context_provider.active()
return active if isinstance(active, Span) else None
return active if isinstance(active, Span) else NoneSpan()

@property
def agent_trace_url(self) -> Optional[str]:
Expand Down
3 changes: 2 additions & 1 deletion ddtrace/appsec/_exploit_prevention/stack_traces.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
from typing import List
from typing import Optional

from ddtrace._trace.span import NoneSpan
from ddtrace._trace.span import Span
from ddtrace.appsec._constants import EXPLOIT_PREVENTION
from ddtrace.settings.asm import config as asm_config
Expand All @@ -29,7 +30,7 @@ def report_stack(
return None
if span is None:
span = ddtrace.tracer.current_span()
if span is None or stack_id is None:
if isinstance(span, NoneSpan) or stack_id is None:
return None
root_span = span._local_root or span
appsec_traces = root_span.get_struct_tag(EXPLOIT_PREVENTION.STACK_TRACES) or {}
Expand Down
11 changes: 6 additions & 5 deletions ddtrace/appsec/_trace_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

from ddtrace import Tracer
from ddtrace import constants
from ddtrace._trace.span import NoneSpan
from ddtrace._trace.span import Span
from ddtrace.appsec import _asm_request_context
from ddtrace.appsec._constants import APPSEC
Expand Down Expand Up @@ -44,7 +45,7 @@ def _track_user_login_common(
) -> Optional[Span]:
if span is None:
span = tracer.current_root_span()
if span:
if not isinstance(span, NoneSpan):
success_str = "success" if success else "failure"
tag_prefix = "%s.%s" % (APPSEC.USER_LOGIN_EVENT_PREFIX, success_str)

Expand Down Expand Up @@ -173,7 +174,7 @@ def track_user_signup_event(
tracer: Tracer, user_id: str, success: bool, login_events_mode: str = LOGIN_EVENTS_MODE.SDK
) -> None:
span = tracer.current_root_span()
if span:
if not isinstance(span, NoneSpan):
success_str = "true" if success else "false"
span.set_tag_str(APPSEC.USER_SIGNUP_EVENT, success_str)
span.set_tag_str(user.ID, user_id)
Expand Down Expand Up @@ -212,7 +213,7 @@ def track_custom_event(tracer: Tracer, event_name: str, metadata: dict) -> None:
return

span = tracer.current_root_span()
if not span:
if isinstance(span, NoneSpan):
log.warning(
"No root span in the current execution. Skipping track_custom_event tags. "
"See https://docs.datadoghq.com/security_platform/application_security"
Expand Down Expand Up @@ -249,7 +250,7 @@ def should_block_user(tracer: Tracer, userid: str) -> bool:

# Early check to avoid calling the WAF if the request is already blocked
span = tracer.current_root_span()
if not span:
if isinstance(span, NoneSpan):
log.warning(
"No root span in the current execution. should_block_user returning False"
"See https://docs.datadoghq.com/security_platform/application_security"
Expand Down Expand Up @@ -293,7 +294,7 @@ def block_request_if_user_blocked(tracer: Tracer, userid: str) -> None:

if should_block_user(tracer, userid):
span = tracer.current_root_span()
if span:
if not isinstance(span, NoneSpan):
span.set_tag_str(user.ID, str(userid))
_asm_request_context.block_request()

Expand Down
12 changes: 4 additions & 8 deletions ddtrace/contrib/aws_lambda/patch.py
Original file line number Diff line number Diff line change
Expand Up @@ -90,16 +90,12 @@ def _crash_flush(self, _, __):
self.crashed = True

root_span = tracer.current_root_span()
if root_span is not None:
root_span.error = 1
root_span.set_tag_str(ERROR_MSG, "Datadog detected an Impending Timeout")
root_span.set_tag_str(ERROR_TYPE, "Impending Timeout")
else:
log.warning("An impending timeout was reached, but no root span was found. No error will be tagged.")
root_span.error = 1
root_span.set_tag_str(ERROR_MSG, "Datadog detected an Impending Timeout")
root_span.set_tag_str(ERROR_TYPE, "Impending Timeout")

current_span = tracer.current_span()
if current_span is not None:
current_span.finish_with_ancestors()
current_span.finish_with_ancestors()

def _remove_alarm_signal(self):
"""Removes the handler set for the signal `SIGALRM`."""
Expand Down
3 changes: 2 additions & 1 deletion ddtrace/debugging/_signal/tracing.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import attr

import ddtrace
from ddtrace._trace.span import NoneSpan
from ddtrace._trace.span import Span
from ddtrace.constants import ORIGIN_KEY
from ddtrace.debugging._expressions import DDExpressionEvaluationError
Expand Down Expand Up @@ -89,7 +90,7 @@ def _decorate_span(self, _locals: t.Dict[str, t.Any]) -> None:
log.error("Invalid target span for span decoration: %s", probe.target_span)
return

if span is not None:
if not isinstance(span, NoneSpan):
log.debug("Decorating span %r according to span decoration probe %r", span, probe)
for d in probe.decorations:
try:
Expand Down
3 changes: 2 additions & 1 deletion ddtrace/llmobs/_llmobs.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
from ddtrace import Span
from ddtrace import config
from ddtrace import patch
from ddtrace._trace.span import NoneSpan
from ddtrace.ext import SpanTypes
from ddtrace.internal import atexit
from ddtrace.internal import telemetry
Expand Down Expand Up @@ -239,7 +240,7 @@ def export_span(cls, span: Optional[Span] = None) -> Optional[ExportedLLMObsSpan
log.warning("Failed to export span. Span must be a valid Span object.")
return None
span = cls._instance.tracer.current_span()
if span is None:
if span is None or isinstance(span, NoneSpan): # Redundant check as current_span() should never return None now
log.warning("No span provided and no active LLMObs-generated span found.")
return None
if span.span_type != SpanTypes.LLM:
Expand Down
3 changes: 2 additions & 1 deletion ddtrace/llmobs/_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import ddtrace
from ddtrace import Span
from ddtrace import config
from ddtrace._trace.span import NoneSpan
from ddtrace.ext import SpanTypes
from ddtrace.internal.logger import get_logger
from ddtrace.llmobs._constants import LANGCHAIN_APM_SPAN_NAME
Expand Down Expand Up @@ -74,7 +75,7 @@ def _get_session_id(span: Span) -> str:
def _inject_llmobs_parent_id(span_context):
"""Inject the LLMObs parent ID into the span context for reconnecting distributed LLMObs traces."""
span = ddtrace.tracer.current_span()
if span is None:
if not isinstance(span, NoneSpan):
log.warning("No active span to inject LLMObs parent ID info.")
return
if span.context is not span_context:
Expand Down
5 changes: 3 additions & 2 deletions ddtrace/opentracer/tracer.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
import ddtrace
from ddtrace import Tracer as DatadogTracer
from ddtrace._trace.context import Context as DatadogContext # noqa:F401
from ddtrace._trace.span import NoneSpan
from ddtrace._trace.span import Span as DatadogSpan
from ddtrace.internal.constants import SPAN_API_OPENTRACING
from ddtrace.internal.utils.config import get_application_name
Expand Down Expand Up @@ -274,7 +275,7 @@ def start_span(
dd_parent = dd_parent_span
else:
dd_parent = active_dd_parent
elif ot_parent is not None and isinstance(ot_parent, Span):
elif ot_parent and isinstance(ot_parent, Span) and not isinstance(ot_parent, NoneSpan):
# a span is given to use as a parent
ot_parent_context = ot_parent.context
dd_parent = ot_parent._dd_span
Expand Down Expand Up @@ -327,7 +328,7 @@ def active_span(self):
else:
dd_span = self._dd_tracer.current_span()
ot_span = None # type: Optional[Span]
if dd_span:
if not isinstance(dd_span, NoneSpan):
ot_span = Span(self, None, dd_span.name)
ot_span._associate_dd_span(dd_span)
return ot_span
Expand Down
3 changes: 2 additions & 1 deletion ddtrace/propagation/http.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
from typing import cast # noqa:F401

import ddtrace
from ddtrace._trace.span import NoneSpan
from ddtrace._trace.span import Span # noqa:F401


Expand Down Expand Up @@ -992,7 +993,7 @@ def parent_call():
else:
root_span = ddtrace.tracer.current_root_span()

if root_span is not None and root_span.context.sampling_priority is None:
if root_span and not isinstance(root_span, NoneSpan) and root_span.context.sampling_priority is None:
ddtrace.tracer.sample(root_span)
else:
log.error("ddtrace.tracer.sample is not available, unable to sample span.")
Expand Down
6 changes: 4 additions & 2 deletions scripts/profiles/flask-simple/app/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,16 +12,18 @@
def entry_point():
if environ.get("DUPLICATE_TAGS_SCENARIO", None):
from ddtrace import tracer
from ddtrace._trace.span import NoneSpan

span = tracer.current_span()
if span:
if not isinstance(span, NoneSpan):
for _ in range(100):
span.set_tag(_, "a" * 100)
elif environ.get("UNIQUE_TAGS_SCENARIO", None):
from ddtrace import tracer
from ddtrace._trace.span import NoneSpan

span = tracer.current_span()
if span:
if not isinstance(span, NoneSpan):
for num in range(100):
span.set_tag(str(num), "".join(random.choices(string.ascii_letters, k=100)))

Expand Down
27 changes: 14 additions & 13 deletions tests/tracer/test_tracer.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@

import ddtrace
from ddtrace._trace.context import Context
from ddtrace._trace.span import NoneSpan
from ddtrace._trace.span import _is_top_level
from ddtrace._trace.tracer import Tracer
from ddtrace.constants import AUTO_KEEP
Expand Down Expand Up @@ -1596,20 +1597,20 @@ def test_get_report_hostname_default(get_hostname, tracer, test_spans):

def test_non_active_span(tracer, test_spans):
with tracer.start_span("test", activate=False):
assert tracer.current_span() is None
assert tracer.current_root_span() is None
assert tracer.current_span() is None
assert tracer.current_root_span() is None
assert isinstance(tracer.current_span(), NoneSpan)
assert isinstance(tracer.current_root_span(), NoneSpan)
assert isinstance(tracer.current_span(), NoneSpan)
assert isinstance(tracer.current_root_span(), NoneSpan)
traces = test_spans.pop_traces()
assert len(traces) == 1
assert len(traces[0]) == 1

with tracer.start_span("test1", activate=False):
with tracer.start_span("test2", activate=False):
assert tracer.current_span() is None
assert tracer.current_root_span() is None
assert tracer.current_span() is None
assert tracer.current_root_span() is None
assert isinstance(tracer.current_span(), NoneSpan)
assert isinstance(tracer.current_root_span(), NoneSpan)
assert isinstance(tracer.current_span(), NoneSpan)
assert isinstance(tracer.current_root_span(), NoneSpan)
traces = test_spans.pop_traces()
assert len(traces) == 2

Expand Down Expand Up @@ -1758,16 +1759,16 @@ def test_closing_other_context_spans_single_span(tracer, test_spans):
"""

def _target(span):
assert tracer.current_span() is None
assert isinstance(tracer.current_span(), NoneSpan)
span.finish()
assert tracer.current_span() is None
assert isinstance(tracer.current_span(), NoneSpan)

span = tracer.trace("main thread")
assert tracer.current_span() is span
t1 = threading.Thread(target=_target, args=(span,))
t1.start()
t1.join(60)
assert tracer.current_span() is None
assert isinstance(tracer.current_span(), NoneSpan)

spans = test_spans.pop()
assert len(spans) == 1
Expand All @@ -1780,9 +1781,9 @@ def test_closing_other_context_spans_multi_spans(tracer, test_spans):
"""

def _target(span):
assert tracer.current_span() is None
assert isinstance(tracer.current_span(), NoneSpan)
span.finish()
assert tracer.current_span() is None
assert isinstance(tracer.current_span(), NoneSpan)

root = tracer.trace("root span")
span = tracer.trace("child span")
Expand Down
Loading