open-telemetry / open-telemetry/opentelemetry-python-contrib

FastAPI: Context change in sync route does not appear in background tasks

Open
#3,586 3 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

bug
Dominant language
Python
Stars
1.1k
Forks
1.1k
Avg merge
4d 15h
Merged PRs (30d)
16

Description

Describe your environment

OS: Ubuntu
Python version: 3.11.0

Package version:
opentelemetry-api==1.34.1
opentelemetry-sdk==1.34.1
opentelemetry-instrumentation-fastapi==0.55b1

What happened?

When having a sync route in FastAPI and launching a background task, the changes done to the context do not appear in the context of the background task.

Steps to Reproduce
from fastapi import BackgroundTasks, FastAPI
from opentelemetry import context, trace
from opentelemetry.context import set_value
from opentelemetry.instrumentation.fastapi import FastAPIInstrumentor
from opentelemetry.sdk.resources import SERVICE_NAME, Resource
from opentelemetry.sdk.trace import TracerProvider
from opentelemetry.sdk.trace.export import BatchSpanProcessor, ConsoleSpanExporter

app = FastAPI()

# =========================================================================== #
# Optional: Bug is reproducible with or without setuping the resource, tracer
# provider and FastAPI instrumentor,
resource = Resource.create(attributes={SERVICE_NAME: "pricing-recommendation-engine"})

provider = TracerProvider(resource=resource)
trace.set_tracer_provider(provider)

# provider.add_span_processor(BatchSpanProcessor(ConsoleSpanExporter()))

FastAPIInstrumentor().instrument_app(app)
# =========================================================================== #


def my_background_task():
    print(context.get_current())


def add_test_value_to_context():
    ctx = context.get_current()
    ctx = set_value("test_key", "test_value", context=ctx)
    context.attach(ctx)


@app.get("/sync-endpoint")
def sync_endpoint(background_tasks: BackgroundTasks):
    print(context.get_current())
    add_test_value_to_context()
    print(context.get_current())
    background_tasks.add_task(my_background_task)
    return "Hello, World!"


@app.get("/async-endpoint")
async def async_endpoint(background_tasks: BackgroundTasks):
    print(context.get_current())
    add_test_value_to_context()
    print(context.get_current())
    background_tasks.add_task(my_background_task)
    return "Hello, World!"
uvicorn fastapi_context_bug:app

On endpoint /sync-endpoint, we get this:

{'current-span-b13884d6-004f-41b3-84b0-8704e8205ff6': _Span(name="GET /sync-endpoint", context=SpanContext(trace_id=0x3d1bdba09cbb92564db952045e92ab94, span_id=0x93ec8565cd311ed4, trace_flags=0x01, trace_state=[], is_remote=False))}
{'current-span-b13884d6-004f-41b3-84b0-8704e8205ff6': _Span(name="GET /sync-endpoint", context=SpanContext(trace_id=0x3d1bdba09cbb92564db952045e92ab94, span_id=0x93ec8565cd311ed4, trace_flags=0x01, trace_state=[], is_remote=False)), 'test_key': 'test_value'}
INFO:     127.0.0.1:45994 - "GET /sync-endpoint HTTP/1.1" 200 OK
{'current-span-b13884d6-004f-41b3-84b0-8704e8205ff6': _Span(name="GET /sync-endpoint", context=SpanContext(trace_id=0x3d1bdba09cbb92564db952045e92ab94, span_id=0x93ec8565cd311ed4, trace_flags=0x01, trace_state=[], is_remote=False))}

On endpoint /async-endpoint, we get this:

{'current-span-b13884d6-004f-41b3-84b0-8704e8205ff6': _Span(name="GET /async-endpoint", context=SpanContext(trace_id=0xf1009be126c6edf441c7f465fcc05e7e, span_id=0xfa1e818754376b97, trace_flags=0x01, trace_state=[], is_remote=False))}
{'current-span-b13884d6-004f-41b3-84b0-8704e8205ff6': _Span(name="GET /async-endpoint", context=SpanContext(trace_id=0xf1009be126c6edf441c7f465fcc05e7e, span_id=0xfa1e818754376b97, trace_flags=0x01, trace_state=[], is_remote=False)), 'test_key': 'test_value'}
INFO:     127.0.0.1:48510 - "GET /async-endpoint HTTP/1.1" 200 OK
{'current-span-b13884d6-004f-41b3-84b0-8704e8205ff6': _Span(name="GET /async-endpoint", context=SpanContext(trace_id=0xf1009be126c6edf441c7f465fcc05e7e, span_id=0xfa1e818754376b97, trace_flags=0x01, trace_state=[], is_remote=False)), 'test_key': 'test_value'}
Expected Result

The sync route should behave as the async route.

Actual Result

See above.

Additional context

No response

Would you like to implement a fix?

None

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start at FastAPIInstrumentor and trace how sync and async routes run background tasks and propagate context. Reproduce the difference between /sync-endpoint and /async-endpoint, then add coverage showing that the sync route preserves the changed context in its background task and run the relevant instrumentation tests.

Written by the indexing model from the issue text.

Assessment

Tech stack
fastapi, python
Domain
api, backend, observability
Issue type
Bug
Difficulty
3/5
Estimated time
1-2 days
Activity status
Quiet
Clarity
Mostly clear
Newbie friendliness
55/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.