open-telemetry / opentelemetry-python-contrib

OpenTelemetry instrumentation for Python modules
https://opentelemetry.io
Apache License 2.0
647 stars 535 forks source link

Issue: ImportError when using OpenTelemetry ASGI Instrumentation in FastAPI #2641

Open GrannyProgramming opened 4 days ago

GrannyProgramming commented 4 days ago

Describe your environment

name: opentelemetry channels:

What happened?

What happened? When attempting to use the opentelemetry-instrumentation-asgi package in a FastAPI application, an ImportError occurs due to missing imports for ClientRequestHook, ClientResponseHook, and ServerRequestHook.

https://github.com/open-telemetry/opentelemetry-python-contrib/blob/main/instrumentation/opentelemetry-instrumentation-fastapi/src/opentelemetry/instrumentation/fastapi/__init__.py

Steps to Reproduce

Run the file https://github.com/open-telemetry/opentelemetry-python-contrib/blob/main/instrumentation/opentelemetry-instrumentation-fastapi/src/opentelemetry/instrumentation/fastapi/__init__.py

with the package pip install fastapi opentelemetry-instrumentation-asgi

Expected Result

The application should run without any import errors, and the OpenTelemetry ASGI middleware should be applied to the FastAPI application.

Actual Result

ImportError: cannot import name 'ClientRequestHook' from 'opentelemetry.instrumentation.asgi'

Additional context

Additional context The ClientRequestHook, ClientResponseHook, and ServerRequestHook are not available in the opentelemetry-instrumentation-asgi package, causing the import error.

Solution To resolve this issue, you can remove the imports for ClientRequestHook, ClientResponseHook, and ServerRequestHook and adjust the middleware setup accordingly. Here is the updated code:

// ... existing code ... from opentelemetry.instrumentation.asgi import OpenTelemetryMiddleware // ... existing code ...

class FastAPIInstrumentor(BaseInstrumentor): // ... existing code ...

@staticmethod
def instrument_app(
    app: fastapi.FastAPI,
    server_request_hook=None,
    client_request_hook=None,
    client_response_hook=None,
    tracer_provider=None,
    meter_provider=None,
    excluded_urls=None,
):
    """Instrument an uninstrumented FastAPI application."""
    // ... existing code ...

// ... existing code ...

def _instrument(self, **kwargs):
    self._original_fastapi = fastapi.FastAPI
    _InstrumentedFastAPI._tracer_provider = kwargs.get("tracer_provider")
    _InstrumentedFastAPI._server_request_hook = kwargs.get(
        "server_request_hook"
    )
    _InstrumentedFastAPI._client_request_hook = kwargs.get(
        "client_request_hook"
    )
    _InstrumentedFastAPI._client_response_hook = kwargs.get(
        "client_response_hook"
    )
    _excluded_urls = kwargs.get("excluded_urls")
    _InstrumentedFastAPI._excluded_urls = (
        _excluded_urls_from_env
        if _excluded_urls is None
        else parse_excluded_urls(_excluded_urls)
    )
    _InstrumentedFastAPI._meter_provider = kwargs.get("meter_provider")
    fastapi.FastAPI = _InstrumentedFastAPI

// ... existing code ...

class _InstrumentedFastAPI(fastapi.FastAPI): _tracer_provider = None _meter_provider = None _excluded_urls = None _server_request_hook = None _client_request_hook = None _client_response_hook = None _instrumented_fastapi_apps = set()

def __init__(self, *args, **kwargs):
    super().__init__(*args, **kwargs)
    tracer = get_tracer(
        __name__,
        __version__,
        _InstrumentedFastAPI._tracer_provider,
        schema_url="https://opentelemetry.io/schemas/1.11.0",
    )
    meter = get_meter(
        __name__,
        __version__,
        _InstrumentedFastAPI._meter_provider,
        schema_url="https://opentelemetry.io/schemas/1.11.0",
    )
    self.add_middleware(
        OpenTelemetryMiddleware,
        excluded_urls=_InstrumentedFastAPI._excluded_urls,
        default_span_details=_get_default_span_details,
        server_request_hook=_InstrumentedFastAPI._server_request_hook,
        client_request_hook=_InstrumentedFastAPI._client_request_hook,
        client_response_hook=_InstrumentedFastAPI._client_response_hook,
        tracer=tracer,
        meter=meter,
    )
    self._is_instrumented_by_opentelemetry = True
    _InstrumentedFastAPI._instrumented_fastapi_apps.add(self)

// ... existing code ...

Explanation
  1. Import Change: Removed the imports for ClientRequestHook, ClientResponseHook, and ServerRequestHook.
  2. Method Signatures: Updated the method signatures to remove the specific hook types.
  3. Middleware Setup: Adjusted the middleware setup to use the OpenTelemetryMiddleware directly without the removed hooks.

Would you like to implement a fix?

Yes