Skip to main content

AioZipkin middleware for Starlette/FastApi

Test Coverage Package version

Features

  • Using sentry-asgi as a boilerplate
  • Client - based on aiozipkin - async compatible zipkin library
  • Server (any zipkin 2.0 compatible server will work) - Jaeger examples
  • Middleware tracing http traffic
  • Injecting tracing headers to responses
  • Extracting tracing headers from requests
  • Context variable with the span for every incoming request - possible to instrument tracing of lower level operations

Quick start

Run tracing server

Jaeger all-in-one

Follow 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/

Add middleware

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/

Advanced Tutorial

To instrument tracing at lower levels, two helper functions are available:

  • get_root_span - returns the span instance corresponding to current request
  • get_tracer - returns the tracer instance corresponding to current request
  • trace - create span in the trace
import 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:

Configuration

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"
    • default local host, needs to be set to point at the agent that collects traces (e.g. jaeger-agent)
  • port = 9411
    • default port, needs to be set to point at the agent that collects traces (e.g. jaeger-agent)
    • 9411 is default for zipkin client/agent (and jaeger-agent)
    • make sure to make accessible
  • service_name = "service_name"
    • name of the service
  • sample_rate = 1.0
    • zipkin sampling rate, default samples every call
  • inject_response_headers = True
    • automatically inject response headers
  • force_new_trace = False
    • if True, does not create child traces if incoming request contains tracing headers
  • json_encoder=json.dumps
    • json encoder can be provided, defaults to json dumps. It is used to format dictionaries for Jaeger UI.
  • header_formatter=B3Headers
    • defaults to b3 headers format. Can be switched to UberHeaders, which imply the uber-trace-id format.

Metadata

Release files for starlette-zipkin 0.3.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for starlette-zipkin 0.3.0
File Size Uploaded
starlette-zipkin-0.3.0.tar.gz 10.4 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for starlette-zipkin 0.3.0
File Interpreter ABI Platform
starlette_zipkin-0.3.0-py3-none-any.whl Python 3 none any Details

Total release size: 20.8 kB

Release files / starlette-zipkin-0.3.0.tar.gz

Download URL starlette-zipkin-0.3.0.tar.gz
Size 10.4 kB
Tags Source
SHA-256 checksum
How to use checksums
3c8270d2de79fb07e97b7ab1649550e3dfa9fd27c63a62f971cbe0cf24f4c1cc
BLAKE2b-256 checksum
How to use checksums
820248cb903e2ee36b3eb5e78c0a5c8589a6150bf6656f096335c5addd149999
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/3.2.0 pkginfo/1.8.3 requests/2.28.1 setuptools/65.3.0 requests-toolbelt/0.9.1 tqdm/4.64.1 CPython/3.8.10

Release files / starlette_zipkin-0.3.0-py3-none-any.whl

Download URL starlette_zipkin-0.3.0-py3-none-any.whl
Size 10.4 kB
Tags Python 3
SHA-256 checksum
How to use checksums
36144dbb6e358961861f2e9f2dc6d27a94f6d9980ad22a04fb7ea82f584a25b1
BLAKE2b-256 checksum
How to use checksums
6ea5329f1e88c69494451a4f8305767518a6160536db83beae294f4b0f94cb0f
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/3.2.0 pkginfo/1.8.3 requests/2.28.1 setuptools/65.3.0 requests-toolbelt/0.9.1 tqdm/4.64.1 CPython/3.8.10

Release history Release notifications | RSS feed

This release

0.3.0 This release

2 release files

0.2.4

2 release files

0.2.3

2 release files

0.2.2

2 release files

0.2.1

2 release files

0.2.0

2 release files

0.1.4

2 release files

0.1.3

2 release files

0.1.2

2 release files

0.1.1

2 release files

0.1.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page