Skip to main content

Send unhandled Python exceptions straight to a Telegram chat. No backend, no account, no database — just your own bot token.

Project description

devalerts

PyPI Downloads Python versions License: MIT Tests Ruff mypy

Русская версия

Send unhandled Python exceptions straight to a Telegram chat — the moment they happen, on your phone. No backend, no account, no database — just your own bot token.

import devalerts

devalerts.init(bot_token="123456:ABC-DEF...", chat_id=123456789)

That's the whole setup. Two minutes with @BotFather and every unhandled crash — including ones raised in threads — lands in your chat instead of a log file nobody's watching.

Why devalerts

  • Zero infrastructure. No SaaS signup, no ingestion server, no API key to manage beyond your own Telegram bot token. State lives in a local SQLite file you already own.
  • One line to install, one line to wire up. init() installs the hook and gets out of the way.
  • Not spam. Errors are grouped by fingerprint and rate-limited per group, so a crash loop sends one message, not a thousand.
  • Framework-aware. Ships an ASGI middleware for FastAPI/Starlette apps, where the default excepthook would never even see a request error.
  • Small and typed. No dependencies, ships py.typed, ~450 lines total — short enough to read in one sitting before you trust it with your errors.

Install

uv add devalerts

(or pip install devalerts if you're not using uv)

Usage

  1. Create a bot with @BotFather and get its token.

  2. Message your bot once (or add it to a group) so it's allowed to message you back.

  3. Get your chat id — message @userinfobot, or call https://api.telegram.org/bot<TOKEN>/getUpdates after step 2 and read message.chat.id.

  4. Verify it's wired up correctly before touching any code:

    uv run devalerts test --bot-token 123456:ABC-DEF... --chat-id 123456789
    
  5. In your app, as early as possible:

import devalerts

devalerts.init(bot_token="123456:ABC-DEF...", chat_id=123456789)

That's it — any unhandled exception (including ones raised in threads) now also lands in your Telegram chat.

Grouping, rate limiting, and the dashboard

Exceptions are grouped by fingerprint (exception type + file + line where it was raised) in a local SQLite file (~/.devalerts/state.db). Each group sends at most one Telegram message per rate_limit_seconds (default 300); repeats inside that window are counted but not sent, and the next message for that group says how many were skipped. Old groups (untouched for 7 days) are pruned automatically. Configure the window via init():

devalerts.init(bot_token="...", chat_id=123456789, rate_limit_seconds=60)

See what's grouped and what's currently rate-limited:

uv run devalerts dashboard

devalerts dashboard output

Pass --json for machine-readable output. Silence a noisy group without touching code — the ID column accepts any unique prefix:

uv run devalerts mute abc12345
uv run devalerts unmute abc12345
uv run devalerts clear abc12345   # or: devalerts clear --all

Unmuting resends the next occurrence with the accumulated skip count, same as a rate-limit window expiring.

Error groups that keep piling up while suppressed are chronic: each such resend doubles the effective rate_limit_seconds for that group (capped at 8x), so a crash loop backs itself off instead of paging you every window forever. A group that goes quiet and reappears once resets to the base rate immediately. The dashboard shows the active multiplier (● sending ×4).

Context: hostname and tags

Every alert automatically includes the sending host, so you can tell which process/server it came from when one bot serves several:

devalerts.init(bot_token="...", chat_id=123456789, tags={"env": "production"})
🔴 ValueError: boom
🖥️ prod-web-2 (env=production)

Add ad-hoc tags to a single call — they override init()'s tags on a key collision:

devalerts.report(extra={"request_id": "abc123"})

@devalerts.capture(extra={"job": "nightly-sync"})
def run_job(): ...

Manually reporting a caught exception

try:
    risky_call()
except Exception:
    devalerts.report()  # sends the currently-handled exception

or:

with devalerts.capture():
    risky_call()  # reports on exception, then re-raises

capture also works as a decorator, so you don't need to touch a function's body at all:

@devalerts.capture()
def risky_call():
    ...

FastAPI / Starlette / any ASGI app

init()'s excepthook won't see request errors — the framework already catches them internally to return a 500 response, so nothing "unhandled" ever reaches the process. Use the ASGI middleware instead:

app.add_middleware(devalerts.ASGIMiddleware)

Only exceptions that actually escape as server errors get reported — routing 404s and raised HTTPExceptions are already turned into responses by the framework before the middleware sees them.

Celery

Same problem as ASGI apps: init()'s excepthook never sees exceptions raised inside a task, because Celery catches them itself to record the task's FAILURE state. Call init_celery() in addition to init():

devalerts.init(bot_token="...", chat_id=123456789)
devalerts.init_celery()

This connects to Celery's task_failure signal (fired once a task has genuinely failed — retries exhausted or none configured, so retried tasks don't spam an alert per attempt) and reports through the same grouping/rate-limiting/redaction path as everything else, tagged with the task name and id automatically. Requires Celery to already be installed in the worker process — it's imported lazily, not a devalerts dependency.

