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

4jZW7jVSdS4U6PC's avatar

Advance Logging with Laravel and Monolog

Hello everyone.

At the moment my only configuration on Logging events is Log::useDailyFiles(storage_path().'/logs/laravel_main.log');

But I think that I can do more than that. My main goal is to separate logging files for example for Log::info i'll use a file called laravel_info.log for Log::error I'll use a file called laravel_error.log etc.

How can I achieve this with the built-in logging functionality?

0 likes
22 replies
moak's avatar

I'd also like to see a laracast on best practices and ideas for logging

13 likes
bashy's avatar

I wanted to use something like this yesterday in one of my apps. Would like to see some options for multiple files as well. Having everything in one file makes it hard to find the relevant info.

toltech's avatar
toltech
Best Answer
Level 2

True. Alright heres a direct answer to the question being asked in this thread (for L5):

You can override the Illuminate\Foundation\Bootstrap\ConfigureLogging Class in the Bootstrap directory (as the documentation states).

Steps are:

  1. Create a ConfigureLogging Class in the /bootstrap directory that implements the configureHandlers() method from Illuminate\Foundation\Bootstrap\ConfigureLogging.
  2. Open App/Console/Kernal.php. It extends ConsoleKernal, which has a $bootstrappers attribute. Overwrite that attribute in App/Console/Kernal.php and register your new /bootstrap/ConfigureLogging class:
// App/Console/Kernal.php
...
class Kernel extends ConsoleKernel {

    /**
     * OVERRIDING PARENT CLASS
     * The bootstrap classes for the application.
     *
     * @var array
     */
    protected $bootstrappers = [
        'Illuminate\Foundation\Bootstrap\DetectEnvironment',
        'Illuminate\Foundation\Bootstrap\LoadConfiguration',
        'Illuminate\Foundation\Bootstrap\HandleExceptions',
        'Illuminate\Foundation\Bootstrap\RegisterFacades',
        'Illuminate\Foundation\Bootstrap\SetRequestForConsole',
        'Illuminate\Foundation\Bootstrap\RegisterProviders',
        'Illuminate\Foundation\Bootstrap\BootProviders',
        'Bootstrap\ConfigureLogging' // custom logger bootstrapper
    ];
...
  1. Do the same for app/Http/Kernal.php
  2. Register your new new bootstrap namespace in composer.json and run composer dumpautoload. My namespace is Bootstrap.
// composer.json
...
"autoload": {
        "psr-4": {
                "Bootstrap\\": "bootstrap/"
        }
    },
...
  1. Implement the custom handlers you want in the configureHandlers method in /bootstrap/ConfigureLogging. Something like:
// bootstrap/ConfigureLogging
<?php namespace Bootstrap;

use Monolog\Logger as Monolog;
use Illuminate\Log\Writer;
use Illuminate\Contracts\Foundation\Application;
use Illuminate\Foundation\Bootstrap\ConfigureLogging as BaseConfigureLogging;
use Monolog\Handler\StreamHandler;

class ConfigureLogging extends BaseConfigureLogging{

    /**
     * OVERRIDE PARENT
     * Configure the Monolog handlers for the application.
     *
     * @param  \Illuminate\Contracts\Foundation\Application  $app
     * @param  \Illuminate\Log\Writer  $log
     * @return void
     */
    protected function configureHandlers(Application $app, Writer $log)
    {

        $bubble = false;

        // Stream Handlers
        $infoStreamHandler = new StreamHandler( storage_path("/logs/laravel_info.log"), Monolog::INFO, $bubble);
        $warningStreamHandler = new StreamHandler( storage_path("/logs/laravel_warning.log"), Monolog::WARNING, $bubble);
        $errorStreamHandler = new StreamHandler( storage_path("/logs/laravel_error.log"), Monolog::ERROR, $bubble);

        // Get monolog instance and push handlers
        $monolog = $log->getMonolog();
        $monolog->pushHandler($infoStreamHandler);
        $monolog->pushHandler($warningStreamHandler);
        $monolog->pushHandler($errorStreamHandler);

        $log->useDailyFiles($app->storagePath().'/logs/daily.log');
    }

One note on that snippet, the order you push the streamhandlers on is important: push the most restrictive on last, so that it is hit first. Otherwise, say if you pushed the infoStreamHandler on last, it would log everything above it also and an error wouldn't make it to the error handler.

This will result in 3 log files:

