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.2.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.2-py3-none-any.whl (39.2 kB view details)

Uploaded Python 3

File details

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

File metadata

  • Download URL: spektr-0.1.2.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.2.tar.gz
Algorithm Hash digest
SHA256 4d422f7fe45ac4664991c7fe96dfab811da7dfb393bb6f08f6d4aa9f08cba6d5
MD5 8887487434008edc230f260b147ee93e
BLAKE2b-256 e4a7495efeeb2b53219e714bd50382e151c3e727f270cfece17bf21835733f94

See more details on using hashes here.

Provenance

The following attestation bundles were made for spektr-0.1.2.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.2-py3-none-any.whl.

File metadata

  • Download URL: spektr-0.1.2-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.2-py3-none-any.whl
Algorithm Hash digest
SHA256 819586a8e6098552dc14df902c8db20222f9f75ccea9d24d430c80a7e4baecac
MD5 237281175b4107a463c82bf44bb5d2dc
BLAKE2b-256 e8dedb1d8afde1b640790d5fa2f0adfc145a2cac4a5d81306fb243c6c4e60ed0

See more details on using hashes here.

Provenance

The following attestation bundles were made for spektr-0.1.2-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