jeffersongoncalves/laravel-telegram-logger
A Laravel package that adds a Telegram logging channel, sending application logs directly to Telegram chats via a bot. Configure the bot token, chat ID, and log level to receive real-time notifications and monitor errors, warnings, and other events from your Laravel app.
This is a monolog handler + queued delivery pipeline, not a general-purpose Telegram SDK. The first thing to understand is that it plugs into Laravel's logging stack as a channel — everything else (dedup, grouping, rate limiting, formatting) happens inside that channel.
composer require jeffersongoncalves/laravel-telegram-logger
php artisan vendor:publish --tag=telegram-logger-config
.env. Missing either produces a graceful no-op, not an exception, so a misconfigured install looks identical to a quiet one. Verify immediately.config/logging.php as a channel and point a stack at it. Don't make it the default channel on day one; add it alongside your existing single/daily channel so you keep local disk logs while you evaluate volume.php artisan telegram-logger:test to confirm the plumbing, then php artisan telegram-logger:test --exception to see what a real throwable renders as (sections, clamping, the stacktrace.txt reply document).First real use case: a production stack that keeps daily for greppable history and adds telegram at error level only. That gives you paging for free without drowning the chat in info noise.
Before going live, check the version constraints: PHP 8.2+ and Laravel 12.61+ / 13. On an older Laravel this package simply won't install — there's no backport path.
Channel-per-destination, not per-level. Because routing (per-level chat ids, forum topics, silent levels) is config-driven inside the handler, resist the urge to define five separate logging channels. One telegram channel with a routing map is easier to reason about and keeps dedup/rate-limit state in one place.
Stack it, don't replace it. The intended production pipeline from its Discord sibling is stack: [daily, telegram]. The package's HTML formatting is tuned for humans on a phone; your disk logs remain the forensic source of truth.
Choose your grouping key deliberately. The three meaningful strategies:
level+message — good default for high-traffic apps; collapses the same 500 across many users into one alert.exception — best for jobs and console commands where the class + message is stable and you want one alert per root cause.message — noisiest; only useful if you interpolate identifiers out of messages first.Lean on the occurred N× roll-up. Combined with the default 20/min global rate limit, this is what makes the package survivable during an incident. Tune the per-error limit rather than the global one — you want one error to be capped hard while unrelated errors still get through.
Exploit the reply-document behaviour. The 4096-char clamp means the message body is a summary; the full stacktrace arrives as stacktrace.txt replying to it. Train yourself to open the reply rather than the message when debugging — the message will truncate mid-frame on deep traces.
Route by level, and use silent levels. warning and notice to a quiet ops topic with disable_notification: true, error+ to the on-call topic with sound. Forum topics need the chat to actually be a forum — a topic id against a normal group will fail delivery (and, per the async design, fail fast as a 4xx).
Mentions for ownership. @username mentions on error+ only. Mentioning on every warning gets the feature muted within a week.
Redaction is a safety net, not a licence. Keys and value patterns are redacted and bot tokens are always masked — but the Exception section includes exception messages, which routinely embed user input, SQL fragments, and URLs with query strings. Add your app-specific patterns (tokens, emails, card numbers) to the redaction config before enabling error level.
Queue it, and size the worker accordingly. Delivery is a queued job, so the Telegram API round-trip never blocks a request. But that job is now in your queue alongside real work: on a busy worker, alert delivery can lag behind a backlog. If alerting latency matters, give the logger its own queue/connection so a stuck default queue doesn't silence your pager.
Test the failure path in staging. Point the config at an invalid chat id and confirm the 4xx path reports Telegram's description to your fallback channel. That message ("chat not found", "bot was blocked by the user") is the single most useful diagnostic this package produces, and it's the thing that will save you at 3am.
The graceful no-op is the sharpest edge. Missing token or chat id means zero errors and zero messages. Nothing in your application logs will hint that alerting is dead. Treat a periodic telegram-logger:test (scheduled, with the result alerted elsewhere) as mandatory, and prefer failing loudly in staging by asserting on the test command's exit code.
The HTML rendering is escaped, but the truncation is not frame-aware. A clamped message can cut mid-<pre> block. Telegram is tolerant of this in practice, but if you see malformed messages, that's why — not a formatting bug in your code.
Config keys live in two places. The handler config lives in the package config; the channel entry lives in config/logging.php. A typo in via or a missing handler reference produces a silently broken channel. After editing, clear config cache — config:cache in production will happily serve your stale pre-fix values.
parameters.retry_after is a Telegram field, not yours. It's read from the 429 response body. If you self-host a proxy in front of the Bot API that rewrites the response, 429 handling breaks. Same for anything that mangles the 5xx classification — the retry-with-backoff vs fail-fast split hinges on the status code, and a proxy that returns 200 with an error body will make failures vanish.
Async means logs can be lost on a crash. If the worker dies between the log write and job execution, that alert is gone. Pair with a disk channel (which you're doing anyway) and don't rely on Telegram as your audit trail.
Dedup state is process-local. Deduplication and grouping windows live in the handler's memory for the lifetime of the worker/request. On a multi-worker or Octane setup, each worker dedups independently — you'll get up to N copies of an error, not one global copy. The rate limit is the backstop here, not the dedup. If you need cluster-wide dedup, this package isn't the tool.
Context and Extra sections can be huge. Laravel's default context (user, request id, etc.) is usually fine, but the Extra section carries whatever array you pass to Log::error($msg, $context). Passing a full Eloquent model or a large payload will blow through the 4096 clamp and push useful info off the message. Log identifiers, not objects.
Livewire / Job / Command sections are conditional. They only appear when the log record originates inside those contexts. If your Job section is always empty, the exception is escaping from a place that doesn't carry job context — usually a queued listener rather than the job itself. Don't chase it as a config bug.
Version floors are hard. Laravel 12.61+ is a specific minor, not "12.x". Pin that in CI and in your composer.json if you rely on this in production; a routine composer update on a slightly older app will resolve to a different version or fail outright.
Extension points are thin by design. There's no custom formatter or transport registration — if you need a different message shape, you're editing published views/config or forking. Conversely, that means upgrades are low-risk, which is worth something for a component wired into your error path.
How can I help you explore Laravel packages today?