  • laravel_error.log - just errors
  • laravel_info.log - just info
  • laravel_warning.log - just warnings *daily log file - all

A related question was asked which I also answered.

4 likes
jgreen's avatar

This was helpful, however I was implementing a mysql connection for my logs using wazarri/monolog-mysql and ran into issues with the DB facade not being fully setup. Essentially, the Bootstrap/ConfigureLogging class was being bootstrapped before the DB was being setup. Here's how I fixed that in case anyone else is trying this. This is in app\Http\Kernel.php. I like this method because it not only replaces the Bootstrap for logging, but also moves it to the end of the stack so everything else is ready for use.

/**
     * Constructor
     */
    public function __construct(Application $app, Router $router)
    {
        parent::__construct($app, $router);

        foreach($this->bootstrappers as $key => $boostrapper)
        {
            if($boostrapper === 'Illuminate\Foundation\Bootstrap\ConfigureLogging')
            {
                unset($this->bootstrappers[$key]);

            }
        }
        $this->bootstrappers[] = 'Bootstrap\ConfigureLogging';
    }
jclermont's avatar

You can have full control over your log configuration without having to remove the built in ConfigureLogging bootstrapper. The app has a configureMonologUsing function which gives you everything you need. More details under "Custom Monolog Configuration" here: http://laravel.com/docs/5.1/errors#configuration

5 likes
Phil_S's avatar

As @jclermont stated, you can easily do this without all the work mentioned in the best answer.

Inside bootstrap\app.php you can simply add the following:

use Monolog\Logger;
use Monolog\Handler\StreamHandler;

$app->configureMonologUsing(function($monolog) {
    $monolog->pushHandler(new StreamHandler(storage_path('logs/laravel_info.log'), Logger::INFO));
    $monolog->pushHandler(new StreamHandler(storage_path('logs/laravel_warning.log'), Logger::WARNING));
});

Simply repeat the call to pushHandler() with your remaining severity levels.

7 likes
ui-matt's avatar

@Phil_S, that works great.

Does anyone know how to setup monolog to send the same messages to two different places? For example, what if I wanted to send errors (and higher) to loggly while still having complete local log files? The bubble parameter indicates that this should be possible, but I can't seem to get it working.

femanso's avatar

@ui-matt, I'm not sure this is the best way, the following code writes Debug messages to laravel_debug.log AND laravel.log.

use Monolog\Logger;
use Monolog\Handler\StreamHandler;

$app->configureMonologUsing(function ($monolog) {
    $monolog->pushHandler(new StreamHandler(storage_path('logs/laravel_debug.log'), Logger::DEBUG, false));
    $monolog->pushHandler(new StreamHandler(storage_path('logs/laravel.log'), Logger::DEBUG, true));
    $monolog->pushHandler(new StreamHandler(storage_path('logs/laravel.log'), Logger::INFO, false));
});
1 like
udgeet's avatar

Here log path is changed according to log levels. But what if, i have two different path for log files each contains log with all levels or custom configured level?

For example I have two paths for Api Logs and Sms Logs having different path.

How can i define two logs? currently logging is done like
ex Log::emergency('Log string'); This string is logged according to defined stream handler of emergency level.

But instead i want this log to write in API path log or Sms path log.

progmars's avatar

@udgeet I guess, you might need a new facade for this. Laravel's Log also is only a facade, and you can create your own facades for your custom loggers. Here is my example of some abstract BusinessLog facade

namespace App\Services\Facades;

use Illuminate\Support\Facades\Facade;

class BusinessLog extends Facade
{
    /**
     * Get the registered name of the component.
     *
     * @return string
     */
    protected static function getFacadeAccessor()
    {
        return "log.business";
    }
}

In my case, I also took the opportunity to specify completely custom line formats etc., so here's how you could implement custom logging handler:

namespace App\Services\Log;

use Illuminate\Log\Writer;

use Monolog\Logger as Monolog;
use Monolog\Formatter\LineFormatter;
use Monolog\Handler\RotatingFileHandler;

class BusinessLogWriter extends Writer
{
    // you can customize these as you wish
    const LINE_FORMAT = "[%datetime%] %level_name%: %message% %context% %extra%\n";
    
    /**
     * Creator for convenient reuse of some Writer functions
     * 
     * @return Monolog
     */
    public function createLogger($path, $numOfKeepFiles, $level)
    {
        // create business logger with prefix
        $log = new Monolog("business");
        
        // create the kind of handler as you wish
        $handler = new RotatingFileHandler(
                $path, 
                $numOfKeepFiles,
                $this->parseLevel($level));  
        
        $handler->setFormatter(
            new LineFormatter(static::LINE_FORMAT, null, true, true));
        
        $log->pushHandler($handler);

        return $log;
    }
}

And then in providers/AppServiceProvider:

$this->app->instance("log.business", 
            (new BusinessLogWriter())
            ->createLogger(
                $this->app->storagePath()."/logs/business/business.log",
                config("app.log_max_files", 0), // how long to keep - default will be forever
                config("app.log_level", "debug")
            ));
2 likes
Ghunti's avatar

After a full day of head scratching, I've finally nailed a nasty side-effect of the chosen answer.

If you install newrelic with those changes the entire framework will stop responding with the infamous Uncaught exception 'ReflectionException' with message 'Class log does not exist'

At least in my case, in order to avoid that, the only thing I need to do was to swap the order of Bootstrap\ConfigureLogging and move it to the third position of $bootstrappers, respecting this way the framework original order.

After reading some more answers here, I do think that the answer of @jclermont is actually the best one.

samlinbris's avatar

@sharq Would it be possible for you to tag your logs in the log message, such as resource:event/resource:audit. My monolog message is basically a json payload with structures like

[2016 - 11 - 16 00: 37: 28] lumen.INFO: Commencing job attempt. 
"payload": {
    "pid": 24273,
    "job_id": "ec908bd5-e024-44ab-a734-150cc7223eb3",
        "resource": "events"
}
envision's avatar

Also in addition to @toltech answer, one can have daily log files by adding these lines to __construct method:

$this->channels['event']['path'] = 'logs/audit-' . date('Y-m-d') . '.log';
$this->channels['audit']['path'] = 'logs/audit-' . date('Y-m-d') . '.log'; 
wired00's avatar

What about if I wanted to log to two different log files explicitly?

Say... logging debug information for client1's endpoint to /logs/laravel_client1.log and then debug information for client2's endpoint to /logs/laravel_client2.log

1 like

Please or to participate in this conversation.