# django-telegram-notifier

[![PyPI version](https://img.shields.io/pypi/v/django-telegram-notifier.svg)](https://pypi.org/project/django-telegram-notifier/)
[![Python versions](https://img.shields.io/pypi/pyversions/django-telegram-notifier.svg)](https://pypi.org/project/django-telegram-notifier/)
[![Django versions](https://img.shields.io/badge/django-5.2%20%7C%206.0-blue.svg)](https://pypi.org/project/django-telegram-notifier/)
[![License: MIT](https://img.shields.io/badge/license-MIT-green.svg)](https://github.com/ganiyevuz/django-telegram-notifier/blob/main/LICENSE)

Catch unhandled Django exceptions and send formatted error reports to Telegram — like a lightweight Sentry. Includes full traceback as a `.py` file attachment with syntax highlighting, smart exception classification, optional database logging with a rich admin dashboard, and async support.

---

## Features

- **Automatic exception catching** via middleware (sync & async)
- **Non-blocking** — Telegram API calls run in a background thread, never slowing down your response
- **Smart exception classification** — auto-assigns level & severity based on exception type
- **Formatted Telegram messages** with emoji levels, request context, and traceback preview
- **Full traceback as `.py` file** — Telegram renders Python syntax highlighting
- **Exception filtering** — ignore specific exceptions, paths, or use custom filter functions
- **Optional database logging** — store every exception with request details, severity, status
- **Rich admin dashboard** — stats, charts, action toolbar, prev/next navigation, Monaco editors
- **Light & dark mode** admin — auto-detects system/Django admin theme
- **Configurable logger** — use stdlib logging, loguru, structlog, or any custom logger
- **Celery support** — optionally offload reporting to a Celery task
- **Decorator** for wrapping individual functions/views
- **Multi-chat support** — send to multiple Telegram chats
- **Proxy support** for restricted environments
- **Management command** to clean up old exception logs

---

## Quick Start

### 1. Install

```bash
pip install django-telegram-notifier
```

### 2. Get a Telegram Bot Token

1. Open Telegram, search for [@BotFather](https://t.me/BotFather)
2. Send `/newbot` and follow the prompts
3. Copy the bot token (e.g. `123456:ABC-DEF1234ghIkl-zyx57W2v1u123ew11`)
4. Send a message to your bot, then visit `https://api.telegram.org/bot<TOKEN>/getUpdates` to find your `chat_id`

### 3. Configure

Add to your Django `settings.py`:

```python
INSTALLED_APPS = [
    # ...
    "telegram_notifier",
]

MIDDLEWARE = [
    # ... other middleware ...
    "telegram_notifier.middleware.GlobalExceptionReporterMiddleware",
]

TELEGRAM_NOTIFIER = {
    "BOT_TOKEN": "your-bot-token",
    "CHAT_IDS": ["your-chat-id"],
}
```

### 4. Migrate (only if using database logging)

```bash
python manage.py migrate
```

That's it. Any unhandled exception will now send a notification to your Telegram chat.

---

## Settings

All settings go inside the `TELEGRAM_NOTIFIER` dictionary in your Django settings:

```python
TELEGRAM_NOTIFIER = {
    # Required
    "BOT_TOKEN": "your-bot-token",
    "CHAT_IDS": ["chat-id-1", "chat-id-2"],

    # Optional (defaults shown)
    "ENVIRONMENT": None,            # e.g. "production", "staging" — shown in messages
    "PROXY": None,                  # e.g. "socks5://127.0.0.1:1080"
    "MESSAGE_MAX_LENGTH": 4000,     # max message length (Telegram limit)
    "STORE_EXCEPTIONS": False,      # save exceptions to database
    "CLEANUP_DAYS": 30,             # days to keep exception logs

    # Filtering
    "IGNORE_EXCEPTIONS": [],        # exception class names to skip
    "IGNORE_PATHS": [],             # URL path prefixes to skip
    "FILTER": None,                 # custom callable(exc, request) -> bool

    # Logging
    "LOGGER": None,                 # custom logger instance (loguru, structlog, etc.)

    # Async / Celery
    "CELERY_TASK": None,            # Celery task for offloading (see Celery section)
}
```

| Setting | Type | Default | Description |
|---------|------|---------|-------------|
| `BOT_TOKEN` | `str` | **required** | Telegram Bot API token |
| `CHAT_IDS` | `list[str]` | **required** | Telegram chat IDs to send notifications to |
| `ENVIRONMENT` | `str \| None` | `None` | Environment name shown in messages (e.g. `"production"`) |
| `PROXY` | `str \| None` | `None` | Proxy URL for Telegram API requests |
| `MESSAGE_MAX_LENGTH` | `int` | `4000` | Maximum message length before truncation |
| `STORE_EXCEPTIONS` | `bool` | `False` | Save exceptions to `ExceptionLog` model |
| `CLEANUP_DAYS` | `int` | `30` | Days to keep exception logs (used by cleanup command) |
| `IGNORE_EXCEPTIONS` | `list[str]` | `[]` | Exception class names to skip (checks MRO) |
| `IGNORE_PATHS` | `list[str]` | `[]` | URL path prefixes to skip |
| `FILTER` | `callable \| None` | `None` | Custom filter function `(exc, request) -> bool` |
| `LOGGER` | `object \| None` | `None` | Custom logger instance (any object with `.info()`, `.error()`) |
| `CELERY_TASK` | `Task \| None` | `None` | Celery task to offload reporting (see below) |

---

## Exception Filtering

Control which exceptions get reported with three layers of filtering:

### Ignore by exception class

```python
TELEGRAM_NOTIFIER = {
    # ...
    "IGNORE_EXCEPTIONS": [
        "Http404",
        "PermissionDenied",
        "DisallowedHost",
        "Throttled",
    ],
}
```

This checks the exception's MRO, so `"DatabaseError"` also catches `IntegrityError`, `OperationalError`, etc.

### Ignore by path

```python
TELEGRAM_NOTIFIER = {
    # ...
    "IGNORE_PATHS": [
        "/health",
        "/favicon.ico",
        "/.well-known",
    ],
}
```

### Custom filter function

```python
TELEGRAM_NOTIFIER = {
    # ...
    "FILTER": lambda exc, request: (
        # Only report in production
        not getattr(request, 'path', '').startswith('/admin/')
    ),
}
```

Return `True` to report, `False` to skip. Filters are evaluated in order: `IGNORE_EXCEPTIONS` -> `IGNORE_PATHS` -> `FILTER`.

---

## Smart Exception Classification

Exceptions are automatically classified by level and severity based on their type:

| Category | Level | Severity | Examples |
|----------|-------|----------|----------|
| System failures | `critical` | `critical` | `MemoryError`, `RecursionError`, `SystemExit` |
| Infrastructure | `error` | `high` | `DatabaseError`, `ConnectionError`, `TimeoutError` |
| Client/validation | `warning` | `low` | `Http404`, `ValidationError`, `PermissionDenied`, `Throttled` |
| Default (bugs) | `error` | `moderate` | `TypeError`, `KeyError`, `AttributeError` |

You can override auto-classification by passing explicit `level` and `severity` to `report_exception()`.

---

## Usage

### Middleware (recommended)

The middleware catches all unhandled exceptions automatically. Place it after Django's built-in middleware:

```python
MIDDLEWARE = [
    "django.middleware.security.SecurityMiddleware",
    "django.contrib.sessions.middleware.SessionMiddleware",
    "django.middleware.common.CommonMiddleware",
    "django.middleware.csrf.CsrfViewMiddleware",
    "django.contrib.auth.middleware.AuthenticationMiddleware",
    "django.contrib.messages.middleware.MessageMiddleware",
    "telegram_notifier.middleware.GlobalExceptionReporterMiddleware",
]
```

The middleware is **async-capable** — works with both WSGI and ASGI servers.

### Decorator

Wrap individual functions or views:

```python
from telegram_notifier import telegram_exception_notifier

@telegram_exception_notifier
def my_task():
    # if this raises, it gets reported to Telegram
    do_something_risky()

# Also works with async functions
@telegram_exception_notifier
async def my_async_task():
    await do_something_risky()
```

### Manual Reporting

Report exceptions programmatically:

```python
from telegram_notifier import report_exception

try:
    do_something()
except Exception as exc:
    report_exception(exc, level="critical", severity="high")
```

**Parameters:**

| Parameter | Type | Default | Description |
|-----------|------|---------|-------------|
| `exc` | `Exception` | **required** | The exception to report |
| `request` | `HttpRequest \| None` | `None` | Django request (adds path, method, body, user) |
| `body` | `bytes \| None` | `None` | Request body |
| `level` | `str \| None` | auto | Overrides auto-classification |
| `severity` | `str \| None` | auto | Overrides auto-classification |

---

## Custom Logger

By default, the package uses Python's stdlib `logging`. You can plug in any logger:

```python
# loguru
from loguru import logger
TELEGRAM_NOTIFIER = { ..., "LOGGER": logger }

# structlog
import structlog
TELEGRAM_NOTIFIER = { ..., "LOGGER": structlog.get_logger() }

# silent (disable all logging)
TELEGRAM_NOTIFIER = { ..., "LOGGER": None }
```

Any object with `.info()` and `.error()` methods works.

---

## Non-Blocking & Celery

By default, all Telegram API calls and database writes run in a **background daemon thread** — your request response is never delayed by slow network or Telegram downtime.

For projects using Celery, you can offload reporting to a Celery task instead:

```python
# tasks.py
from celery import shared_task
from telegram_notifier.report import _do_report

@shared_task
def send_telegram_report(
    exc_class_name, exc_message, message,
    traceback_content, level, severity, request_data,
):
    _do_report(
        exc_class_name=exc_class_name,
        exc_message=exc_message,
        message=message,
        traceback_content=traceback_content,
        effective_level=level,
        effective_severity=severity,
        request_data=request_data,
        body=None,
    )
```

```python
# settings.py
from myapp.tasks import send_telegram_report

TELEGRAM_NOTIFIER = {
    # ...
    "CELERY_TASK": send_telegram_report,
}
```

When `CELERY_TASK` is set, reporting is dispatched via `.delay()` instead of a thread — giving you retries, monitoring, and rate limiting through your existing Celery infrastructure.

---

## Telegram Message Format

Each notification is sent as a **document** (`.py` file with full traceback) with a **caption** containing:

```
🔴 ValueError in production

Message: invalid literal for int()
Timestamp: 2026-03-11 12:30:45

▸ Traceback (preview)
    value = int(user_input)
ValueError: invalid literal for int() with base 10: 'abc'

▸ Request
  Path: /api/users/
  Method: POST
  Body: {"name": "test"}
  User: admin@example.com
```

**Level emojis:** ⚪ debug · 🔵 info · 🟡 warning · 🔴 error · ⛔ critical

The attached `.py` file contains the **complete traceback** with Python syntax highlighting in Telegram.

---

## Database Logging & Admin Dashboard

Enable with `"STORE_EXCEPTIONS": True`. This creates an `ExceptionLog` entry for every reported exception.

### Admin List View

- **Summary stats** — exceptions in last 24h/7d, by level, unresolved count
- **Charts** — exceptions timeline (24h bar chart) and level breakdown (doughnut)
- **Colored badges** — level, severity, method, environment, status
- **Inline status editing** — change status directly from the list
- **Bulk actions** — Mark as Resolved, Mark as Ignored, Mark as Seen, Resend to Telegram
- **Date hierarchy** — quick date navigation
- **Filters** — by level, severity, status, sent, environment, method, date

### Admin Detail View

- **Action toolbar** — Resolve, Seen, Ignore, Resend to Telegram (AJAX, no reload)
- **Occurrence stats** — how many times this exception occurred in 24h/7d/total
- **Prev/Next navigation** — browse exceptions chronologically
- **Monaco editors** — syntax-highlighted traceback (Python), headers/body/params (JSON)
- **Copy/Format/Minify buttons** — on all JSON sections with tooltip feedback
- **Collapsible headers section**
- **Light & dark mode** — auto-detects Django admin theme and system preference

### ExceptionLog Fields

| Field | Type | Description |
|-------|------|-------------|
| `exception_class` | `CharField` | e.g. `"ValueError"` |
| `message` | `TextField` | Exception message |
| `traceback` | `TextField` | Full traceback |
| `level` | `CharField` | `debug`, `info`, `warning`, `error`, `critical` |
| `severity` | `CharField` | `low`, `moderate`, `high`, `critical` |
| `status` | `CharField` | `new`, `seen`, `resolved`, `ignored` |
| `path` | `CharField` | Request path |
| `method` | `CharField` | HTTP method |
| `query_params` | `JSONField` | Query string parameters |
| `body` | `TextField` | Request body |
| `user_info` | `CharField` | Authenticated user string |
| `ip_address` | `GenericIPAddressField` | Client IP (with X-Forwarded-For support) |
| `headers` | `JSONField` | Request headers (sensitive headers filtered) |
| `view_name` | `CharField` | Django view name |
| `hostname` | `CharField` | Server hostname |
| `environment` | `CharField` | Environment from settings |
| `is_sent` | `BooleanField` | Whether Telegram notification was sent |
| `created_at` | `DateTimeField` | When the exception occurred |

### Querying Exceptions

```python
from telegram_notifier import ExceptionLog, Status, Level

# Unresolved errors
ExceptionLog.objects.filter(status=Status.NEW, level=Level.ERROR)

# Errors in the last 24 hours
from django.utils.timezone import now
from datetime import timedelta
ExceptionLog.objects.filter(created_at__gte=now() - timedelta(hours=24))

# Mark as resolved
ExceptionLog.objects.filter(pk=1).update(status=Status.RESOLVED)
```

### Cleanup Command

Delete old exception logs:

```bash
# Use CLEANUP_DAYS setting (default: 30)
python manage.py cleanup_exceptions

# Override with custom days
python manage.py cleanup_exceptions --days=7
```

Schedule with cron for automatic cleanup:

```bash
0 3 * * * python /path/to/manage.py cleanup_exceptions
```

---

## Public API

```python
from telegram_notifier import (
    # Core
    report_exception,              # Report an exception manually
    notify_error_via_telegram,     # Send raw message to Telegram
    classify_exception,            # Get (level, severity) for an exception

    # Message building
    build_exception_message,       # Build formatted HTML message
    build_traceback_content,       # Get full traceback string

    # Middleware & decorator
    GlobalExceptionReporterMiddleware,
    telegram_exception_notifier,

    # Models & choices
    ExceptionLog,                  # Database model (lazy-loaded)
    Level,                         # debug, info, warning, error, critical
    Severity,                      # low, moderate, high, critical
    Status,                        # new, seen, resolved, ignored
)
```

---


## Requirements

- Python >= 3.11
- Django >= 5.2
- httpx >= 0.28.1

---

## Development

```bash
git clone https://github.com/ganiyevuz/django-telegram-notifier.git
cd django-telegram-notifier

# Install dependencies
uv sync --group dev

# Copy env file and add your bot token
cp .env.example .env

# Run tests
pytest

# Run linter
ruff check .
```

---

## License

MIT
