Skip to content

Instrumentation & metrics

InstrumentableHttpClient extends BaseHttpClient with request metrics — latency, status, and error counts — emitted through a pluggable sink. When no sink is enabled it behaves exactly like BaseHttpClient with negligible overhead.

Subclass it the same way you would BaseHttpClient:

from asyncly.client.metrics.instrumentable_client import InstrumentableHttpClient


class CatfactClient(InstrumentableHttpClient):
    ...

Enabling a sink

client.enable_metrics(sink)
client.disable_metrics()

Or scope metrics to a block with the context manager:

with client.instrument(sink):
    await client.fetch_fact()

Each completed request calls sink.observe_request(...) with the client name, method, resolved route, status, duration, and error type.

Sinks

Prometheus

Requires the prometheus extra. PrometheusSink records a histogram of request durations labeled by client, method, route, and status:

from asyncly.client.metrics.sinks.prometheus import PrometheusSink

sink = PrometheusSink(namespace="asyncly", subsystem="client")
client.enable_metrics(sink)

OpenTelemetry

Requires the opentelemetry extra. OpenTelemetrySink records through an OpenTelemetry Meter:

from opentelemetry import metrics
from asyncly.client.metrics.sinks.opentelemetry import OpenTelemetrySink

sink = OpenTelemetrySink(meter=metrics.get_meter("asyncly"))
client.enable_metrics(sink)

Noop

NoopSink is the default — it does nothing and adds no overhead.

Route labels

To avoid high-cardinality metrics, request paths are normalized into a route label by default_route_resolver, which replaces numeric and UUID path segments with :id (so /cats/42 becomes /cats/:id). Pass your own route_resolver to enable_metrics / instrument to customize this.

Custom sinks

Any object implementing the MetricsSink protocol works:

class LoggingSink:
    def observe_request(
        self, *, client, method, route, status, duration_seconds, error_type=None
    ) -> None:
        print(f"{client} {method} {route} -> {status} in {duration_seconds:.3f}s")