open-telemetry / open-telemetry/opentelemetry-python

FastAPI instrumentation - Invalid TraceID/SpanID propagation, even with right traceparent information

Open
#4,440 0 comments 4 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

bug
Dominant language
Python
Stars
2.6k
Forks
1k
Avg merge
4d 15h
Merged PRs (30d)
19

Description

Describe your environment

OS: Ubuntu
Python version:3.11
SDK version: 1.29.0
API version: 1.29.0

What happened?

Hi everyone!
We are using a FastAPI server for some application using FastAPI instrumentation tool from OpenTelemetry, let's call it "computing_server".
This server is called behind another one also using OpenTelemetry in order to propagate some traceID, let's call it "calling_server".

Most of the time, when request are "slow" let's say arround every 10 seconds, everything is working finig fine and the right traceID is propagate from calling_server to computing_server, and logs are consistant.

Sometimes when we are doing calls every 0.5s or 1s, things starts to be disturbing: we have on computing_server some old remaining traceID from a previous call.

Instead of starting from NonRecordingSpan, application start from another retrieve Span, even if Detach token seems to be used ....
I don't really get it...

Thanks you for your help

Steps to Reproduce
import logging
import uvicorn

from opentelemetry import trace
from opentelemetry.sdk.resources import Resource
from opentelemetry.sdk.trace import TracerProvider
from opentelemetry.sdk.trace.export import BatchSpanProcessor
from opentelemetry.exporter.otlp.proto.http.trace_exporter import OTLPSpanExporter
from opentelemetry.instrumentation.logging import LoggingInstrumentor    
from opentelemetry.instrumentation.fastapi import FastAPIInstrumentor

from contextlib import asynccontextmanager
from fastapi import FastAPI, APIRouter, Request, Response


class App:
    
    def __init__(self):
        self.app = None
        self.router = APIRouter(redirect_slashes=False)
        
    def __prepare_routes(self):
        self.router.add_api_route("/estimate/", self.__call_estimate, methods=["POST"], response_model=bytes)

    async def __call_estimate(self, request: Request):
        logging.info(request.headers["traceparent"])
        request = await request.body()
        result = Object()
        return Response(result, media_type='application/octet-stream')

    
    ## SERVER ACCESSORS
    def get_app(self):
        return self.app
    
    def init(self):
        @asynccontextmanager
        async def lifespan(app: FastAPI):
            logging.info("Starting...")

            # Prepare routes
            self.__prepare_routes()
            app.include_router(self.router)
            
            yield

            # Shutdown
            logging.info("Shutdown")

        # Init server
        self.app = FastAPI(lifespan=lifespan, redirect_slashes=False)
        
    def run(self):
        # Run server
        uvicorn.run(self.app, host="0.0.0.0", port=7771, log_config=None)

    
if __name__ == "__main__":
    span_processor = BatchSpanProcessor(OTLPSpanExporter(endpoint=f'http://0.0.0.0:4318/v1/traces'))
    provider = TracerProvider(resource=Resource.create({"service.name": "my_server"}))
    provider.add_span_processor(span_processor)
    trace.set_tracer_provider(provider)
    LoggingInstrumentor().instrument()
    root = logging.getLogger()
    root.disabled = False

    root.setLevel(logging.INFO)
    # create console handler and set level to debug
    console_handler = logging.StreamHandler()
    console_handler.setLevel(logging.INFO)
    # add formatter to console
    console_handler.setFormatter(logging.Formatter("---- 'service_name': '%(otelServiceName)s', 'trace_id': '%(otelTraceID)s', 'span_id': '%(otelSpanID)s', 'message': '%(message)s'"))
    root.addHandler(console_handler)

    # Run server
    server = App()
    server.init()
    FastAPIInstrumentor().instrument_app(server.get_app())
    server.run()

And some client like curl to send a lot of requests

Expected Result

In a perfect working case, we expect the tracedi from http request to be used like this :

