mahmoud-hamed/multichannel-logger

Send logs and notifications to Slack, Discord and Zoom webhooks from Laravel 11+.

Maintainers

Package info

github.com/mahmoud-hamed/multichannel-logger

pkg:composer/mahmoud-hamed/multichannel-logger

Transparency log

Statistics

Installs: 2

Dependents: 0

Suggesters: 0

Stars: 0

Open Issues: 0

v1.1.0 2026-08-15 20:22 UTC

This package is auto-updated.

Last update: 2026-08-15 20:40:54 UTC


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 channelLog::channel('slack')->error('...')
  • A notification channel$user->notify(new DeployFailed())
  • A first-class messengerMultichannelLogger::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.