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.3.tar.gz (104.2 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.3-py3-none-any.whl (39.5 kB view details)

Uploaded Python 3

File details

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

File metadata

  • Download URL: spektr-0.1.3.tar.gz
  • Upload date:
  • Size: 104.2 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.3.tar.gz
Algorithm Hash digest
SHA256 f203a5ec2e25cd361f5c818423fdf9b2ee56d269d2fbc9db220cbd7970826ce6
MD5 6c5cd3d9e6743d3cf079a8cd989a988c
BLAKE2b-256 b8b7a5da81907b422408b337aea3d6b9248a4e640fa091a0d74c0e98610a960a

See more details on using hashes here.

Provenance

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

File metadata

  • Download URL: spektr-0.1.3-py3-none-any.whl
  • Upload date:
  • Size: 39.5 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.3-py3-none-any.whl
Algorithm Hash digest
SHA256 0d9ef9f568cc07740ec84fc04db91e6045ad391926044f6fa2af08cddb12c225
MD5 7850d560fcaded879e4b855e6488499d
BLAKE2b-256 2fb6674cc37399ed83665c814223696cbb8e1fef5e3cde627274c77a18bf4a10

See more details on using hashes here.

Provenance

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