Pular para o conteúdo
← Voltar para projetos

Laravel Discord Logger

Send Laravel logs to Discord with deduplication, configurable error grouping, rate limiting, async delivery and a graceful no-op when the webhook URL is missing.

Laravel Discord Logger

#Laravel Discord Logger

Buy Me A Coffee

Latest Version on Packagist Tests Total Downloads License

Send Laravel logs to Discord — built for production. A Monolog channel with deduplication, configurable error grouping, rate limiting, async delivery with 429 backoff, and a graceful no-op when the webhook URL is missing.

#Installation

composer require jeffersongoncalves/laravel-discord-logger

Publish the config (optional):

php artisan vendor:publish --tag="laravel-discord-logger-config"

#Configuration

Add a channel to config/logging.php:

'discord' => [
    'driver' => 'custom',
    'via'    => \JeffersonGoncalves\DiscordLogger\Logger::class,
    'level'  => env('LOG_DISCORD_LEVEL', 'error'),
    'url'    => env('LOG_DISCORD_WEBHOOK_URL'),
],

Stack it onto your default channel so errors fan out:

'stack' => [
    'driver'   => 'stack',
    'channels' => ['single', 'discord'],
    'ignore_exceptions' => false,
],

Set the webhook (leave empty in local/testing — nothing will be sent, nothing will break):

LOG_DISCORD_WEBHOOK_URL=https://discord.com/api/webhooks/xxx/yyy

#How the improvements work

#Error grouping (fingerprint)

config/discord-logger.phpgrouping.strategy:

  • message — group by message text
  • level_message — group by level + message
  • exception (default) — group by exception class + file + line
  • a callback fn (\Monolog\LogRecord $record): string for full control

With grouping.normalize on, volatile tokens (numbers, UUIDs, hashes) are stripped before hashing, so User 123 not found and User 456 not found collapse together.

#Deduplication

Within deduplication.window seconds, only the first occurrence of a fingerprint is sent. Repeats are counted silently. When the window closes, a single summary (🔁 occurred N×) is delivered — turn it off with deduplication.summary => false.

#Rate limiting

  • rate_limit.global — hard cap on total messages app-wide
  • rate_limit.per_fingerprint — extra guard against a single looping error

#Async delivery

Delivery runs through a queued job by default (queue.enabled). Discord 429s are retried respecting Retry-After; bad webhooks (4xx) fail fast instead of looping. Set queue.enabled => false to send inline (best-effort, errors swallowed).

This means a queue worker must be running (php artisan queue:work, Horizon, or supervisor) for Log::debug/info/error(...) calls to actually reach Discord. discord-logger:test sends inline and bypasses the queue entirely, so it will succeed even with no worker running — don't use it alone to confirm real logging works. If you don't run a worker (or don't want to), set DISCORD_LOGGER_QUEUE=false.

#State store

Dedup + rate-limit counters live in the cache store named by store (null = default). Use Redis in production for atomic counters.

#Per-level webhooks & mentions

Route a level to its own channel and ping someone when it matters:

'webhooks' => [
    'EMERGENCY' => env('DISCORD_LOGGER_WEBHOOK_ALERTS'),
    'CRITICAL'  => env('DISCORD_LOGGER_WEBHOOK_ALERTS'),
],

'mentions' => [
    'EMERGENCY' => '@here',
    'CRITICAL'  => '<@&123456789012345678>', // role id
],

allowed_mentions is set automatically, so @here / @everyone / role / user pings actually fire.

#Context redaction

Sensitive data is masked before it ever reaches Discord, via two complementary strategies:

  • Key matching (redact) — case-insensitive key fragments whose values are masked. Defaults cover password, secret, token, authorization, api_key, apikey. Scrubbing recurses into nested arrays and the public properties of objects carried in the context.
  • Value patterns (redact_value_patterns) — PCRE regexes matched against scalar values, so a secret leaking under an innocuous key (e.g. url, auth) — or inside the message text and the stacktrace — is still caught. Defaults cover Bearer tokens and JWTs; leave the array empty to disable value-based redaction.

#Fallback channel & safety

The handler never throws and is guarded against logging-while-logging recursion. If delivery throws, the error is swallowed; set fallback_channel (e.g. 'single') to record those failures instead of losing them.

#Troubleshooting

discord-logger:test works, but Log::debug/info/error(...) never shows up in Discord.

The test command sends inline, straight to the webhook. Real log calls go through a queued job by default (see Async delivery), so nothing is delivered until a queue worker processes it. Fixes:

  • Run a worker: php artisan queue:work (or Horizon/supervisor in production), or
  • Set DISCORD_LOGGER_QUEUE=false to send inline instead (no worker needed).

This is especially easy to hit on a fresh Laravel install (Laravel 11+, including 13): QUEUE_CONNECTION defaults to database, but no worker runs automatically — jobs just sit pending in the jobs table.

Also check the channel's level in config/logging.php — if it's set above the level you're logging at (e.g. level => 'error' while calling Log::debug(...)), Monolog filters the record before it ever reaches the handler.

#Commands

# Publish the config + optionally star the repo
php artisan discord-logger:install

# Send a test message to verify the webhook
php artisan discord-logger:test --channel=discord

#Testing

composer test

#Changelog

Please see CHANGELOG for more information on what has changed recently.

#Contributing

Please see CONTRIBUTING for details.

#Security Vulnerabilities

Please review our security policy on how to report security vulnerabilities.

#Credits

#License

The MIT License (MIT). See License File.

Nova versão disponível.