Observability¶
Veloce exposes a single instrumentation hook that fires once per finished HTTP request. Every observability integration — the access log and the Prometheus exporter — is built on it, and you can register your own hook the same way.
The instrumentation hook¶
app.add_instrumentation(hook)
registers a callable that receives a
RequestMetrics record after each
request finishes. The hook may be a plain function or a coroutine function,
and a hook that raises is logged and skipped so instrumentation never breaks
a response. With no hook registered the request path carries no
instrumentation cost — not even a clock read.
@app.add_instrumentation
def export(metrics):
statsd.timing(metrics.route or "unmatched", metrics.duration_ms)
RequestMetrics carries these fields:
| Field | Carries |
|---|---|
method |
the request method |
path |
the concrete path |
route |
the matched route template (or None for a 404/405) |
status_code |
the status_code |
duration_ms |
the wall-clock duration_ms |
streamed |
a streamed flag |
The route template
(/items/{id}) — not the concrete path — is the safe aggregation key:
an attacker-controlled URL can never explode label cardinality.
Access logging¶
instrument_access_log emits
one access-log record per request, sourced from the same RequestMetrics
record so logs and traces stay joinable on (route, status). It bootstraps a
default handler on the veloce.access logger and supports a text or JSON
format:
from veloce import Veloce
from veloce.observability import instrument_access_log
app = Veloce()
instrument_access_log(app, json=True)
The hook gates on logger.isEnabledFor, so a muted access log does zero
serialization work. Pass your own logger=... to route records into an
existing logging setup. Register this instead of LoggingMiddleware, not
in addition — doing both double-logs each request.
Prometheus metrics¶
instrument_with_prometheus (from veloce.metrics) exports a request counter
and a request-duration histogram from the same hook. It is an optional
integration — install the extra:
Then wire it up once at startup. The exporter only records series; serving
/metrics is the application's job:
from veloce import Veloce
from veloce.metrics import instrument_with_prometheus
app = Veloce()
instrument_with_prometheus(app)
This registers two series:
| Series | Labels |
|---|---|
http_requests_total counter |
method, route template, and status |
http_request_duration_seconds histogram |
method and route template |
An unmatched request (404/405) uses the constant "<unmatched>" route label.
Override the metric name prefix, the histogram buckets, or pass a custom
registry=... to isolate apps (use a fresh registry per app — instrumenting
twice against the same registry raises prometheus_client's "Duplicated
timeseries" error).
Calling instrument_with_prometheus without the extra installed raises an
ImportError with an install hint.
See also¶
- Middleware —
LoggingMiddlewareandRequestIDMiddlewarefor request-scoped logging without the instrumentation hook. - Deployment — running Veloce in production.