---- HTTP call start
NonRecordingSpan(SpanContext(trace_id=0x00000000000000000000000000000000, span_id=0x0000000000000000, trace_flags=0x00, trace_state=[], is_remote=False))
-- Attach  <Token var=<ContextVar name='current_context' default={} at 0x7f04c507ea70> at 0x7f04c4365140>
-- Attach  <Token var=<ContextVar name='current_context' default={} at 0x7f04c507ea70> at 0x7f04c4307640>
---- From HTTP: 00-bbf1de194dd806bd18a68c7abde63c06-6051ddd8243e1671-01
-- Attach  <Token var=<ContextVar name='current_context' default={} at 0x7f04c507ea70> at 0x7f04c42ff000>
-- Detach  <Token var=<ContextVar name='current_context' default={} at 0x7f04c507ea70> at 0x7f04c42ff000>
{"trace_id": "bbf1de194dd806bd18a68c7abde63c06", "span_id": "bb142ed50f99272d"}
-- Attach  <Token var=<ContextVar name='current_context' default={} at 0x7f04c507ea70> at 0x7f04c438e840>
-- Detach  <Token var=<ContextVar name='current_context' default={} at 0x7f04c507ea70> at 0x7f04c438e840>
{"trace_id": "bbf1de194dd806bd18a68c7abde63c06", "span_id": "bb142ed50f99272d"}
-- Attach  <Token var=<ContextVar name='current_context' default={} at 0x7f04c507ea70> at 0x7f04c438e940>
-- Detach  <Token var=<ContextVar name='current_context' default={} at 0x7f04c507ea70> at 0x7f04c438e940>
-- Detach  <Token var=<ContextVar name='current_context' default={} at 0x7f04c507ea70> at 0x7f04c4307640>
-- Detach  <Token var=<ContextVar name='current_context' default={} at 0x7f04c507ea70> at 0x7f04c4365140>

---- HTTP call start
NonRecordingSpan(SpanContext(trace_id=0x00000000000000000000000000000000, span_id=0x0000000000000000, trace_flags=0x00, trace_state=[], is_remote=False))
-- Attach  <Token var=<ContextVar name='current_context' default={} at 0x7f04c507ea70> at 0x7f04c438eac0>
-- Attach  <Token var=<ContextVar name='current_context' default={} at 0x7f04c507ea70> at 0x7f04c438ed40>
---- From HTTP: 00-1ede97640306257aad3de05d4d0a2079-d80ade9ffb28f0cd-01
-- Attach  <Token var=<ContextVar name='current_context' default={} at 0x7f04c507ea70> at 0x7f04c438f080>
-- Detach  <Token var=<ContextVar name='current_context' default={} at 0x7f04c507ea70> at 0x7f04c438f080>
{"trace_id": "1ede97640306257aad3de05d4d0a2079", "span_id": "3093d4f2979b6ea9"}
-- Attach  <Token var=<ContextVar name='current_context' default={} at 0x7f04c507ea70> at 0x7f04c438f540>
-- Detach  <Token var=<ContextVar name='current_context' default={} at 0x7f04c507ea70> at 0x7f04c438f540>
{"trace_id": "1ede97640306257aad3de05d4d0a2079", "span_id": "3093d4f2979b6ea9"}
-- Attach  <Token var=<ContextVar name='current_context' default={} at 0x7f04c507ea70> at 0x7f04c438f740>
-- Detach  <Token var=<ContextVar name='current_context' default={} at 0x7f04c507ea70> at 0x7f04c438f740>
-- Detach  <Token var=<ContextVar name='current_context' default={} at 0x7f04c507ea70> at 0x7f04c438ed40>
-- Detach  <Token var=<ContextVar name='current_context' default={} at 0x7f04c507ea70> at 0x7f04c438eac0>

