open-telemetry / opentelemetry-python

OpenTelemetry Python API and SDK
https://opentelemetry.io
Apache License 2.0
1.66k stars 568 forks source link

trace.get_current_span doesn't provide active span under test #3999

Open mukund-ananthu opened 3 days ago

mukund-ananthu commented 3 days ago

Describe your environment

OS: Ubuntu Python version: 3.11.8 SDK version: opentelemetry-sdk=1.25.0

What happened?

Described in steps to reproduce

Steps to Reproduce

conftest.py

from opentelemetry.sdk.trace import TracerProvider
from opentelemetry.sdk.trace.export.in_memory_span_exporter import InMemorySpanExporter
from opentelemetry.sdk.trace.export import SimpleSpanProcessor
from opentelemetry import trace

import google.auth.credentials
import pytest

@pytest.fixture
def creds():
    """
    Provide test creds to unit tests so that they can run without them.
    """
    yield google.auth.credentials.AnonymousCredentials()

@pytest.fixture(scope="session", autouse=True)
def set_trace_provider():
    provider = TracerProvider()
    trace.set_tracer_provider(provider)

@pytest.fixture(scope="function")
def span_exporter():
    exporter = InMemorySpanExporter()
    processor = SimpleSpanProcessor(exporter)
    provider = trace.get_tracer_provider()
    provider.add_span_processor(processor)
    yield exporter

sample.py

from opentelemetry import trace

class Bar():
    def foo(self):
        tracer = trace.get_tracer("com.bar")
        with tracer.start_as_current_span(name="footrace", end_on_exit=False) as _:
            print("foo")

sample_test.py

import pytest
from sample import Bar

from opentelemetry.sdk.trace import TracerProvider
from opentelemetry.sdk.trace.export.in_memory_span_exporter import InMemorySpanExporter
from opentelemetry.sdk.trace.export import SimpleSpanProcessor
from opentelemetry import trace

def test_fo(span_exporter):
    b = Bar()
    b.foo()
    #spans = span_exporter.get_finished_spans()
    #assert len(spans) == 1    -> This fails because the span hasn't ended because end_on_exit=False

    span = trace.get_current_span()
    assert span is not None -> Succeeds, but the contents of the span doesn't match the active span
    # For example, the span.name is not footrace, but something else

Expected Result

Return the current active span

Actual Result

Returns some other span

Additional context

Understood that unfinished spans may not be exported, and hence may be unavailable, but open telemetry could provide a recommended way to test the values of active spans. This is required in scenarios where the spans are closed asynchronously in a different function / file altogether and we want to verify the contents of the active span in the unit tests of the function that creates it.

Would you like to implement a fix?

None

mariojonke commented 1 day ago

I think you are mixing up the started/ended state of a span with its activation state. A call to trace.get_current_span() will always return the current active span (or an invalid span if no span is currently activated). Spans are usually activated with Tracer.start_as_current_span (or Tracer.start_span and then some time later with the trace.use_span context manager) which keeps the span active in the scope the context manager is active.

In your example the tracer.start_as_current_span(name="footrace", end_on_exit=False) context manager in Bar.foo keeps the span running but ends its activation state right after print("foo"). So when trace.get_current_span() is then later called in test_fo the isn't ended yet but it also isn't the active span anymore. Instead you'll get an invalid span since there is no other span currently active.

mukund-ananthu commented 1 day ago

@xrmx My end goal here is to be able to verify the contents of the "footrace" span in test_foo(). How can I achieve this?