Timing ASGI
Timing-asgi is an ASGI middleware designed to automatically instrument ASGI endpoints and emit timing metrics. Developed by GRID, it's particularly useful for integrating with statsd-based cloud monitoring services like Datadog. The library currently supports ASGI3 and is actively maintained, with version 0.3.2 being the latest release.
Warnings
- breaking As of version 0.2.0, `timing-asgi` exclusively supports ASGI3. Applications requiring ASGI2 compatibility must use `timing-asgi` version 0.1.2.
- gotcha The middleware logs a debug message indicating that 'ASGI scope of type lifespan is not supported yet'. This means lifecycle events (startup/shutdown) of your ASGI application will not be instrumented by this middleware.
- gotcha When implementing a custom `TimingClient`, ensure it provides a `timing` method with the signature `(self, metric_name: str, timing: float, tags: list[str]) -> None`. The middleware performs a runtime check for this method, and its absence or incorrect signature will lead to errors.
- gotcha By default, if no `metric_namer` is provided, `PathToName` is used, which might generate less descriptive metric names (e.g., based on URL path). For framework-specific routing, a specialized namer like `StarletteScopeToName` is often preferred for more accurate metric names.
Install
-
pip install timing-asgi
Imports
- TimingMiddleware
from timing_asgi import TimingMiddleware
- TimingClient
from timing_asgi import TimingClient
- StarletteScopeToName
from timing_asgi.integrations import StarletteScopeToName
- PathToName
from timing_asgi.utils import PathToName
Quickstart
import logging
import uvicorn
from starlette.applications import Starlette
from starlette.responses import PlainTextResponse
from timing_asgi import TimingMiddleware, TimingClient
from timing_asgi.integrations import StarletteScopeToName
class PrintTimings(TimingClient):
"""A simple TimingClient that prints metrics to stdout."""
def timing(self, metric_name: str, timing: float, tags: list[str]) -> None:
print(f"Metric: {metric_name}, Time: {timing:.6f}, Tags: {tags}")
app = Starlette()
@app.route("/")
async def homepage(request):
return PlainTextResponse("hello world")
app.add_middleware(
TimingMiddleware,
client=PrintTimings(),
metric_namer=StarletteScopeToName(prefix="myapp", starlette_app=app)
)
if __name__ == "__main__":
logging.basicConfig(level=logging.INFO)
print("Running Uvicorn on http://127.0.0.1:8000. Access / to see timing metrics.")
uvicorn.run(app, host="127.0.0.1", port=8000)