---- HTTP call start
NonRecordingSpan(SpanContext(trace_id=0x00000000000000000000000000000000, span_id=0x0000000000000000, trace_flags=0x00, trace_state=[], is_remote=False))
-- Attach  <Token var=<ContextVar name='current_context' default={} at 0x7f04c507ea70> at 0x7f04c438fac0>
-- Attach  <Token var=<ContextVar name='current_context' default={} at 0x7f04c507ea70> at 0x7f04c438fd40>
---- From HTTP: 00-41946a2aef9860e175524c6e7517d041-1de81ff4bc522e5f-01
-- Attach  <Token var=<ContextVar name='current_context' default={} at 0x7f04c507ea70> at 0x7f04bc490100>
-- Detach  <Token var=<ContextVar name='current_context' default={} at 0x7f04c507ea70> at 0x7f04bc490100>
{"trace_id": "41946a2aef9860e175524c6e7517d041", "span_id": "9bb5ad1366bdc997"}
-- Attach  <Token var=<ContextVar name='current_context' default={} at 0x7f04c507ea70> at 0x7f04bc4905c0>
-- Detach  <Token var=<ContextVar name='current_context' default={} at 0x7f04c507ea70> at 0x7f04bc4905c0>
{"trace_id": "41946a2aef9860e175524c6e7517d041", "span_id": "9bb5ad1366bdc997"}
-- Attach  <Token var=<ContextVar name='current_context' default={} at 0x7f04c507ea70> at 0x7f04bc4907c0>
-- Detach  <Token var=<ContextVar name='current_context' default={} at 0x7f04c507ea70> at 0x7f04bc4907c0>
-- Detach  <Token var=<ContextVar name='current_context' default={} at 0x7f04c507ea70> at 0x7f04c438fd40>
-- Detach  <Token var=<ContextVar name='current_context' default={} at 0x7f04c507ea70> at 0x7f04c438fac0>
Actual Result

The non expected behaviour:

---- HTTP call start
NonRecordingSpan(SpanContext(trace_id=0x00000000000000000000000000000000, span_id=0x0000000000000000, trace_flags=0x00, trace_state=[], is_remote=False))
-- Attach  <Token var=<ContextVar name='current_context' default={} at 0x7f9558c6f290> at 0x7f9553fff580>
{'current-span-1cfff2ec-c830-49ea-8c63-7905b29e6847': NonRecordingSpan(SpanContext(trace_id=0x733247fb3efe5fe2b7fa8a5db0cabd6b, span_id=0xd1fa76b36391d33d, trace_flags=0x01, trace_state=[], is_remote=True))}
-- Attach  <Token var=<ContextVar name='current_context' default={} at 0x7f9558c6f290> at 0x7f95536e6740>
---- From HTTP: 00-733247fb3efe5fe2b7fa8a5db0cabd6b-d1fa76b36391d33d-01
{'current-span-1cfff2ec-c830-49ea-8c63-7905b29e6847': _Span(name="POST /estimate/", context=SpanContext(trace_id=0x733247fb3efe5fe2b7fa8a5db0cabd6b, span_id=0xb5d3e2c16655d2b6, trace_flags=0x01, trace_state=[], is_remote=False))}
-- Attach  <Token var=<ContextVar name='current_context' default={} at 0x7f9558c6f290> at 0x7f9551c0ef00>
-- Detach  <Token var=<ContextVar name='current_context' default={} at 0x7f9558c6f290> at 0x7f9551c0ef00>
-- Attach  <Token var=<ContextVar name='current_context' default={} at 0x7f9558c6f290> at 0x7f9551c0f040>
-- Detach  <Token var=<ContextVar name='current_context' default={} at 0x7f9558c6f290> at 0x7f9551c0f040>
-- Attach  <Token var=<ContextVar name='current_context' default={} at 0x7f9558c6f290> at 0x7f9551c0f200>
-- Detach  <Token var=<ContextVar name='current_context' default={} at 0x7f9558c6f290> at 0x7f9551c0f200>
-- Attach  <Token var=<ContextVar name='current_context' default={} at 0x7f9558c6f290> at 0x7f9551c0f480>
-- Detach  <Token var=<ContextVar name='current_context' default={} at 0x7f9558c6f290> at 0x7f9551c0f480>
{"trace_id": "733247fb3efe5fe2b7fa8a5db0cabd6b", "span_id": "b5d3e2c16655d2b6"}
-- Attach  <Token var=<ContextVar name='current_context' default={} at 0x7f9558c6f290> at 0x7f95536ce700>
-- Detach  <Token var=<ContextVar name='current_context' default={} at 0x7f9558c6f290> at 0x7f95536ce700>
{"trace_id": "733247fb3efe5fe2b7fa8a5db0cabd6b", "span_id": "b5d3e2c16655d2b6"}
-- Attach  <Token var=<ContextVar name='current_context' default={} at 0x7f9558c6f290> at 0x7f951f330900>
-- Detach  <Token var=<ContextVar name='current_context' default={} at 0x7f9558c6f290> at 0x7f951f330900>
-- Detach  <Token var=<ContextVar name='current_context' default={} at 0x7f9558c6f290> at 0x7f95536e6740>
-- Detach  <Token var=<ContextVar name='current_context' default={} at 0x7f9558c6f290> at 0x7f9553fff580>

