Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Laravel Telegram Logging Laravel Package

grkamil/laravel-telegram-logging

Send Laravel application logs straight to Telegram with this Monolog handler. Configure a bot token and chat ID, choose log levels, and receive instant error and warning notifications in your team chat—simple setup, no extra services.

View on GitHub
Deep Wiki
Context7

Getting Started

1. Install and wire up the channel. The package registers a custom Monolog channel driver named telegram. Install it, then add a channel entry to config/logging.php:

composer require grkamil/laravel-telegram-logging
// config/logging.php
'channels' => [
    'telegram' => [
        'driver'  => 'telegram',
        'token'   => env('TELEGRAM_BOT_TOKEN'),
        'chat_id' => env('TELEGRAM_LOG_CHAT_ID'),
        'level'   => env('TELEGRAM_LOG_LEVEL', 'error'),
    ],
],
TELEGRAM_BOT_TOKEN=123456:AA...
TELEGRAM_LOG_CHAT_ID=-1001234567890
TELEGRAM_LOG_LEVEL=error

2. Get the two credentials right before writing any code. Create the bot with @BotFather (/newbot) to get the token. Then send the bot a message (or add it to the group as a member and post something), and call https://api.telegram.org/bot<TOKEN>/getUpdates to read the chat.id. Group/supergroup IDs are negative (-100…); public channels can be addressed as @channelname. The bot must be a member of the target chat — this is the single most common "why is nothing arriving" cause.

3. First use case: production exception alerting. The highest-value use of this package is a dedicated alert channel that is not part of your normal stack, called explicitly only for things a human must see:

// bootstrap/app.php (Laravel 11+) or App\Exceptions\Handler::report()
$exceptions->report(function (Throwable $e) {
    if (app()->isProduction() && $e instanceof Reportable) {
        Log::channel('telegram')->critical($e->getMessage(), [
            'exception' => $e,
            'url'       => request()?->fullUrl(),
            'user_id'   => auth()->id(),
        ]);
    }
});

4. Verify from the CLI before trusting it in prod:

php artisan tinker
>>> Log::channel('telegram')->error('telegram logging smoke test');

Where to look first: config/logging.php (the channel array is the whole public API), then the package's handler class under vendor/grkamil/laravel-telegram-logging/src/ — it's a small Monolog AbstractProcessingHandler, and reading it tells you exactly which config keys your installed version honours (parse mode, templates, notification flags vary by release). Check the released tag against your Laravel/Monolog version in composer.lock — Monolog 3 (Laravel 10+) broke a lot of third-party handlers.

Implementation Patterns

Dedicated channel over stack inclusion. Adding telegram to your stack channel means every debug/info line in the app becomes an HTTP request and a chat message. Treat it as an alerting transport, not a log sink — call it explicitly, or if you must stack it, give the Telegram channel its own level => critical so only severe records reach it while daily still gets everything.

'stack' => [
    'driver'   => 'stack',
    'channels' => ['daily', 'telegram'], // telegram filtered by its own 'level'
],

Severity routing. Use warning/error for things that self-heal and critical/alert/emergency for paging-grade events. Since everything lands in one chat, the level in the message body is your only triage signal — keep the convention strict so on-call can skim.

Runtime-built channels for per-tenant or per-service routing. Log::build() lets you keep one bot token and fan out to different chats without editing config:

Log::build([
    'driver'  => 'telegram',
    'token'   => config('services.telegram.token'),
    'chat_id' => $tenant->telegram_chat_id,
    'level'   => 'error',
])->error('Import pipeline failed', ['tenant' => $tenant->id]);

Queue it if it's in a hot path. The handler posts synchronously inside the request/job lifecycle. Wrap calls in a small queued job so a slow or unavailable Telegram API never extends your response time:

class SendTelegramAlert implements ShouldQueue
{
    public function __construct(
        public string $message,
        public array $context = [],
        public string $level = 'error',
    ) {}

    public function handle(): void
    {
        Log::channel('telegram')->log($this->level, $this->message, $this->context);
    }
}

SendTelegramAlert::dispatch('Payment webhook failed', ['id' => $id])->onQueue('alerts');

