AioZipkin middleware for Starlette/FastApi
aiozipkin
- async compatible zipkin libraryFollow instructions at https://www.jaegertracing.io/docs/latest/getting-started/
$ docker run -d --name jaeger \
-e COLLECTOR_ZIPKIN_HTTP_PORT=9411 \
-p 5775:5775/udp \
-p 6831:6831/udp \
-p 6832:6832/udp \
-p 5778:5778 \
-p 16686:16686 \
-p 14268:14268 \
-p 9411:9411 \
jaegertracing/all-in-one:latest
Trace queries at http://localhost:16686/
import uvicorn
from starlette.applications import Starlette
from starlette.responses import JSONResponse
from starlette.routing import Route
from starlette_zipkin import ZipkinMiddleware
routes = [
Route("/", JSONResponse({"status": "OK"})),
]
app = Starlette(debug=True, routes=routes)
app.add_middleware(ZipkinMiddleware)
if __name__ == "__main__":
uvicorn.run(app, host="0.0.0.0", port=8000, log_level="info", reload=True)
By default the client emits to http://localhost:9411
.
All traffic is captured and available at http://localhost:16686/
To instrument tracing at lower levels, two helper functions are available:
get_root_span
- returns the span instance corresponding to current requestget_tracer
- returns the tracer instance corresponding to current requesttrace
- create span in the traceimport json
import asyncio
import uvicorn
from starlette.applications import Starlette
from starlette.responses import JSONResponse
from starlette.routing import Route
from starlette_zipkin import (
ZipkinMiddleware,
ZipkinConfig,
trace,
B3Headers,
UberHeaders
)
async def homepage(request):
with trace("NewParent") as child_span:
# ! if headers not explicitly provided,\
# root span from middleware injects headers
# and becomes the parent for subsequet services
headers = child_span.context.make_headers()
child_span.kind("SERVER")
# possible span kinds
# CLIENT = "CLIENT"
# SERVER = "SERVER"
# PRODUCER = "PRODUCER"
# CONSUMER = "CONSUMER"
child_span.annotate(
"Child, sleeps for 1, injects headers and becomes parent"
)
await asyncio.sleep(1)
return JSONResponse({"hello": "world"}, headers=headers)
routes = [
Route("/", JSONResponse({"status": "OK"})),
Route("/homepage", homepage),
]
app = Starlette(debug=True, routes=routes)
config = ZipkinConfig(
host="localhost",
port=9411,
service_name="service_name",
sample_rate=1.0,
inject_response_headers=True,
force_new_trace=False,
json_encoder=json.dumps,
header_formatter=B3Headers
)
app.add_middleware(ZipkinMiddleware, config=config)
if __name__ == "__main__":
uvicorn.run(app, host="0.0.0.0", port=8000, log_level="info", reload=True)
This way we are able to followup at the call from a different service. Here we use the same server, but pass the tracing headers to subsequent calls to demonstrate future spans:
To change the middleware configuration, provide a config object (here with default values being as shown)
import json
from starlette_zipkin import ZipkinMiddleware, ZipkinConfig, B3Headers
config = ZipkinConfig(
host="localhost",
port=9411,
service_name="service_name",
sample_rate=1.0,
inject_response_headers=True,
force_new_trace=False,
json_encoder=json.dumps,
header_formatter=B3Headers
)
app = Starlette()
app.add_middleware(ZipkinMiddleware, config=config)
where:
host = "localhost"
port = 9411
service_name = "service_name"
sample_rate = 1.0
inject_response_headers = True
force_new_trace = False
True
, does not create child traces if incoming request contains tracing headersjson_encoder=json.dumps
header_formatter=B3Headers
uber-trace-id
format.