Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
112 changes: 108 additions & 4 deletions docs/usage/metrics/open-telemetry.rst
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,7 @@ this package, you should first install the required dependencies:
pip install 'litestar[opentelemetry]'

Once these requirements are satisfied, you can instrument your Litestar application by creating an instance
of :class:`OpenTelemetryConfig <litestar.plugins.opentelemetry.OpenTelemetryConfig>` and passing the middleware it creates to
the Litestar constructor:
of :class:`OpenTelemetryConfig <litestar.plugins.opentelemetry.OpenTelemetryConfig>` and passing it to the plugin:

.. code-block:: python

Expand All @@ -32,5 +31,110 @@ The above example will work out of the box if you configure a global ``tracer_pr
exporter to use these (see the
`OpenTelemetry Exporter docs <https://opentelemetry.io/docs/instrumentation/python/exporters/>`_ for further details).

You can also pass configuration to the ``OpenTelemetryConfig`` telling it which providers to use. Consult
:class:`reference docs <litestar.plugins.opentelemetry.OpenTelemetryConfig>` regarding the configuration options you can use.
You can also pass configuration to the ``OpenTelemetryConfig`` telling it which providers to use. Consult the
:class:`OpenTelemetryConfig <litestar.plugins.opentelemetry.OpenTelemetryConfig>` reference docs for all available configuration options.

Configuration options
---------------------

Provider configuration
~~~~~~~~~~~~~~~~~~~~~~

The following options allow you to configure custom OpenTelemetry providers:

- ``tracer_provider``: Custom ``TracerProvider`` instance. If omitted, the globally configured provider is used.
- ``meter_provider``: Custom ``MeterProvider`` instance. If omitted, the globally configured provider is used.
- ``meter``: Custom ``Meter`` instance. If omitted, the meter from the provider is used.

Example with custom tracer provider:

.. code-block:: python

from opentelemetry.sdk.trace import TracerProvider
from opentelemetry.sdk.trace.export import BatchSpanProcessor, ConsoleSpanExporter
from litestar import Litestar
from litestar.plugins.opentelemetry import OpenTelemetryConfig, OpenTelemetryPlugin

# Configure a custom tracer provider
tracer_provider = TracerProvider()
tracer_provider.add_span_processor(BatchSpanProcessor(ConsoleSpanExporter()))

config = OpenTelemetryConfig(tracer_provider=tracer_provider)
app = Litestar(plugins=[OpenTelemetryPlugin(config)])

Hook handlers
~~~~~~~~~~~~~

Hook handlers allow you to customize span behavior at various points in the request lifecycle:

- ``server_request_hook_handler``: Called with the server span and ASGI scope for every incoming request.
- ``client_request_hook_handler``: Called with the internal span when the ``receive`` method is invoked.
- ``client_response_hook_handler``: Called with the internal span when the ``send`` method is invoked.

Example adding custom attributes:

.. code-block:: python

from opentelemetry.trace import Span
from litestar.plugins.opentelemetry import OpenTelemetryConfig, OpenTelemetryPlugin

def request_hook(span: Span, scope: dict) -> None:
span.set_attribute("custom.user_agent", scope.get("headers", {}).get("user-agent", ""))

config = OpenTelemetryConfig(server_request_hook_handler=request_hook)
app = Litestar(plugins=[OpenTelemetryPlugin(config)])

URL filtering
~~~~~~~~~~~~~

You can exclude specific URLs from instrumentation:

- ``exclude``: Pattern or list of patterns to exclude from instrumentation.
- ``exclude_opt_key``: Route option key to disable instrumentation on a per-route basis.
- ``exclude_urls_env_key`` (default ``"LITESTAR"``): Environment variable prefix for excluded URLs. With the default, the environment variable ``LITESTAR_EXCLUDED_URLS`` will be checked.

Example excluding health check endpoints:

.. code-block:: python

from litestar.plugins.opentelemetry import OpenTelemetryConfig, OpenTelemetryPlugin

config = OpenTelemetryConfig(
exclude=["/health", "/readiness", "/metrics"]
)
app = Litestar(plugins=[OpenTelemetryPlugin(config)])

Advanced options
~~~~~~~~~~~~~~~~

- ``scope_span_details_extractor``: Callback that returns a tuple of ``(span_name, attributes)`` for customizing span details from the ASGI scope.
- ``scopes``: ASGI scope types to process (e.g., ``{"http", "websocket"}``). If ``None``, both HTTP and WebSocket are processed.
- ``middleware_class``: Custom middleware class. Must be a subclass of ``OpenTelemetryInstrumentationMiddleware``.