---- HTTP call start
_Span(name="POST /estimate/", context=SpanContext(trace_id=0x733247fb3efe5fe2b7fa8a5db0cabd6b, span_id=0xb5d3e2c16655d2b6, trace_flags=0x01, trace_state=[], is_remote=False))
-- Attach  <Token var=<ContextVar name='current_context' default={} at 0x7f9558c6f290> at 0x7f95435df780>
---- From HTTP: 00-6eb43e73f936aee8b83328f1d9c901b7-b3ed9b4f5e27fb35-01
{'current-span-1cfff2ec-c830-49ea-8c63-7905b29e6847': _Span(name="POST /estimate/", context=SpanContext(trace_id=0x733247fb3efe5fe2b7fa8a5db0cabd6b, span_id=0xe35d2e0ce570cc68, trace_flags=0x01, trace_state=[], is_remote=False))}
-- Attach  <Token var=<ContextVar name='current_context' default={} at 0x7f9558c6f290> at 0x7f9541916b40>
-- Detach  <Token var=<ContextVar name='current_context' default={} at 0x7f9558c6f290> at 0x7f9541916b40>
-- Attach  <Token var=<ContextVar name='current_context' default={} at 0x7f9558c6f290> at 0x7f9581233000>
-- Detach  <Token var=<ContextVar name='current_context' default={} at 0x7f9558c6f290> at 0x7f9581233000>
-- Attach  <Token var=<ContextVar name='current_context' default={} at 0x7f9558c6f290> at 0x7f94f7d80400>
-- Detach  <Token var=<ContextVar name='current_context' default={} at 0x7f9558c6f290> at 0x7f94f7d80400>
-- Attach  <Token var=<ContextVar name='current_context' default={} at 0x7f9558c6f290> at 0x7f9558935940>
-- Detach  <Token var=<ContextVar name='current_context' default={} at 0x7f9558c6f290> at 0x7f9558935940>
{"trace_id": "733247fb3efe5fe2b7fa8a5db0cabd6b", "span_id": "e35d2e0ce570cc68"}
-- Attach  <Token var=<ContextVar name='current_context' default={} at 0x7f9558c6f290> at 0x7f95435f3240>
-- Detach  <Token var=<ContextVar name='current_context' default={} at 0x7f9558c6f290> at 0x7f95435f3240>
-- Attach  <Token var=<ContextVar name='current_context' default={} at 0x7f9558c6f290> at 0x7f954360cb40>
-- Detach  <Token var=<ContextVar name='current_context' default={} at 0x7f9558c6f290> at 0x7f954360cb40>
-- Detach  <Token var=<ContextVar name='current_context' default={} at 0x7f9558c6f290> at 0x7f95435df780>
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 with the FastAPIInstrumentor().instrument_app(server.get_app()) entry point and trace the context attach/detach behavior shown in the reproduction. Run the provided FastAPI application with frequent requests and compare the propagated trace and span IDs with the request traceparent; done means each request uses its own incoming context without retaining a previous span.

Written by the indexing model from the issue text.

Assessment

Tech stack
fastapi, python
Domain
api, backend, observability-sre
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Stale
Clarity
Mostly clear
Newbie friendliness
35/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.