OpenTelemetry#
OTelHook provides OpenTelemetry metrics integration for monitoring rate limiting events.
Installation#
Install throttled-py with OpenTelemetry support:
pip install 'throttled-py[otel]'
This installs opentelemetry-api only. throttled-py emits metrics in OpenTelemetry format - how you collect and export them is up to your application.
Quick Start#
from opentelemetry.metrics import get_meter
from throttled import Throttled
from throttled.contrib.otel import OTelHook
# Create OTelHook with a meter from the OTel API.
# The actual metrics backend (e.g., Prometheus, OTLP) is configured separately
# via opentelemetry-sdk or your framework's setup.
meter = get_meter("throttled-example")
# Create a rate limiter with OTelHook attached.
throttle = Throttled(key="/api/ping", quota="5/m", hooks=[OTelHook(meter)])
def main() -> None:
# First 5 requests are allowed.
for i in range(5):
result = throttle.limit("/api/ping")
print(f"Request {i + 1}: {'denied' if result.limited else 'allowed'}")
# But 6th request is denied.
result = throttle.limit("/api/ping")
print(f"Request 6: {'denied' if result.limited else 'allowed'}")
# π OTelHook records the following metrics:
# - throttled.requests (Counter): number of rate limit checks
# - throttled.duration (Histogram): latency of rate limit checks
# Attributes: key, algorithm, store_type, result ("allowed" / "denied")
if __name__ == "__main__":
main()
import asyncio
from opentelemetry.metrics import get_meter
from throttled.asyncio import Throttled
from throttled.asyncio.contrib.otel import OTelHook
# Create OTelHook with a meter from the OTel API.
# The actual metrics backend (e.g., Prometheus, OTLP) is configured separately
# via opentelemetry-sdk or your framework's setup.
meter = get_meter("throttled-example")
# Create an async rate limiter with OTelHook attached.
throttle = Throttled(key="/api/ping", quota="5/m", hooks=[OTelHook(meter)])
async def main() -> None:
# First 5 requests are allowed.
for i in range(5):
result = await throttle.limit("/api/ping")
print(f"Request {i + 1}: {'denied' if result.limited else 'allowed'}")
# But 6th request is denied.
result = await throttle.limit("/api/ping")
print(f"Request 6: {'denied' if result.limited else 'allowed'}")
# π OTelHook records the following metrics:
# - throttled.requests (Counter): number of rate limit checks
# - throttled.duration (Histogram): latency of rate limit checks
# Attributes: key, algorithm, store_type, result ("allowed" / "denied")
if __name__ == "__main__":
asyncio.run(main())
Metrics#
OTelHook records the following metrics:
Metric Name |
Type |
Description |
|---|---|---|
|
Counter |
Number of rate limit checks (with |
|
Histogram |
Duration of rate limit checks in seconds |
All metrics include these attributes:
Attribute |
Description |
|---|---|
|
The rate limit key (e.g., β/api/usersβ, βuser:123β) |
|
Algorithm used (e.g., βtoken_bucketβ, βfixed_windowβ) |
|
Storage backend (e.g., βmemoryβ, βredisβ) |
|
Result of rate limit check: βallowedβ or βdeniedβ |
Configuration#
Both OTelHook (sync and async) require a Meter instance:
from opentelemetry import metrics
from throttled.contrib.otel import OTelHook
meter = metrics.get_meter("my-service", "1.0.0")
hook = OTelHook(meter)
The async OTelHook uses the same Meter instance and follows the same dependency injection pattern:
from opentelemetry import metrics
from throttled.asyncio.contrib.otel import OTelHook
meter = metrics.get_meter("my-service", "1.0.0")
hook = OTelHook(meter)
Architecture#
throttled-py depends only on opentelemetry-api, not the SDK:
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β throttled-py β
β Dependency: opentelemetry-api (interface only) β
β Output: counter.add(), histogram.record() β
ββββββββββββββββββββββββββββββββ¬βββββββββββββββββββββββββββββββββββββββ
β
v
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β Your Application β
β You decide how to collect and export metrics: β
β - Console, OTLP, Prometheus, Datadog, etc. β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
This keeps the library lightweight. You have full control over how metrics are exported.
Exporter Examples#
Below are examples of how you might configure exporters in your application. These are just examples - use whatever setup fits your infrastructure.
Console (for debugging)#
from opentelemetry import metrics
from opentelemetry.sdk.metrics import MeterProvider
from opentelemetry.sdk.metrics.export import ConsoleMetricExporter, PeriodicExportingMetricReader
reader = PeriodicExportingMetricReader(ConsoleMetricExporter())
provider = MeterProvider(metric_readers=[reader])
metrics.set_meter_provider(provider)
# Now get_meter() will use this provider
meter = metrics.get_meter("my-app")
OTLP#
from opentelemetry import metrics
from opentelemetry.sdk.metrics import MeterProvider
from opentelemetry.sdk.metrics.export import PeriodicExportingMetricReader
from opentelemetry.exporter.otlp.proto.grpc.metric_exporter import OTLPMetricExporter
reader = PeriodicExportingMetricReader(
OTLPMetricExporter(endpoint="http://collector:4317")
)
provider = MeterProvider(metric_readers=[reader])
metrics.set_meter_provider(provider)
Prometheus#
from opentelemetry import metrics
from opentelemetry.sdk.metrics import MeterProvider
from opentelemetry.exporter.prometheus import PrometheusMetricReader
from prometheus_client import start_http_server
start_http_server(port=8000)
reader = PrometheusMetricReader()
provider = MeterProvider(metric_readers=[reader])
metrics.set_meter_provider(provider)
# Metrics available at http://localhost:8000/metrics
Grafana Dashboard Example#
Example PromQL queries for Grafana:
# Request rate by key
sum(rate(throttled_requests_total[5m])) by (key)
# Denial rate
sum(rate(throttled_requests_total{result="denied"}[5m]))
/
sum(rate(throttled_requests_total[5m]))
# Allowed vs Denied comparison
sum(rate(throttled_requests_total{result="allowed"}[5m]))
sum(rate(throttled_requests_total{result="denied"}[5m]))
# P95 duration by algorithm
histogram_quantile(0.95, rate(throttled_duration_bucket[5m])) by (algorithm)