Deduplicate per-request and per-window. Exception storms (a dead DB, a broken cron) otherwise produce thousands of identical messages. Laravel's RateLimiter or a cache lock is enough:

$key = 'tg:'.md5($e::class.'|'.$e->getMessage());

if (RateLimiter::attempt($key, 1, fn () => null, 300)) { // once per 5 min
    Log::channel('telegram')->critical($e->getMessage(), ['exception' => $e]);
}

Add ambient context once. Log::withContext([...]) applied in a middleware (app name, environment, request ID, deploy SHA) makes every Telegram message self-identifying across staging and production bots.

Use it for operational signals too, not just exceptions. Scheduler onFailure() hooks, failed queue-job failed() methods, deploy start/finish markers, and a daily heartbeat from Schedule::call() are all good fits — they're low-volume and high-signal.

Test without hitting the API. Override the channel in phpunit.xml (<env name="TELEGRAM_LOG_CHAT_ID" value=""/> plus a null driver) or Log::spy(), and assert on Log::shouldReceive('channel')->with('telegram') rather than asserting on a real HTTP call.

Gotchas and Tips

  • Telegram rate limits will bite you. Roughly 30 messages/second globally and ~20 messages/minute to a single group. Verbose logging during an incident turns into 429 Too Many Requests, and your alerts silently stop. Keep level at error or above, dedupe, and prefer one aggregated message over N.
  • 4096-character hard limit. Long stack traces get truncated or rejected. The handler doesn't split messages — include the exception message plus a short trace, and keep the full trace in daily/Sentry.
  • Parse-mode escaping. If your version/config enables HTML or Markdown parse modes, log bodies containing <, &, *, _ cause 400 Bad Request and the record is lost. Plain text is safest for arbitrary exception messages; sanitise or strip markup.
  • Failures can be silent. A chat not found, bot was blocked, or 403 from Telegram usually just means nothing appears in the chat. Always keep a daily channel as the primary destination, and check storage/logs/laravel.log for handler-level errors when a smoke test "works" but production alerts never arrive. Confirm bubble/exception handling in the handler so a Telegram outage can't throw inside your request.
  • Synchronous network I/O. Default Guzzle timeouts can stall a request for seconds if Telegram is unreachable. Queue the send, or at minimum run it only in report() paths rather than per log line.
  • Config caching. config/logging.php reading env() means php artisan config:clear (or a redeploy) after changing the token or chat ID — a stale cached config is a classic "I updated .env and it still posts to the old chat."
  • Never log secrets or PII. This ships data to a third party with unclear retention and to every chat member. Strip request bodies, headers, tokens, and emails from context before sending.
  • Chat ID nuance. Private chats need the user to have pressed /start first; supergroups need the bot added as a member (not just an admin invite left pending). If getUpdates is empty, the bot has never seen a message in that chat.
  • Not a notification system. If you need formatted, user-facing messages with buttons, use a Telegram notification channel package instead — this one emits raw log records only. Mixing the two responsibilities gets messy fast.
  • Extension point: if you need async dispatch, custom formatting, or retry/backoff, bypass the driver and register the package handler manually under 'driver' => 'monolog', 'handler' => ..., 'handler_with' => [...], or subclass it — the channel config array is just constructor arguments, so anything the handler accepts can be injected there.
  • Multiple recipients aren't supported natively; declare several channel entries (one per chat ID) and call whichever is relevant, rather than trying to jam a comma-separated list into chat_id.
Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
octoberrain/html
octoberrain/extension
octoberrain/exception
octoberrain/contracts
frankyso/ipaymu
3mdikram/worldmoneywords
snowsoft/config
itmosoft/laravel-subscriptions
larva/laravel-ai-qwen
diephp/sequences
users-au/socialite-provider
twidpay/twid-logger
mrnewport/laravel-plaid
mrnewport/laravel-repo
mrnewport/laravel-docsign
mrnewport/laravel-stow
mrnewport/laravel-flow
mrnewport/laravel-priceable
mrnewport/laravel-nestedterms
fredmeng/laravel-apidoc-generator