Skip to main content

Zero-config Python observability. Logging, tracing, and error tracking in one.

Project description

spektr

Observability that doesn't suck.

CI PyPI Python

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

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

spektr-0.1.0.tar.gz (100.6 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

spektr-0.1.0-py3-none-any.whl (39.2 kB view details)

Uploaded Python 3

File details

Details for the file spektr-0.1.0.tar.gz.

File metadata

  • Download URL: spektr-0.1.0.tar.gz
  • Upload date:
  • Size: 100.6 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.7

File hashes

Hashes for spektr-0.1.0.tar.gz
Algorithm Hash digest
SHA256 d23ef238c86626eafff151d47c43cab9448f1ec7466875e5455b83a9bdae6319
MD5 d3fbf6d6949c923934f50f70bdf6d2b9
BLAKE2b-256 a6337a54413a73f220f2202ca91a4baf3dc3210b36ae054930d1b7102e87287b

See more details on using hashes here.

Provenance

The following attestation bundles were made for spektr-0.1.0.tar.gz:

Publisher: release.yml on olemeyer/spektr

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file spektr-0.1.0-py3-none-any.whl.

File metadata

  • Download URL: spektr-0.1.0-py3-none-any.whl
  • Upload date:
  • Size: 39.2 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.7

File hashes

Hashes for spektr-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 3503db8bb4b386ddeec9f95273700c901504ec591566ca586f4eeeed8f18dba6
MD5 853e012508f8e95d509c1120c7e61879
BLAKE2b-256 af8a771f470dd87bed8303d5ef4d6ddb34465d47462fc01fbfc527fa0a03d254

See more details on using hashes here.

Provenance

The following attestation bundles were made for spektr-0.1.0-py3-none-any.whl:

Publisher: release.yml on olemeyer/spektr

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page