Why not Sentry?

If you already run Sentry/Rollbar/etc., keep using it — devalerts isn't a replacement. It's for the side project, internal tool, or small service that doesn't have (and doesn't want) that infrastructure: no account to create, no SDK to configure, no server to trust — just a bot token you already control.

devalerts Sentry-style tracker
Setup one bot token account + project + SDK config
Backend none — Telegram only hosted or self-hosted service
Where alerts land your Telegram chat a web dashboard
Grouping / rate limiting yes, local SQLite yes, server-side
Search, trends, releases no yes

FAQ

Works with FastAPI / Starlette? Yes — use ASGIMiddleware, since the default excepthook never sees request errors.

Works with Docker? Yes, nothing container-specific. Just make sure ~/.devalerts/ (the dedup state file) is either writable inside the container or a mounted volume if you want dedup state to survive restarts — it self-recreates otherwise.

Works with threads? Yes — init() installs both sys.excepthook and threading.excepthook.

Works with Celery / background workers? Yes — call init_celery() in addition to init() to catch exceptions raised inside tasks, which the excepthook alone won't see.

Works on Windows / Linux / macOS? Yes — stdlib only (urllib, sqlite3, threading), no OS-specific code paths.

Privacy & Security

  • The only network call devalerts makes is to api.telegram.org — no telemetry, no analytics, nothing else phones home.
  • No third-party server and no devalerts-run backend — messages go straight from your process to your own Telegram bot.
  • No accounts, no signup, no API key beyond the bot token you create and control yourself.
  • Basic secret redaction only (a few common token/key patterns) — do not rely on this for sensitive production data; scrub what you can before it ever reaches an exception message.
  • If Telegram delivery fails after retrying, the alert (already redacted, if redact=True) is appended to ~/.devalerts/failed.log instead of being dropped — clean it up like any other local log file.

What this does NOT do (by design)

  • Grouping/rate limiting is local and in-process only (SQLite file, no server) — the dashboard is a CLI table, not a web UI.
  • No backend, no accounts — each user runs their own bot.

Roadmap

  • Web dashboard (hosted, optional — the local CLI dashboard stays either way)
  • Slack delivery
  • Discord delivery
  • Email delivery

Development

uv sync --group dev
uv run pre-commit install

pre-commit runs ruff check, ruff format, and mypy before each commit. Run the full check manually with:

uv run pytest
uv run pre-commit run --all-files

License

MIT — see LICENSE.

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

devalerts-0.2.0.tar.gz (14.9 kB view details)

Uploaded Source

Built Distribution

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

devalerts-0.2.0-py3-none-any.whl (18.3 kB view details)

Uploaded Python 3

File details

Details for the file devalerts-0.2.0.tar.gz.

File metadata

  • Download URL: devalerts-0.2.0.tar.gz
  • Upload date:
  • Size: 14.9 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: uv/0.11.29 {"installer":{"name":"uv","version":"0.11.29","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

File hashes

Hashes for devalerts-0.2.0.tar.gz
Algorithm Hash digest
SHA256 4883d45c623220d0d602db49202e02beb8fdcb10991ae949e49c1d349871ffff
MD5 fd0a48b60a43d6f662a4bd8694787b21
BLAKE2b-256 8e0d31b7188c524133980b7a7ad15a30faf6dfb22b9461cd9f056e8185cc0c70

See more details on using hashes here.

File details

Details for the file devalerts-0.2.0-py3-none-any.whl.

File metadata

  • Download URL: devalerts-0.2.0-py3-none-any.whl
  • Upload date:
  • Size: 18.3 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: uv/0.11.29 {"installer":{"name":"uv","version":"0.11.29","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

File hashes

Hashes for devalerts-0.2.0-py3-none-any.whl
Algorithm Hash digest
SHA256 160cf1cc68f272d739e5754401938d95f3c9d84ce9633a792c6ea3cd68a31300
MD5 1cf76a51ea669784075bd959c39fafcc
BLAKE2b-256 4c24418ce1954b8a51c4dd9561455798c26a7c1a4c6d02640d8fe4e92062348a

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