Skip to main content

Lightweight state breadcrumb tracking with context propagation

Project description

statecrumbs

Lightweight runtime state breadcrumbs for crash debugging, observability, and post‑mortem analysis. Capture small, structured “crumbs” of state as your code executes - thread‑safe, context‑aware, and bounded in memory.

Features

  • Bounded ring buffer (no log spam) via max_crumbs
  • Zero‑overhead crash hook that never blocks the real exception
  • Context propagation with scoped values (request_id, pipeline_id, etc.)
  • Thread‑safe recording
  • Structured output as JSON or readable text
  • Crash-time snapshot: dump recent state automatically on unhandled exceptions
  • Works alongside the standard logging module (no handler/level setup required)
  • Tiny API and zero config by default; add where it matters
  • ISO‑8601 timestamps and optional thread names

How this differs from logging

  • Logging emits an unbounded text stream; statecrumbs keeps a small, in‑memory, structured buffer.
  • Logging focuses on levels/formatters/handlers; statecrumbs focuses on concise event breadcrumbs.
  • Logging requires configuration to persist/rotate; statecrumbs is optimized for post‑mortem snapshots.
  • Scoped context is automatic via a context manager; no adapters or extra plumbing.
  • Designed to be complementary: keep your logs, add statecrumbs for crisp crash/debug context.

Installation

From PyPI:

pip install statecrumbs

Quick start

from statecrumbs import enable, crumb

# Install crash hook and set a small buffer
enable(on_crash="stderr", max_crumbs=5)

crumb("start", user="alice")
crumb("before_error", x=10)

1 / 0  # On crash, recent crumbs are dumped to stderr as JSON

Common use cases

  • Debugging flaky tests and CI failures
  • Post‑mortem triage of production crashes
  • Tracing requests across service boundaries
  • Understanding data pipeline stages and failures
  • Capturing lightweight state without committing to full logging

Example crash output (shape):

[
  {
    "event": "start",
    "ts": "2025-12-30T10:23:45.123456+00:00",
    "thread": "MainThread"
  },
  {
    "event": "before_error",
    "ts": "2025-12-30T10:23:45.234567+00:00",
    "thread": "MainThread",
    "x": "10"
  }
]

Core API

  • enable(**kwargs): Configure and install the crash hook.

    • Supported config keys (defaults shown):
      • max_crumbs: int = 50
      • on_crash: str = "stderr" (one of "stderr" | "file" | "none" | callable)
      • output_file: str = "statecrumbs.json" (used when on_crash == "file")
      • include_timestamp: bool = True (adds ISO‑8601 ts field)
      • include_thread: bool = True (adds thread field)
  • crumb(event: str, **data): Record a breadcrumb.

    • Adds fields:
      • event: required string
      • ts: ISO‑8601 UTC timestamp (e.g. 2025-12-30T10:23:45.123456+00:00) if enabled
      • thread: current thread name if enabled
      • context: dict of scoped values if any (see Context below)
      • any **data you pass, stored via repr(value)
    • Never raises; no‑op for empty/invalid events.
  • snapshot(): Return a copy of the current crumb buffer (oldest to newest).

  • clear(): Clear the buffer.

  • dump(): Print current crumbs as pretty JSON to stdout.

Crash behavior

When the crash hook is installed (via enable()), an unhandled exception will trigger a dump of snapshot() according to on_crash:

  • "stderr": write JSON to stderr (default)
  • "file": write to output_file
  • "none": do nothing
  • callable: a callable receiving the crumbs list

The hook is defensive and won’t block or replace the real exception.

Context

Use the context manager to add scoped metadata automatically to all crumbs recorded within the scope:

from statecrumbs import crumb, snapshot, clear
from statecrumbs.context import context

clear()

with context(request_id="req-999", user="bob"):
    crumb("request_start")
    with context(step="db_query"):
        crumb("query_started")
        crumb("query_finished")
    crumb("request_end")

for c in snapshot():
    print(c)

Each crumb within the context(...) scope includes a context field containing the merged key/values active at capture time.

Available context helpers:

  • context(**kwargs): context manager that merges keys for the duration of the block.
  • get_context(): returns the current context dict (for advanced use).

Formatting and export

  • formatter.format_json(crumbs) -> str: pretty JSON string.
  • formatter.format_text(crumbs) -> str: simple text format.
  • export.export_json(path): write the current snapshot() to a file as pretty JSON.

Thread‑safety and buffer semantics

  • All crumb operations are protected by a lock; concurrent crumb() calls are safe.
  • The buffer is a ring with capacity max_crumbs. When full, oldest crumbs are dropped.

Examples in repo

  • examples/basic_crumb.py: minimal breadcrumbs + crash behavior.
  • examples/data_ingest.py: staged pipeline with success/failure paths, context scoping, and JSON export.
  • examples/web_request.py: request‑scoped context and nested steps.
  • examples/basic_context.py: simple request‑scoped context.
  • examples/nested_context.py: nested contexts across multiple steps.
  • examples/prod_scale_pipeline.py: large batch pipeline with many crumbs and buffer tuning.

Run an example:

python examples/basic_crumb.py

Testing

The project includes a small test suite:

pip install -e .[test]  # or just ensure pytest is available
pytest

Data model

A typical crumb looks like:

{
  "event": "transform_completed",
  "ts": "2025-12-30T10:23:45.987654+00:00",
  "thread": "MainThread",
  "context": {"pipeline_id": "ingest-42", "stage": "transform"},
  "records": "4"
}

Notes:

  • ts is ISO‑8601 UTC with timezone offset.
  • Arbitrary fields come from **data and are stored using repr(value) to avoid serialization errors.

FAQ

  • Why both context and ad‑hoc fields?
    • Use context(...) for durable, cross‑cutting metadata (request IDs, stages). Use **data for event‑specific details.
  • What happens if values aren’t JSON‑serializable?
    • Values from **data are stored via repr(value). If repr fails, the value falls back to "<unserializable>".
  • Can I disable timestamps or thread names?
    • Yes, set include_timestamp=False or include_thread=False in enable(...).

License

MIT © Hemapriya N

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

statecrumbs-0.1.2.tar.gz (7.2 kB view details)

Uploaded Source

Built Distribution

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

statecrumbs-0.1.2-py3-none-any.whl (7.6 kB view details)

Uploaded Python 3

File details

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

File metadata

  • Download URL: statecrumbs-0.1.2.tar.gz
  • Upload date:
  • Size: 7.2 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.13.2

File hashes

Hashes for statecrumbs-0.1.2.tar.gz
Algorithm Hash digest
SHA256 a815e0a23a58f74c44b9c32dfcf8227c10a5f65521be4bec9a2524a1f57b9505
MD5 1941e980efeb9b23d9e69cb1f7bff94a
BLAKE2b-256 a274d687ba13d757e72295793a7eeed390d330f346c963dfacd5470586f227fe

See more details on using hashes here.

File details

Details for the file statecrumbs-0.1.2-py3-none-any.whl.

File metadata

  • Download URL: statecrumbs-0.1.2-py3-none-any.whl
  • Upload date:
  • Size: 7.6 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.13.2

File hashes

Hashes for statecrumbs-0.1.2-py3-none-any.whl
Algorithm Hash digest
SHA256 9cefc5aca47458564b2a0d6f49995d2b7c162261f4c21c5c23606a089efc43d6
MD5 67ae7588d019ff4cd700f734b5bfafac
BLAKE2b-256 1c3c99fcdf8309eb0067bc73e313b624bb3599653b9abf27e78886e09283acad

See more details on using hashes here.

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