Add type hints to Starlette instrumentation (#3045)
* Add type hints to Starlette instrumentation * format * Add changelog * Add changelog * Remove pyright ignore --------- Co-authored-by: Riccardo Magliocchetti <riccardo.magliocchetti@gmail.com> Co-authored-by: Emídio Neto <9735060+emdneto@users.noreply.github.com>
This commit is contained in:
parent
142b86c1bf
commit
0da62aa532
|
|
@ -13,6 +13,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|
||||||
|
- `opentelemetry-instrumentation-starlette` Add type hints to the instrumentation
|
||||||
|
([#3045](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/3045))
|
||||||
- `opentelemetry-distro` default to OTLP log exporter.
|
- `opentelemetry-distro` default to OTLP log exporter.
|
||||||
([#3042](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/3042))
|
([#3042](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/3042))
|
||||||
- `opentelemetry-instrumentation-sqlalchemy` Update unit tests to run with SQLALchemy 2
|
- `opentelemetry-instrumentation-sqlalchemy` Update unit tests to run with SQLALchemy 2
|
||||||
|
|
|
||||||
|
|
@ -170,7 +170,9 @@ API
|
||||||
---
|
---
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from typing import Collection
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from typing import TYPE_CHECKING, Any, Collection, cast
|
||||||
|
|
||||||
from starlette import applications
|
from starlette import applications
|
||||||
from starlette.routing import Match
|
from starlette.routing import Match
|
||||||
|
|
@ -184,18 +186,29 @@ from opentelemetry.instrumentation.asgi.types import (
|
||||||
from opentelemetry.instrumentation.instrumentor import BaseInstrumentor
|
from opentelemetry.instrumentation.instrumentor import BaseInstrumentor
|
||||||
from opentelemetry.instrumentation.starlette.package import _instruments
|
from opentelemetry.instrumentation.starlette.package import _instruments
|
||||||
from opentelemetry.instrumentation.starlette.version import __version__
|
from opentelemetry.instrumentation.starlette.version import __version__
|
||||||
from opentelemetry.metrics import get_meter
|
from opentelemetry.metrics import MeterProvider, get_meter
|
||||||
from opentelemetry.semconv.trace import SpanAttributes
|
from opentelemetry.semconv.trace import SpanAttributes
|
||||||
from opentelemetry.trace import get_tracer
|
from opentelemetry.trace import TracerProvider, get_tracer
|
||||||
from opentelemetry.util.http import get_excluded_urls
|
from opentelemetry.util.http import get_excluded_urls
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from typing import TypedDict, Unpack
|
||||||
|
|
||||||
|
class InstrumentKwargs(TypedDict, total=False):
|
||||||
|
tracer_provider: TracerProvider
|
||||||
|
meter_provider: MeterProvider
|
||||||
|
server_request_hook: ServerRequestHook
|
||||||
|
client_request_hook: ClientRequestHook
|
||||||
|
client_response_hook: ClientResponseHook
|
||||||
|
|
||||||
|
|
||||||
_excluded_urls = get_excluded_urls("STARLETTE")
|
_excluded_urls = get_excluded_urls("STARLETTE")
|
||||||
|
|
||||||
|
|
||||||
class StarletteInstrumentor(BaseInstrumentor):
|
class StarletteInstrumentor(BaseInstrumentor):
|
||||||
"""An instrumentor for starlette
|
"""An instrumentor for Starlette.
|
||||||
|
|
||||||
See `BaseInstrumentor`
|
See `BaseInstrumentor`.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
_original_starlette = None
|
_original_starlette = None
|
||||||
|
|
@ -206,8 +219,8 @@ class StarletteInstrumentor(BaseInstrumentor):
|
||||||
server_request_hook: ServerRequestHook = None,
|
server_request_hook: ServerRequestHook = None,
|
||||||
client_request_hook: ClientRequestHook = None,
|
client_request_hook: ClientRequestHook = None,
|
||||||
client_response_hook: ClientResponseHook = None,
|
client_response_hook: ClientResponseHook = None,
|
||||||
meter_provider=None,
|
meter_provider: MeterProvider | None = None,
|
||||||
tracer_provider=None,
|
tracer_provider: TracerProvider | None = None,
|
||||||
):
|
):
|
||||||
"""Instrument an uninstrumented Starlette application."""
|
"""Instrument an uninstrumented Starlette application."""
|
||||||
tracer = get_tracer(
|
tracer = get_tracer(
|
||||||
|
|
@ -253,7 +266,7 @@ class StarletteInstrumentor(BaseInstrumentor):
|
||||||
def instrumentation_dependencies(self) -> Collection[str]:
|
def instrumentation_dependencies(self) -> Collection[str]:
|
||||||
return _instruments
|
return _instruments
|
||||||
|
|
||||||
def _instrument(self, **kwargs):
|
def _instrument(self, **kwargs: Unpack[InstrumentKwargs]):
|
||||||
self._original_starlette = applications.Starlette
|
self._original_starlette = applications.Starlette
|
||||||
_InstrumentedStarlette._tracer_provider = kwargs.get("tracer_provider")
|
_InstrumentedStarlette._tracer_provider = kwargs.get("tracer_provider")
|
||||||
_InstrumentedStarlette._server_request_hook = kwargs.get(
|
_InstrumentedStarlette._server_request_hook = kwargs.get(
|
||||||
|
|
@ -269,7 +282,7 @@ class StarletteInstrumentor(BaseInstrumentor):
|
||||||
|
|
||||||
applications.Starlette = _InstrumentedStarlette
|
applications.Starlette = _InstrumentedStarlette
|
||||||
|
|
||||||
def _uninstrument(self, **kwargs):
|
def _uninstrument(self, **kwargs: Any):
|
||||||
"""uninstrumenting all created apps by user"""
|
"""uninstrumenting all created apps by user"""
|
||||||
for instance in _InstrumentedStarlette._instrumented_starlette_apps:
|
for instance in _InstrumentedStarlette._instrumented_starlette_apps:
|
||||||
self.uninstrument_app(instance)
|
self.uninstrument_app(instance)
|
||||||
|
|
@ -278,14 +291,14 @@ class StarletteInstrumentor(BaseInstrumentor):
|
||||||
|
|
||||||
|
|
||||||
class _InstrumentedStarlette(applications.Starlette):
|
class _InstrumentedStarlette(applications.Starlette):
|
||||||
_tracer_provider = None
|
_tracer_provider: TracerProvider | None = None
|
||||||
_meter_provider = None
|
_meter_provider: MeterProvider | None = None
|
||||||
_server_request_hook: ServerRequestHook = None
|
_server_request_hook: ServerRequestHook = None
|
||||||
_client_request_hook: ClientRequestHook = None
|
_client_request_hook: ClientRequestHook = None
|
||||||
_client_response_hook: ClientResponseHook = None
|
_client_response_hook: ClientResponseHook = None
|
||||||
_instrumented_starlette_apps = set()
|
_instrumented_starlette_apps: set[applications.Starlette] = set()
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args: Any, **kwargs: Any):
|
||||||
super().__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
tracer = get_tracer(
|
tracer = get_tracer(
|
||||||
__name__,
|
__name__,
|
||||||
|
|
@ -318,21 +331,22 @@ class _InstrumentedStarlette(applications.Starlette):
|
||||||
_InstrumentedStarlette._instrumented_starlette_apps.remove(self)
|
_InstrumentedStarlette._instrumented_starlette_apps.remove(self)
|
||||||
|
|
||||||
|
|
||||||
def _get_route_details(scope):
|
def _get_route_details(scope: dict[str, Any]) -> str | None:
|
||||||
"""
|
"""
|
||||||
Function to retrieve Starlette route from scope.
|
Function to retrieve Starlette route from ASGI scope.
|
||||||
|
|
||||||
TODO: there is currently no way to retrieve http.route from
|
TODO: there is currently no way to retrieve http.route from
|
||||||
a starlette application from scope.
|
a starlette application from scope.
|
||||||
See: https://github.com/encode/starlette/pull/804
|
See: https://github.com/encode/starlette/pull/804
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
scope: A Starlette scope
|
scope: The ASGI scope that contains the Starlette application in the "app" key.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
A string containing the route or None
|
The path to the route if found, otherwise None.
|
||||||
"""
|
"""
|
||||||
app = scope["app"]
|
app = cast(applications.Starlette, scope["app"])
|
||||||
route = None
|
route: str | None = None
|
||||||
|
|
||||||
for starlette_route in app.routes:
|
for starlette_route in app.routes:
|
||||||
match, _ = starlette_route.matches(scope)
|
match, _ = starlette_route.matches(scope)
|
||||||
|
|
@ -344,18 +358,20 @@ def _get_route_details(scope):
|
||||||
return route
|
return route
|
||||||
|
|
||||||
|
|
||||||
def _get_default_span_details(scope):
|
def _get_default_span_details(
|
||||||
"""
|
scope: dict[str, Any],
|
||||||
Callback to retrieve span name and attributes from scope.
|
) -> tuple[str, dict[str, Any]]:
|
||||||
|
"""Callback to retrieve span name and attributes from ASGI scope.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
scope: A Starlette scope
|
scope: The ASGI scope that contains the Starlette application in the "app" key.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
A tuple of span name and attributes
|
A tuple of span name and attributes.
|
||||||
"""
|
"""
|
||||||
route = _get_route_details(scope)
|
route = _get_route_details(scope)
|
||||||
method = scope.get("method", "")
|
method: str = scope.get("method", "")
|
||||||
attributes = {}
|
attributes: dict[str, Any] = {}
|
||||||
if route:
|
if route:
|
||||||
attributes[SpanAttributes.HTTP_ROUTE] = route
|
attributes[SpanAttributes.HTTP_ROUTE] = route
|
||||||
if method and route: # http
|
if method and route: # http
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue