Zero-config observability SDK for Python services — auto-instruments FastAPI with traces, metrics, and logs via OpenTelemetry
Project description
zerotel
Zero-config observability SDK for Python services.
Add one line. Get distributed traces, Prometheus metrics, and structured JSON logs — all correlated by trace_id and ready to query in Grafana.
from fastapi import FastAPI
from zerotel import Zerotel
app = FastAPI()
Zerotel(app, service_name="my-api") # done
What you get
| Signal | What's captured |
|---|---|
| Traces | Root span per request — method, route, status, latency, error |
| Traces | @trace child spans, nested correctly under the request |
| Traces | SQLAlchemy async queries as child spans (with sanitised SQL) |
| Metrics | zerotel_requests_total — counter by method/route/status |
| Metrics | zerotel_request_duration_seconds — histogram |
| Metrics | zerotel_requests_in_flight — gauge |
| Logs | Structured JSON with trace_id, span_id, service auto-injected |
Installation
pip install zerotel
Optional extras:
pip install "zerotel[sqlalchemy]" # async SQLAlchemy query tracing
pip install "zerotel[flask]" # Flask WSGI adapter
pip install "zerotel[all]" # everything
Quickstart
Minimal
from fastapi import FastAPI
from zerotel import Zerotel
app = FastAPI()
Zerotel(app, service_name="payments-api")
Full configuration
from fastapi import FastAPI
from zerotel import Zerotel, ZerotelConfig
app = FastAPI()
Zerotel(app, config=ZerotelConfig(
service_name="payments-api",
service_version="2.1.0",
otlp_endpoint="http://otel-collector:4317",
enable_traces=True,
enable_metrics=True,
enable_logging=True,
exclude_paths=["/health", "/metrics"], # skip these from tracing
log_request_body=False, # keep off in prod (PII risk)
trace_sample_rate=1.0, # lower in high-volume prod
))
@trace decorator
Add a named child span to any function — works on both async def and def:
from zerotel import trace
@trace(name="send-email")
async def send_email(user_id: int) -> None:
... # this entire function becomes a child span under the request span
@trace
def compute_score(data: list[float]) -> float:
... # span name defaults to "module.compute_score"
Reading the current trace ID
from zerotel import get_trace_id, get_span_id
def my_helper() -> None:
tid = get_trace_id() # 32-char hex or "0" * 32 if outside a request
sid = get_span_id() # 16-char hex
FastAPI dependency injection
from fastapi import FastAPI
from zerotel.integrations.fastapi import TraceIdDep, RequestContextDep
app = FastAPI()
@app.get("/profile")
async def get_profile(trace_id: TraceIdDep) -> dict:
return {"trace_id": trace_id}
SQLAlchemy async query tracing
from sqlalchemy.ext.asyncio import create_async_engine
from zerotel.integrations.sqlalchemy import instrument_sqlalchemy
engine = create_async_engine("postgresql+asyncpg://user:pw@localhost/mydb")
instrument_sqlalchemy(engine)
# Every query is now a child span with sanitised SQL as an attribute
Local observability stack
The docker/ folder contains a ready-to-use Docker Compose stack:
| Service | URL | Purpose |
|---|---|---|
| OTel Collector | — | Receives OTLP, fans out |
| Grafana Tempo | http://localhost:3200 | Trace storage |
| Prometheus | http://localhost:9090 | Metrics storage |
| Grafana Loki | http://localhost:3100 | Log aggregation |
| Grafana | http://localhost:3000 | Unified UI |
cd docker/
docker compose up -d
# Run your service
OTLP_ENDPOINT=http://localhost:4317 uvicorn myapp:app --reload
# Open Grafana (admin / admin)
open http://localhost:3000
Configuration reference
| Field | Type | Default | Description |
|---|---|---|---|
service_name |
str |
"unknown-service" |
Appears on every trace, metric, and log |
service_version |
str |
"0.0.0" |
Attached to trace resources |
otlp_endpoint |
str |
"http://localhost:4317" |
OTLP gRPC collector address |
enable_traces |
bool |
True |
Export OpenTelemetry spans |
enable_metrics |
bool |
True |
Expose Prometheus /metrics |
enable_logging |
bool |
True |
Configure structlog JSON |
exclude_paths |
list[str] |
["/health", "/metrics"] |
Paths to skip from instrumentation |
log_request_body |
bool |
False |
Capture request body on span (PII risk) |
trace_sample_rate |
float |
1.0 |
Fraction of requests to sample (0.0–1.0) |
metrics_endpoint |
str |
"/metrics" |
Path for Prometheus scrape endpoint |
Development
git clone https://github.com/Kamalesh-Kavin/zerotel
cd zerotel
uv sync --extra dev
# Run tests
uv run pytest
# Lint
uv run ruff check src/
uv run ruff format src/
See CONTRIBUTING.md for full guidelines.
Philosophy
"If you cannot explain the code without the AI, you haven't learned it yet."
Every file in this project is heavily commented. The goal is for you to be able to read any file cold and understand exactly what it does and why.
License
MIT — see LICENSE.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file zerotel-0.1.0.tar.gz.
File metadata
- Download URL: zerotel-0.1.0.tar.gz
- Upload date:
- Size: 19.7 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.10.11 {"installer":{"name":"uv","version":"0.10.11","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
aa51f913bca56dab52aa1e4d722c956e06175cec24af23d8a4d80c6537ebaea3
|
|
| MD5 |
0aef990d14f3ea0930ae9f2322aba60d
|
|
| BLAKE2b-256 |
c872345d19b293fcbe68290780c3e30a9cf7e18ac2f592d55ab5e8b37c800ce2
|
File details
Details for the file zerotel-0.1.0-py3-none-any.whl.
File metadata
- Download URL: zerotel-0.1.0-py3-none-any.whl
- Upload date:
- Size: 26.0 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.10.11 {"installer":{"name":"uv","version":"0.10.11","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
55d1389bb80a45ce92a68736d86ff8b19d681deb288e161878004e68c1a138de
|
|
| MD5 |
aefd58aa9f4634152970403ce4a03562
|
|
| BLAKE2b-256 |
fbcfd4348c07fd25cc83ca49d5a27f8e6f77cd887c159a0ad78ee326862b038b
|