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

Monolog Bridge Laravel Package

symfony/monolog-bridge

Symfony Monolog Bridge integrates the Monolog logging library with Symfony components, providing handlers and processors for console, HTTP kernel, security, and mailer so you can route framework logs into Monolog channels cleanly.

View on GitHub
Deep Wiki
Context7

Getting Started

Minimal Steps

  1. Installation: Add the package via Composer in a Laravel project (it is primarily designed for Symfony, but Laravel's logging layer is Monolog under the hood, so the bridge's processors and handlers are directly usable):

    composer require symfony/monolog-bridge
    

    Note: Laravel already ships Monolog, so this package extends what you have rather than replacing it. Match the bridge to your Symfony component version line — 5.4.x for Symfony 5.4 projects, 6.4.x / 7.x / 8.1.x for the newer lines. The bridge is released per-branch, so a fix landing in v5.4.54 will also appear as e.g. v8.1.6 on the current branch; the same bug ID (#65692) can show up under two different tags.

  2. First Use Case: Leverage Symfony's Monolog processors to enrich every log record with runtime metadata. For example, add a MemoryUsageProcessor so you can correlate slow requests with memory spikes:

    use Symfony\Bridge\Monolog\Processor\MemoryUsageProcessor;
    
    $log = app('log')->getMonolog();
    $log->pushProcessor(new MemoryUsageProcessor());
    

    Once this is in a service provider's boot(), Log::info('...') records will carry memory_usage / memory_peak_usage keys in their context.

  3. Where to Look First:

    • Processors — extend log context with metadata (MemoryUsageProcessor, UidProcessor, TokenProcessor): Symfony Processors Docs
    • Handlers — Symfony-specific handlers (ConsoleHandler, NotifierHandler, ServerLogHandler) plus the activation strategies under Symfony\Bridge\Monolog\Handler\FingersCrossed: Symfony Handlers Docs
    • Error Logging — bridge ErrorLogger to route PHP errors/exceptions through Monolog:
      use Symfony\Component\ErrorHandler\Bridge\Monolog\ErrorLogger;
      
      $errorLogger = new ErrorLogger(app('log')->getMonolog());
      set_exception_handler([$errorLogger, 'handleException']);
      

Implementation Patterns

Usage Patterns

  1. Structured Logging with Processors: Add request-scoped context without touching every Log:: call:

    // app/Providers/AppServiceProvider.php — boot()
    app('log')->getMonolog()->pushProcessor(function (array $record) {
        $record['extra']['user_id']    = auth()->id();
        $record['extra']['request_id'] = request()->header('X-Request-Id');
        return $record;
    });
    

    Prefer writing to $record['extra'] rather than returning a replacement array, so you don't clobber context already set by other processors.

  2. Buffered Logging with FingersCrossed: Keep debug/context records in memory and only flush them when something actually goes wrong — ideal for noisy request tracing in staging:

    use Monolog\Handler\FingersCrossedHandler;
    use Monolog\Handler\StreamHandler;
    use Monolog\Logger as Monolog;
    
    $handler = new FingersCrossedHandler(
        new StreamHandler(storage_path('logs/errors.log'), Monolog::DEBUG),
        Monolog::ERROR,   // activation threshold
        0,                // buffer size (0 = unlimited)
        true              // bubble
    );
    app('log')->getMonolog()->pushHandler($handler);
    

    The activation level is the second argument — the old assessment's "3 retries" was incorrect.

  3. Console-Specific Logging: ConsoleHandler writes to a Symfony OutputInterface, which is what makes it useful in Artisan commands. In Laravel, hand it a console output instance:

    use Symfony\Bridge\Monolog\Handler\ConsoleHandler;
    use Symfony\Component\Console\Output\ConsoleOutput;
    
    $output  = new ConsoleOutput();
    $handler = new ConsoleHandler($output);
    $handler->setFormatter(new \Monolog\Formatter\LineFormatter(
        "[%datetime%] %channel%.%level_name%: %message%\n",
        null,
        true,
        true
    ));
    app('log')->getMonolog()->pushHandler($handler);
    

    The constructor signature is (OutputInterface $output, bool $bubble = true, array $verbosityLevelMap = [], array $consoleFormatterOptions = []) — it does not take a log level as its first argument.

  4. HTTP-Response-Driven Activation: Buffer records and only flush them when a response has an error status code — cheap "log everything for failing requests" behaviour:

    use Monolog\Handler\FingersCrossedHandler;
    use Monolog\Handler\StreamHandler;
    use Symfony\Bridge\Monolog\Handler\FingersCrossed\HttpCodeActivationStrategy;
    
    $strategy = new HttpCodeActivationStrategy(request(), [400, 500], Monolog::ERROR);
    $handler  = new FingersCrossedHandler(new StreamHandler('php://stderr'), $strategy);
    

Workflows

  1. Centralized Configuration: Declare handlers/processors in config/logging.php so they're per-channel rather than global:

    'channels' => [
        'stack' => [
            'driver'   => 'stack',
            'channels' => ['single', 'symfony'],
        ],
        'symfony' => [
            'driver'     => 'monolog',
            'level'      => 'debug',
            'handlers'   => [
                Symfony\Bridge\Monolog\Handler\ConsoleHandler::class,
            ],
            'processors' => [
                Symfony\Bridge\Monolog\Processor\MemoryUsageProcessor::class,
            ],
        ],
    ],
    

    Handlers listed under handlers are resolved from the container, so only register classes whose constructor can be resolved or bind them explicitly in a provider.

  2. Service Provider Integration: Bind the bridge components in a provider when you need a shared, configured instance:

    public function register(): void
    {
        $this->app->singleton('symfony.logger', function ($app) {
            return new \Symfony\Bridge\Monolog\Logger('symfony');
        });
    }
    
  3. Exception Handling: Replace Laravel's default error-to-log path when you want Symfony's ErrorLogger semantics:

    use Symfony\Component\ErrorHandler\Bridge\Monolog\ErrorLogger;
    
    $errorLogger = new ErrorLogger(app('log')->getMonolog());
    set_exception_handler([$errorLogger, 'handleException']);
    

    Do this inside bootstrap/app.php or a provider's boot() — but be aware Laravel's own handler already logs exceptions, so you'll get duplicates unless you suppress one path.

Integration Tips

  1. Avoid DI Conflicts: Don't try to boot Symfony's DI container inside Laravel. Instantiate bridge services by hand:

    $logger = new \Symfony\Bridge\Monolog\Logger('app');
    $logger->pushHandler(new \Monolog\Handler\StreamHandler('php://stderr'));
    
  2. Reach Monolog Through the Log Facade: Log::channel('x')->getMonolog() gives you the underlying Monolog instance for a specific channel — usually more precise than app('log')->getMonolog(), which targets the default channel.

  3. Testing: Swap the logger for a mock so you can assert handler/processor wiring:

    $monolog = Mockery::mock(\Monolog\Logger::class);
    $monolog->shouldReceive('pushProcessor')->once();
    $this->app->instance('symfony.logger', $monolog);
    

    For assertions on emitted records, Log::spy() plus Laravel's Log::shouldHaveReceived(...) is usually less brittle than mocking the bridge.


Gotchas and Tips

Pitfalls

  1. Namespace Collisions: The bridge ships Symfony\Bridge\Monolog\Logger, which shadows neither Laravel's Log facade nor Monolog\Logger, but mixing all three in one file is confusing. Alias explicitly:

    use Symfony\Bridge\Monolog\Logger as SymfonyLogger;
    $logger = new SymfonyLogger('app');
    
  2. DI Container Incompatibility: Symfony's DependencyInjection container is not compatible with Laravel's. Avoid:

    // Don't do this inside a Laravel app:
    $container = new \Symfony\Component\DependencyInjection\Container();
    
  3. ConsoleHandler Polluting CLI Output: Because ConsoleHandler writes through a Symfony OutputInterface, it can interleave log lines with command output (and break things like --format=json consumers). Scope it to the channel/commands that need it, and use the $verbosityLevelMap constructor argument to keep -q quiet.

  4. ConsoleHandler Now Escapes Context Values (v5.4.54 and 8.1.6 — same fix, different branches): The fix for "Escape context strings written to the terminal" (symfony/monolog-bridge#65692, by @nicolas-grekas) shipped as v5.4.54 on the 5.4 line and as v8.1.6 on the current line. If your Laravel project pulls Symfony 5.4 components (the common case for older Laravel versions), the behaviour arrives via v5.4.54, not v8.1.6 — check composer show symfony/monolog-bridge rather than assuming either tag. Practical consequences:

    • Any ANSI colour codes or control sequences you previously baked into context values will render as literal escaped text in CLI output rather than being interpreted.
    • Terminal escape-sequence / log-injection attacks via user-supplied data in context are now neutralised — treat this as a hardening fix, not a behaviour you need to opt into.
    • The message itself is still formatted normally, so %message% colouring in a LineFormatter is unaffected. If you rely on coloured context output, move the formatting into the message or a custom formatter.
    • This is flagged as a [SECURITY] release by upstream, so the correct action is to upgrade rather than pin. Any custom CLI formatter that assumes raw context strings must be re-tested after the bump.
  5. Circular References: Serialising Eloquent models into context can recurse (model → relations → model). Use Monolog\Handler\WhatFailureGroupHandler or guard with Monolog\Processor\PsrLogMessageProcessor plus explicit array extraction instead of passing whole objects:

    Log::info('order failed', ['order_id' => $order->id]); // not ['order' => $order]
    
  6. Performance Overhead: Processors run on every record. Benchmark hot paths and strip processors in production:

    if (app()->environment('production')) {
        app('log')->getMonolog()->popProcessor(); // pops the last pushed processor
    }
    

    Note popProcessor() takes no index argument — the popProcessor(0) form is invalid.

Debugging

  1. Log Level Mismatches: Monolog levels are shared between Laravel and Symfony (debug…emergency), but the channel's configured level in config/logging.php filters before handlers see the record. If a handler seems dead, check the channel level first.

  2. Handler Not Triggering: Confirm you are pushing to the same Monolog instance that handles the record:

    $log = app('log')->channel('stack')->getMonolog();
    $log->pushHandler($handler);
    

    The stack driver wraps channels in a StackHandler; pushing onto the stack logger is not the same as pushing onto an individual channel.

  3. Console Output Issues: If nothing appears in CLI, check in this order: the handler is attached to the channel the command logs to; verbosity mapping allows the level at the current -v setting; the formatter can render the record (a formatter without %message% silently drops content). After v5.4.54 / v8.1.6, also verify you aren't expecting raw escape sequences in context.

  4. Memory Leaks: Long-running workers (queue, Octane) keep processors alive for the process lifetime. Anything that captures the request/container can pin memory:

    app('log')->getMonolog()->popProcessor();
    

    Reset in a terminating callback or job middleware rather than per request.

Config Quirks

  1. Environment-Specific Handlers: Evaluate environment at config build time — config/logging.php is cached, so avoid runtime-only closures:

    'channels' => [
        'symfony' => [
            'driver'   => 'monolog',
            'handlers' => env('LOG_SYMFONY_CONSOLE')
                ? [\Symfony\Bridge\Monolog\Handler\ConsoleHandler::class]
                : [],
        ],
    ],
    

    Run php artisan config:clear after changing handler classes, otherwise the cached config keeps the old wiring.

  2. Container Resolution of Handlers: Classes listed in handlers/processors are instantiated by Laravel's container. ConsoleHandler requires an OutputInterface, so either bind OutputInterface in a provider or construct and push the handler manually in boot().

  3. Version Pinning: The console-escaping fix landed in v5.4.54 (5.4 branch) and v8.1.6 (current branch). Pinning below those keeps the unescaped-context behaviour and leaves you exposed to the log-injection issue upstream classified as a security release. Constrain on the patch line instead, e.g. "symfony/monolog-bridge": "^5.4.54" or "^8.1.6", and re-run your CLI formatter tests after the bump.

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.
jinas/dhivatar
store-doc/laravel-storage-driver
plus-it/laravel-translation-manager
diephp/perhaps
diephp/telegraph
osmaviation/spreadsheet
microsoft/fluentui-system-icons
mmccook/php-json-canonicalization-scheme
middlewares/firewall
qubiqx/montapacking
nyoncode/laravel-package-console
pnscripts/filament-broadquill-lite
pnscripts/laravel-sentfold-lite
vleap/mx-sdk-laravel-internal
twidpay/utils
htetmyathlaing/scout-elasticsearch-driver
artisan-toolbox/front-awesome
ls/client-assistant
users-au/laravel-client
cgv/localized-resource