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

Uploaded Python 3

File details

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

File metadata

  • Download URL: spektr-0.1.1.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.1.tar.gz
Algorithm Hash digest
SHA256 46514c3b2756411af33d2c9f374b7607fb5f4fd05a250ac338aa34703d82d47b
MD5 b969cd1f0da56c1dd5568f02c2abe5ba
BLAKE2b-256 7502d2cb7fa4bf5b691b57bf072e98bcf1103f41d11226bbecd3af8605b25d5c

See more details on using hashes here.

Provenance

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

File metadata

  • Download URL: spektr-0.1.1-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.1-py3-none-any.whl
Algorithm Hash digest
SHA256 ee0988123aa8471d3c7f42bf3ea484a46bd8405943b66de3773f2f5df2a047ec
MD5 23ccb9c73e31e77e5e183ddb3295badc
BLAKE2b-256 fe70afd9027ad6380b0c37c1440768bdcce7e1a3318e55905b4dcb765d9bfd41

See more details on using hashes here.

Provenance

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