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

Console Exception Bundle Laravel Package

opale/console-exception-bundle

Opale ConsoleExceptionBundle enhances exception handling for Symfony console commands, replacing noisy stack traces with clearer, more readable error output so CLI failures are easier to diagnose and debug. A lightweight integration for Symfony applications.

View on GitHub
Deep Wiki
Context7

Getting Started

Read this first: you cannot use this package in a Laravel application. It is a Symfony bundle (Opale\...\ConsoleExceptionBundle), it is archived, its last release was June 2016, and it has zero dependents. Its composer.json will constrain symfony/symfony to the 2.x/3.x line, which directly conflicts with the symfony/console ^5/^6/^7 that illuminate/console requires. Composer will refuse the install, and even if you force it, a Symfony Bundle subclass needs AppKernel::registerBundles() and compiler passes that Laravel has no equivalent for. There is nothing to "get started" with.

What you can get started with is the problem it solves, because that problem is real and still exists in Laravel today.

The problem: Symfony Console catches exceptions thrown inside a command, renders them to stderr, and returns a non-zero exit code — it never reports them. In Laravel, Illuminate\Foundation\Console\Kernel::handle() does call reportException(), but that catch block is rarely the one that fires: Symfony's Application::doRunCommand() swallows the throwable first, renders it, and returns the exit code. So php artisan some:command failing inside handle() frequently produces a pretty terminal message and nothing in storage/logs. That is fatal for cron jobs, scheduled commands, and container entrypoints where stderr goes nowhere.

Where to look first (only if you inherited a legacy Symfony 2/3 app that already uses it): Resources/config/services.xml for the tagged listener service, then the listener class — almost always under Listener/ or EventSubscriber/. The entire package is under ~100 LOC. You are reading it to copy the idea, not to install it.

Your first use case: log every failing Artisan command with command name, exit code, and full stack trace. Confirm the gap exists before you build anything — make a command that throw new RuntimeException('boom'), run it, and check whether storage/logs/laravel.log grew.

Implementation Patterns

The idiomatic Laravel port is a single service provider that attaches a Symfony ConsoleEvents::ERROR listener to the Artisan application's dispatcher. Symfony dispatches ConsoleEvents::ERROR inside doRunCommand()'s catch block before rendering, which is exactly the hook this bundle used.

// app/Providers/ConsoleErrorLoggingProvider.php
use Illuminate\Support\Facades\Log;
use Symfony\Component\Console\ConsoleEvents;
use Symfony\Component\Console\Event\ConsoleErrorEvent;
use Symfony\Component\EventDispatcher\EventDispatcher;

public function boot(): void
{
    $dispatcher = new EventDispatcher();

    $dispatcher->addListener(ConsoleEvents::ERROR, function (ConsoleErrorEvent $event) {
        Log::channel('console')->error('Artisan command failed', [
            'command' => $event->getCommand()?->getName(),
            'exit_code' => $event->getExitCode(),
            'exception' => $event->getError(),   // object, not getMessage()
        ]);
    });

    $this->app->make(\Illuminate\Contracts\Console\Kernel::class)
        ->getArtisan()
        ->setDispatcher($dispatcher);
}

Guard it with if ($this->app->runningInConsole()) and verify getArtisan() is reachable in your version before relying on it — the console kernel is bound early, and calling getArtisan() from a provider triggers command discovery.

Patterns worth adopting around it:

  • Log the exception object, not the message. Monolog's introspection processor (enabled by default in the single/daily handlers) expands Throwable into class, file, line, trace, and the full getPrevious() chain. Logging $e->getMessage() — which the old bundle did in some configurations — throws away everything you need.
  • Separate channel. Route to a console channel so alerting rules on "anything from the app logger" don't fire for benign command failures (e.g. migrate:status in a health check).
  • Redact input. $event->getInput() contains every --option and --argument, including --password and --token. Either omit input entirely or filter against a denylist before logging.
  • Assert it in tests. $this->artisan('boom')->assertExitCodes([1]) plus Log::shouldReceive('error')->once() catches regressions when framework internals shift.
  • Cron/CI wrapper. For scheduled commands, prefer Schedule::command(...)->onFailure(fn () => Log::error(...)) (Laravel 10.19+) where it fits — it is more explicit than a global listener and doesn't depend on Symfony event internals.
  • If you must consume the legacy bundle (Symfony 2.8/3.x only): register in AppKernel, ensure Monolog has a handler with a Psr\Log\LoggerInterface alias, and check the service tag — pre-4.4 it is console.exception, not console.error.

Gotchas and Tips

  • Event rename. console.exception became console.error in Symfony 4.4; the old name survived only as a deprecated alias and is gone in 5.0+. Any fork must target ConsoleEvents::ERROR.
  • \Exception vs \Throwable. 2016-era typehints say \Exception. In PHP 7+/8, TypeError, Error, and OOM-adjacent fatals are \Throwable — which is what actually kills console commands. If you port the listener verbatim, keep the Throwable typehint.
  • Swallowing failures. Returning from a ConsoleErrorEvent listener or calling setExitCode(0) tells Symfony the error was handled; the command exits successfully and CI goes green on a broken build. Never touch the exit code unless you genuinely intend to suppress.
  • Double logging. Symfony renders and you log; some Laravel versions also re-report at Kernel::handle() if setCatchExceptions(false) is set. If you see two entries per failure, that's why — pick one hook and stay there.
  • Signals aren't exceptions. SIGTERM in a queue worker or a killed container bypasses the exception path entirely; log those via pcntl_signal or ConsoleEvents::TERMINATE, not this listener.
  • -vvv changes rendering, not the exception. Verbosity affects the terminal output only. The logged object is identical either way — don't try to "improve" logging by bumping verbosity.
  • Supply chain. One star, zero dependents, no maintainer, no PHP 8 support. Vendor the ~20 lines into app/Support/ behind your own interface rather than adding it to composer.json — you keep patch room, composer update can't break you, and composer audit stays clean.
  • Extension point if forking. Inject Psr\Log\LoggerInterface and expose channel + level config keys. Keep the listener idempotent (a re-entrant failure inside the logger must not recurse) and wrap the log call in its own try/catch.
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.
coleus/frame
thtg88/journalism
filagora/filagora-ai-forums
rlacerda83/query-parser
socialdept/atp-orm
cboxdk/laravel-assistant-client
socialdept/atp-resolver
socialdept/tenant-domains
tautvydasr/blade-class-names
socialdept/color-scales
maherelgamil/controller
coleus/widgets
coleus/table
coleus/users
coleus/music
coleus/health
coleus/calendar
coleus/notes
coleus/support
coleus/settings