Litestar-specific spans
------------------------

Litestar can automatically create spans for framework events. With :class:`OpenTelemetryConfig <litestar.plugins.opentelemetry.OpenTelemetryConfig>`,
all instrumentation options are enabled by default:

- ``instrument_guards`` (default ``True``): Create ``guard.*`` spans for each guard executed within a request.
- ``instrument_events`` (default ``True``): Create ``event.emit.*`` and ``event.listener.*`` spans for the event emitter.
- ``instrument_lifecycle`` (default ``True``): Wrap application startup/shutdown hooks with ``lifecycle.*`` spans.
- ``instrument_cli`` (default ``True``): Emit ``cli.*`` spans for Litestar CLI commands.

Example with selective instrumentation:

.. code-block:: python

from litestar import Litestar
from litestar.plugins.opentelemetry import OpenTelemetryConfig, OpenTelemetryPlugin

# Enable only guard and event instrumentation
config = OpenTelemetryConfig(
instrument_guards=True,
instrument_events=True,
instrument_lifecycle=False,
instrument_cli=False
)

app = Litestar(plugins=[OpenTelemetryPlugin(config)])
12 changes: 12 additions & 0 deletions litestar/plugins/opentelemetry/__init__.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,21 @@
from .config import OpenTelemetryConfig
from .instrumentation import (
create_span,
instrument_channel_operation,
instrument_dependency,
instrument_guard,
instrument_lifecycle_event,
)
from .middleware import OpenTelemetryInstrumentationMiddleware
from .plugin import OpenTelemetryPlugin

__all__ = (
"OpenTelemetryConfig",
"OpenTelemetryInstrumentationMiddleware",
"OpenTelemetryPlugin",
"create_span",
"instrument_channel_operation",
"instrument_dependency",
"instrument_guard",
"instrument_lifecycle_event",
)
35 changes: 30 additions & 5 deletions litestar/plugins/opentelemetry/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,17 +13,16 @@
__all__ = ("OpenTelemetryConfig",)


try:
try: # pragma: no cover - dependency is optional at runtime
import opentelemetry # noqa: F401
except ImportError as e:
from opentelemetry.trace import Span, TracerProvider # pyright: ignore
except ImportError as e: # pragma: no cover
raise MissingDependencyException("opentelemetry") from e


from opentelemetry.trace import Span, TracerProvider # pyright: ignore

if TYPE_CHECKING:
from opentelemetry.metrics import Meter, MeterProvider

from litestar.plugins.opentelemetry.plugin import OpenTelemetryPlugin
from litestar.types import Scope, Scopes

OpenTelemetryHookHandler = Callable[[Span, dict], None]
Expand Down Expand Up @@ -87,6 +86,18 @@ class OpenTelemetryConfig:
Should be a subclass of OpenTelemetry
InstrumentationMiddleware][litestar.plugins.opentelemetry.OpenTelemetryInstrumentationMiddleware].
"""
instrument_guards: bool = field(default=True)
"""Whether to automatically instrument Litestar route guards."""
instrument_events: bool = field(default=True)
"""Whether to instrument event listeners and emitters."""
instrument_lifecycle: bool = field(default=True)
"""Whether to instrument application lifecycle hooks (startup/shutdown)."""
instrument_middleware: bool = field(default=False)
"""Reserved for future middleware-level instrumentation. Currently unused and left for forward compatibility."""
instrument_cli: bool = field(default=True)
"""Whether to instrument Litestar CLI commands (click entrypoints)."""

_plugin: OpenTelemetryPlugin | None = field(default=None, init=False, repr=False)

@property
def middleware(self) -> DefineMiddleware:
Expand All @@ -100,3 +111,17 @@ def middleware(self) -> DefineMiddleware:
An instance of ``DefineMiddleware``.
"""
return DefineMiddleware(self.middleware_class, config=self)

@property
def plugin(self) -> OpenTelemetryPlugin:
"""Convenience accessor to build an :class:`OpenTelemetryPlugin` from this config.

Returns:
A plugin instance wired with this configuration.
"""

if self._plugin is None:
from litestar.plugins.opentelemetry.plugin import OpenTelemetryPlugin

self._plugin = OpenTelemetryPlugin(self)
return self._plugin
Loading
Loading