mahmoud-hamed / multichannel-logger
Send logs and notifications to Slack, Discord and Zoom webhooks from Laravel 11+.
Package info
github.com/mahmoud-hamed/multichannel-logger
pkg:composer/mahmoud-hamed/multichannel-logger
Requires
- php: ^8.2
- illuminate/contracts: ^11.0|^12.0
- illuminate/log: ^11.0|^12.0
- illuminate/notifications: ^11.0|^12.0
- illuminate/support: ^11.0|^12.0
- monolog/monolog: ^3.0
- saloonphp/laravel-plugin: ^4.0
- saloonphp/saloon: ^4.0
- spatie/laravel-data: ^4.0
Requires (Dev)
- larastan/larastan: ^3.0
- laravel/pint: ^1.0
- orchestra/testbench: ^9.0|^10.0
- pestphp/pest: ^3.0
- pestphp/pest-plugin-laravel: ^3.0
- phpstan/phpstan: ^2.0
- phpstan/phpstan-strict-rules: ^2.0
README
Send logs and notifications to Slack, Discord and Zoom webhooks from Laravel 11+.
Built on top of Monolog, Saloon and Spatie Laravel Data, this package exposes every channel as:
- A custom log channel —
Log::channel('slack')->error('...') - A notification channel —
$user->notify(new DeployFailed()) - A first-class messenger —
MultichannelLogger::slack()->send($message)
Requirements
- PHP 8.2+
- Laravel 11 or 12
Installation
composer require mahmoud-hamed/multichannel-logger
Publish the configuration file (optional):
php artisan vendor:publish --tag=multichannel-logger-config
Configuration
Set your webhook URLs in .env:
SLACK_WEBHOOK_URL=https://hooks.slack.com/services/xxxxx/xxxxx/xxxxx DISCORD_WEBHOOK_URL=https://discord.com/api/webhooks/xxxxx/xxxxx ZOOM_WEBHOOK_URL=https://webhooks.zoom.us/api/chat/v1/message?token=xxxxx
Or publish and edit config/multichannel-logger.php:
'webhooks' => [ 'slack' => env('SLACK_WEBHOOK_URL'), 'discord' => env('DISCORD_WEBHOOK_URL'), 'zoom' => env('ZOOM_WEBHOOK_URL'), ], 'defaults' => [ 'client' => [ 'timeout' => 15, // request timeout in seconds 'retry_times' => 2, // retries on 429/5xx responses 'retry_interval_ms' => 100, ], ],
Usage
Log channels
Register a channel in config/logging.php:
'channels' => [ 'slack' => [ 'driver' => 'custom', 'via' => \MahmoudHamed\MultichannelLogger\Slack\CreateSlackLogger::class, 'webhook_url' => env('SLACK_WEBHOOK_URL'), 'level' => 'debug', 'username' => 'Laravel', // optional 'emoji' => ':boom:', // optional 'channel' => '#ops', // optional, target channel 'retry_times' => 2, // optional 'retry_interval_ms' => 100, // optional 'ignore_exceptions' => true, // optional, never break the app ], 'discord' => [ 'driver' => 'custom', 'via' => \MahmoudHamed\MultichannelLogger\Discord\CreateDiscordLogger::class, 'webhook_url' => env('DISCORD_WEBHOOK_URL'), 'level' => 'debug', 'username' => 'Laravel', // optional 'avatar_url' => 'https://...', // optional ], 'zoom' => [ 'driver' => 'custom', 'via' => \MahmoudHamed\MultichannelLogger\Zoom\CreateZoomLogger::class, 'webhook_url' => env('ZOOM_WEBHOOK_URL'), 'to' => 'chat@company.com', // required, the chat target 'level' => 'debug', ], ],
Log away:
Log::channel('slack')->error('Payment failed', ['order_id' => $order->id]); Log::channel('discord')->critical('Database down', ['exception' => $e]); Log::channel('zoom')->info('Deploy finished');
By default a channel never throws — webhook failures are caught and ignored so they can't take your application down. Set ignore_exceptions => false on the channel config to let MahmoudHamed\MultichannelLogger\Exceptions\MultichannelLoggerException bubble up.
Notification channels
Add a toSlack(), toDiscord() or toZoom() method to your notification and return the matching message data:
use Illuminate\Notifications\Notification; use MahmoudHamed\MultichannelLogger\Discord\DiscordMessageData; use MahmoudHamed\MultichannelLogger\Discord\DiscordWebhookChannel; use MahmoudHamed\MultichannelLogger\Slack\SlackMessageData; use MahmoudHamed\MultichannelLogger\Slack\SlackWebhookChannel; use MahmoudHamed\MultichannelLogger\Zoom\ZoomMessageData; use MahmoudHamed\MultichannelLogger\Zoom\ZoomWebhookChannel; class DeployFailed extends Notification { public function via(object $notifiable): array { return [SlackWebhookChannel::class, DiscordWebhookChannel::class]; } public function toSlack(object $notifiable): SlackMessageData { return new SlackMessageData(text: 'Deploy failed', username: 'Deploy Bot'); } public function toDiscord(object $notifiable): DiscordMessageData { return new DiscordMessageData(content: 'Deploy failed'); } public function toZoom(object $notifiable): ZoomMessageData { return new ZoomMessageData(to: 'team@company.com', message: 'Deploy failed'); } }
The notifiable must implement Illuminate\Notifications\RoutesNotifications (the default Notifiable trait) and expose its webhook via routeNotificationFor:
public function routeNotificationForSlack(): ?string // snake_case { return $this->slack_webhook; } public function routeNotificationForDiscord(): ?string { return $this->discord_webhook; } public function routeNotificationForZoom(): ?string { return $this->zoom_webhook; }
The method can be routeNotificationForSlack, routeNotificationFor('slack', $notification), or routeNotificationForSlack($notification) — Laravel's usual notification routing applies. If no webhook is resolved, a MissingWebhookException is thrown.
Facade & messenger
Send raw messages from anywhere using the facade (falls back to the configured default webhook):
use MahmoudHamed\MultichannelLogger\Facades\MultichannelLogger; use MahmoudHamed\MultichannelLogger\Slack\SlackMessageData; MultichannelLogger::slack()->send(new SlackMessageData(text: 'Hello world')); // Override the webhook for a single call MultichannelLogger::discord('https://discord.com/api/webhooks/...')->send(new DiscordMessageData(content: 'Hi'));
Extending
Every integration is composed of four small pieces, all behind contracts:
| Concern | Contract | Slack default |
|---|---|---|
| Message payload | MahmoudHamed\MultichannelLogger\Contracts\WebhookMessage |
Slack\SlackMessageData |
| HTTP transport | MahmoudHamed\MultichannelLogger\Contracts\WebhookMessenger |
Slack\SlackMessenger |
| Log formatting | MahmoudHamed\MultichannelLogger\Contracts\LogMessageFormatter |
Slack\SlackFormatter |
| Log channel | MahmoudHamed\MultichannelLogger\Logging\WebhookLoggerFactory |
Slack\CreateSlackLogger |
For example, a custom Slack formatter:
use Monolog\LogRecord; use MahmoudHamed\MultichannelLogger\Contracts\LogMessageFormatter; use MahmoudHamed\MultichannelLogger\Slack\SlackMessageData; class MySlackFormatter implements LogMessageFormatter { public function format(LogRecord $record): SlackMessageData { return new SlackMessageData(text: "[{$record->level->getName()}] {$record->message}"); } }
Register it by building the channel manually:
'channels' => [ 'slack' => [ 'driver' => 'custom', 'via' => function (array $config) { $messenger = new \MahmoudHamed\MultichannelLogger\Slack\SlackMessenger( webhookUrl: $config['webhook_url'], ); $handler = new \MahmoudHamed\MultichannelLogger\Logging\WebhookLogHandler( messenger: $messenger, messageFormatter: new MySlackFormatter(), ); return new Monolog\Logger('slack', [$handler]); }, 'webhook_url' => env('SLACK_WEBHOOK_URL'), ], ],
Adding a brand-new provider (e.g. Teams) means implementing the same four contracts plus a Create*Logger factory — see src/Slack for the reference implementation.
Testing
composer test # Pest composer analyse # PHPStan (level 7 + strict rules) composer format # Laravel Pint composer quality # analyse + test
Changelog
Please see CHANGELOG for recent changes.
License
The MIT License (MIT). Please see License File for more information.