Be part of JetBrains PHPverse 2026 on June 9 – a free online event bringing PHP devs worldwide together.

nztim's avatar

Error logs and reporting

I usually set up applications to send emails when an error occurs.

In my L4 applications I added a Log::listen(), and and depending on the $level I sent an alert email. It seems that in L5 this doesn't work, it catches the use of the Log facade but not application errors, even though they are logged.

It appears that adding the relevant code to app/Exceptions/Handler::report() catches errors, while Log::listen() continues to capture use of the Log facade.

Is this a reasonable approach to catching everything or is there a better way to go? Thanks :)

0 likes
4 replies
phildawson's avatar

I think I understand, as the log event (illuminate.log)..

Route::get('/', function(){
    Log::error('fail');
});

will be able to be listened to with

Log::listen(function($level, $message, $context)
{
    dd($message); // fail
});

and this exception...

Route::get('/', function(){
    return view('invalid');
});

will be caught with

public function report(Exception $e)
{
    dd($e); // InvalidArgumentException "View [invalid] not found."

    return parent::report($e);
}

and the parent::report is just writing to the log with the exception $this->log->error((string) $e); and not firing an event.


So your aim is send an email when either the logging event fires or exception thrown.

How about either putting your email code in EmailErrorNotification and then firing the event from both places.

php artisan handler:event EmailErrorNotification --event=ErrorHasOccurred
Log::listen(function($level, $message, $context)
{
    event(new ErrorHasOccurred($message));
});
public function report(Exception $e)
{
    event(new ErrorHasOccurred($e->getMessage()));

    return parent::report($e);
}

or keep the email trigger in the listen closure and simply do this to fire the event

    public function report(Exception $e)
    {
        if ($this->shouldntReport($e)) return;

        \Log::error($e->getMessage(), [
            'file' => $e->getFile(),
            'line' => $e->getLine()
        ]);

        return;
    }

You could keep the return parent::report($e); if you obviously wanted the full exception saved too or pass it with the context

'trace' => $e->getTrace()
1 like
phildawson's avatar
Level 26

Ok so thinking this through the exception handler just uses the MonologLogger (through Writer) to write to the log whilst the Log facade is Writer which does the additional step of firing the event and then passing to the MonologLogger.

    protected function writeLog($level, $message, $context)
    {
        $this->fireLogEvent($level, $message = $this->formatMessage($message), $context);

        $this->monolog->{$level}($message, $context);
    }

As Writer follows the same LoggerInterface contract you can safely swap the log that the Handler is using to Writer and that's it you can now get the exceptions thrown in the Log::listen

use Illuminate\Log\Writer;

class Handler extends ExceptionHandler
{
    public function __construct(Writer $log)
    {
        $this->log = $log;
    }

As far as viewers I've found this is sweet https://github.com/rap2hpoutre/laravel-log-viewer

nztim's avatar

@phildawson those answers are just awesome. I ticked the later one because it's such an elegant solution but they are both extremely helpful. I'm going to give the log viewer a go too. Thanks very much for your help!

nztim's avatar

So here's how I spent the morning, it's rough but it's the kind of thing I wanted:

https://github.com/nztim/logger

Basically you can have separate log files for different things, such as keeping audit trails separate from errors.
It also makes it easy to send email alerts for a specific log level, and it can send whatever entries you want to Papertrail.

Please or to participate in this conversation.