Add kaya-otel package for OpenTelemetry tracing and metrics
CI / Build Pip package (push) Successful in 4m8s

Instrument HTTP requests and WebSocket connections via Kaya hooks, covering both ASGI and RSGI. Records handler exceptions, WebSocket close codes, optional header capture, exclusions and lifecycle hooks.

Add route-template resolution and exception visibility to kaya-core so trace/metric attributes can use low-cardinality routes and failed spans can record escaped exceptions.
This commit is contained in:
2026-09-19 00:39:15 +00:00
parent 69762d93df
commit 148a35b71c
16 changed files with 1182 additions and 1 deletions
+85
View File
@@ -0,0 +1,85 @@
# kaya-otel
OpenTelemetry tracing and metrics for the Kaya web framework.
Provides `OTelMixin`, a `KayaMixin` that instruments HTTP requests and
WebSocket connections with OpenTelemetry spans and exports HTTP server
metrics, shipping everything to an OTLP/HTTP collector.
## Usage
```python
from kaya.core import KayaApp, HttpContext
from kaya.otel import OTelMixin
app = KayaApp(mixins=[
OTelMixin(
service_name='my-service',
endpoint='http://localhost:4318',
)
])
@app.GET('/')
async def home(ctx: HttpContext):
await ctx.send_str(200, 'Hello World!')
```
## Parameters
- `service_name`: value of the `service.name` resource attribute.
- `endpoint`: base URL of an OTLP/HTTP collector; the signal paths
`/v1/traces` and `/v1/metrics` are appended. When `None`, the exporters
use their own defaults, including the standard
`OTEL_EXPORTER_OTLP_ENDPOINT` environment variable.
- `headers`: extra HTTP headers sent to the collector (e.g. authentication).
- `metric_export_interval_millis`: metric export interval
(default `60000`).
- `tracer_provider` / `meter_provider`: inject custom providers (e.g. with
in-memory exporters for tests) instead of the OTLP defaults. The mixin
only shuts down providers it created itself.
- `resource_attributes`: extra resource attributes merged with
`service.name` when the mixin creates the providers.
- `excluded_paths` / `excluded_path_regexes`: skip tracing and metrics for
matching paths (useful for health checks and metrics endpoints).
- `capture_request_headers` / `capture_response_headers`: opt-in HTTP header
capture as `http.request.header.<name>` / `http.response.header.<name>`
span attributes. Header names are normalized to lowercase with `-`
replaced by `_`; values are captured as string lists.
- `sanitize_headers`: headers captured as `REDACTED` (default:
`authorization`, `proxy-authorization`, `cookie`, `set-cookie`).
- `server_request_hook` / `server_response_hook` / `websocket_connect_hook` /
`websocket_close_hook`: optional synchronous callbacks invoked with the
span and the Kaya context/websocket at the corresponding lifecycle point.
Hook exceptions are logged and do not fail the request.
- `metrics_include_raw_path`: when `True`, metrics use raw `url.path`
attributes (legacy, potentially high cardinality). The default `False`
keeps metrics low-cardinality by using `http.route` when Kaya can resolve
a route template.
- `websocket_error_close_codes`: close codes that mark a websocket span as
failed. Defaults to protocol/application error codes such as `1002`,
`1003` and `1007`-`1011`.
## Behavior
- Every HTTP request gets a `SERVER` span named `<METHOD> <path>` with the
usual HTTP semantic attributes (`http.request.method`, `url.path`,
`client.address`, `server.address`, ...). When Kaya resolves a route
template, the span name is updated to `<METHOD> <route template>` and
`http.route` is set. The response status code is recorded as
`http.response.status_code` when the handler sends the response; 5xx
statuses mark the span as failed. Exceptions escaping the handler are
recorded on the span and also mark it as failed.
- Every WebSocket connection gets one span for its whole lifetime. The close
code is recorded as `kaya.websocket.close_code`; exceptions and configured
error close codes mark the span as failed.
- W3C `traceparent`/`tracestate` headers on incoming requests are honored,
so traces propagate from upstream services.
- Metrics:
- `http.server.request.duration` (histogram, seconds), with
`http.request.method`, `http.route` when known and
`http.response.status_code` attributes.
- `http.server.active_requests` (up-down counter), with
`http.request.method` attributes.
`OTelMixin` is a `KayaMixin`, so the app stays a `KayaApp` and both ASGI and
RSGI keep working.