Observability that doesn't suck.
Guide · API Reference · Architecture
Be honest — you've used print() for debugging because configuring Python's logging module felt like filing taxes. And when someone said "add tracing," you closed the browser tab.
spektr is logging, tracing, and error tracking in a single import. It replaces loguru, structlog, the OpenTelemetry SDK, and Sentry's error capture — with zero configuration.
pip install spektr
from spektr import log
log("server started", port=8080, env="production")
14:23:01.123 INFO server started port=8080 env='production' main.py:3
That's it. No getLogger(). No handlers. No YAML files. Structured data, colors, source locations — out of the box.
Tracing
Add @trace to see where time goes:
from spektr import trace
@trace
def handle_order(order_id: int):
user = fetch_user(user_id=order_id) # also @trace
charge_payment(amount=99.99) # also @trace
send_confirmation(to="ole@test.com") # also @trace
handle_order 86.5ms order_id=42
├── fetch_user 10.1ms user_id=42
├── charge_payment 50.1ms amount=99.99
└── send_confirmation 20.1ms to='ole@test.com'
Logs inside spans automatically get trace_id and span_id — no wiring needed:
@trace
def handle_order(order_id: int):
log("fetching user") # trace_id + span_id attached
user = fetch_user(order_id)
log("charging", amount=99.99) # same trace
Here's the thing — those are real OpenTelemetry spans. spektr uses OTel as its tracing backbone, so every @trace creates a proper OTel span with W3C context propagation. You just don't have to think about it.
Point it at any OTLP-compatible backend and your traces just show up — without changing a single line of application code:
# Self-hosted (Jaeger, Grafana Tempo)
OTEL_EXPORTER_OTLP_ENDPOINT=http://collector:4318 python app.py
# Managed (Dash0, Grafana Cloud, Honeycomb, Datadog, etc.)
OTEL_EXPORTER_OTLP_ENDPOINT=https://ingress.eu-west-1.aws.dash0.com \
OTEL_EXPORTER_OTLP_HEADERS="Authorization=Bearer <token>" \
python app.py
Exception Tracking
Rich tracebacks with local variables at the point of failure:
@log.catch
def process_payment(order_id: int, amount: float):
balance = get_balance(order_id)
charge(balance, amount)
14:23:05 ERROR InsufficientFunds: 12.50 < 99.99 payments.py:8
╭── InsufficientFunds ──────────────────────────────────────╮
│ billing.py:17 in charge │
│ balance = 12.50 │
│ amount = 99.99 │
│ │
│ InsufficientFunds: 12.50 < 99.99 │
╰───────────────────────────────────────────────────────────╯
No more staring at a naked traceback wondering what x was.
Context That Flows
Context propagates through function calls and async boundaries — no thread-local hacks:
with log.context(request_id="abc-123", user_id=42):
log("processing") # has request_id + user_id
do_something() # called functions inherit the context
log("done") # still has them
FastAPI / Starlette
One line to instrument every HTTP request:
import spektr
app = FastAPI()
spektr.install(app)
Every request automatically gets a unique request_id, a trace span, W3C context extraction, completion logging with status and duration, and request metrics. Also installs rich exception hooks and routes stdlib logging (uvicorn, SQLAlchemy, etc.) through spektr.
Production
In dev you get colored console output. Set the endpoint and it switches to structured JSON with full OTel export:
# Self-hosted collector
OTEL_EXPORTER_OTLP_ENDPOINT=http://collector:4318 python app.py
# Managed backend
OTEL_EXPORTER_OTLP_ENDPOINT=https://ingress.eu-west-1.aws.dash0.com \
OTEL_EXPORTER_OTLP_HEADERS="Authorization=Bearer <token>" \
python app.py
{"ts":"2026-03-22T14:23:01+00:00","level":"info","msg":"order created","order_id":42,"trace_id":"4bf92f3577b34da6a3ce929d0e0e4736","span_id":"00f067aa0ba902b7"}
Works with any OTLP-compatible backend: Dash0, Grafana Cloud, Honeycomb, Datadog, Jaeger, Grafana Tempo, SigNoz, Axiom, and more.
Everything Else
log("user {name} signed up", name="ole", plan="pro")
# 14:23:01 INFO user ole signed up name='ole' plan='pro'
try:
db.execute(query)
except DatabaseError:
log.exception("query failed", table="orders")
# 14:23:02 ERROR query failed table='orders' error_type='DatabaseError' error_message='timeout'
with log.time("db query"):
rows = db.fetch_all()
# 14:23:03 INFO db query duration_ms=42.1
log.once("cache ready") # only the first call emits
log.every(1000, "heartbeat") # every 1000th call
log.sample(0.01, "verbose detail") # ~1% probability
log.once().warn("deprecated API") # chaining picks the level
log.count("http.requests", method="GET")
log.gauge("queue.depth", 42)
log.histogram("latency_ms", 123.4)
log.emit_metrics()
# 14:23:04 INFO metrics http.requests=1 queue.depth=42 latency_ms=123.4
with log.progress("importing", total=10000) as p:
for item in items:
process(item)
p.advance()
# importing: 100%|████████████████████| 10000/10000 [00:02<00:00, 3571.43it/s]
# 14:23:07 INFO importing completed total=10000 duration_ms=2800.0
db = log.bind(component="database")
db("connected", host="primary.db")
# 14:23:08 INFO connected component='database' host='primary.db'
log("auth", password="secret123", api_key="sk-abc")
# 14:23:09 INFO auth password='***' api_key='***'
headers = trace.inject() # {"traceparent": "00-4bf92f35...-01"}
context = trace.extract(headers) # context.trace_id, context.parent_id
configure(health_path="/healthz")
# GET /healthz → 200 {"status": "ok", "service": "order-api"}
with capture() as logs:
create_order(42)
assert logs[0].message == "order created"
configure(sampler=RateLimitSampler(per_second=100))
configure(sinks=[DatadogSink(), SlackAlertSink()])
See the Guide for the full walkthrough and API Reference for every method.
Requirements
- Python 3.10+
- Dependencies:
rich,opentelemetry-api,opentelemetry-sdk - Optional:
pip install spektr[otlp]for OTLP export,spektr[tqdm]for progress bars
License
MIT
Release files for spektr 0.1.3
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| spektr-0.1.3.tar.gz | 104.2 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| spektr-0.1.3-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 143.7 kB
Release files / spektr-0.1.3.tar.gz
| Download URL | spektr-0.1.3.tar.gz |
|---|---|
| Size | 104.2 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
f203a5ec2e25cd361f5c818423fdf9b2ee56d269d2fbc9db220cbd7970826ce6
|
|
BLAKE2b-256 checksum How to use checksums |
b8b7a5da81907b422408b337aea3d6b9248a4e640fa091a0d74c0e98610a960a
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.7
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Mar 23, 2026.
Transparency logRelease files / spektr-0.1.3-py3-none-any.whl
| Download URL | spektr-0.1.3-py3-none-any.whl |
|---|---|
| Size | 39.5 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
0d9ef9f568cc07740ec84fc04db91e6045ad391926044f6fa2af08cddb12c225
|
|
BLAKE2b-256 checksum How to use checksums |
2fb6674cc37399ed83665c814223696cbb8e1fef5e3cde627274c77a18bf4a10
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.7
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Mar 23, 2026.
Transparency log