Michael Dyrynda
Home Blog Podcasts
 
Configuring per-environment logging in Laravel 5 June 4th, 2016

Introduction

Laravel allows you to configure the default Monolog logger instance, but this largely allows you to add additional handlers or to put debug into another file for example.

The default logging implementation sets the log level to debug and doesn't appear to provide a way to change this. This means that even if you specify a separate handler for debug logs, all log events with higher priority (info, notice, error, etc) will still be logged there.

You could use the setHandlers method to create an entirely new set of stream handlers and log each level - but this is still the minimum logging level. If you create a handler for the error level, you will still get all levels above this - critical, alert, and emergency.

I didn't want to have to remove the debug level log messages - Log::debug() - from my code, but didn't want to have them logged in production, as they are expected to get quite noisy.

Following the documentation

Your first thought might be to handle this within the bootstrap/app.php file per the documentatino:

1$level = $app->environment('local') ? 'debug' : 'warning';
2 
3$app->configureMonologUsing(function ($monolog) {
4 $monolog->pushHandler(new StreamHandler(storage_path('logs/laravel.log'), $level);
5});

Done, right? Wrong.

At this stage of the boot process, the environment has not yet been configured, so environment() triggers a fatal error:

1Fatal error: Uncaught ReflectionException: Class env does not exist in /home/vagrant/Code/laravel.test/vendor/laravel/framework/src/Illuminate/Container/Container.php:741

There also doesn't appear to be any way to set the $level parameter of Laravel's underlying useFiles, useDailyFiles, useSyslog, or useErrorLog methods.

In order to get around this limitation, you need to override the functionality entirely.

A custom ConfigureLogging class

1<?php
2 
3namespace App\Bootstrap;
4 
5use Illuminate\Foundation\Bootstrap\ConfigureLogging as BaseConfigureLogging;
6use Illuminate\Contracts\Foundation\Application;
7use Illuminate\Log\Writer;
8 
9class ConfigureLogging extends BaseConfigureLogging
10{
11 protected function configureSingleHandler(Application $app, Writer $log)
12 {
13 $log->useFiles($app->storagePath().'/logs/laravel.log', $this->resolveLogLevel($app));
14 }
15 
16 protected function configureDailyHandler(Application $app, Writer $log)
17 {
18 $log->useDailyFiles(
19 $app->storagePath().'/logs/laravel.log',
20 $app->make('config')->get('app.log_max_files', 5),
21 $this->resolveLogLevel($app)
22 );
23 }
24 
25 protected function configureSyslogHandler(Application $app, Writer $log)
26 {
27 $log->useSyslog('laravel', $this->resolveLogLevel($app));
28 }
29 
30 protected function configureErrorlogHandler(Application $app, Writer $log)
31 {
32 $log->useErrorLog($this->resolveLogLevel($app));
33 }
34 
35 /**
36 * Based on the current environment, return the desired log level.
37 *
38 * @param \Illuminate\Contracts\Foundation\Application $app
39 *
40 * @return string
41 */
42 private function resolveLogLevel(Application $app)
43 {
44 return $app->isLocal() ? 'debug' : 'warning';
45 }
46}

In doing so, you overwrite the relevant methods in Laravel's ConfigureLogging class, giving you the ability to set the log level based on the environment via the new resolveLogLevel method.

How you derive the appropriate log level is up to you; you might target a specific environment using $app->environment('staging'), for example.

Implementing the custom logging

At this point, you've overwritten the necessary methods, but there's a little more work needed to wire it all up.

This part of the process isn't managed via Service Providers like much of Laravel's configuration is; it's handled via an array of bootstrappers in the Illuminate\Foundation\Http\Kernel and Illuminate\Foundation\Console\Kernel classes. This can be relatively easily achieved by overwriting the constructor of your application's console or HTTP (or both) Kernel classes as needed.

1<?php
2 
3namespace App\Http;
4 
5use Illuminate\Foundation\Http\Kernel as HttpKernel;
6use Illuminate\Contracts\Foudnation\Application;
7use Illuminate\Routing\Router;
8 
9class Kernel extends HttpKernel
10{
11 // ...
12 public function __construct(Application $app, Router $router)
13 {
14 $this->bootstrappers = array_map(function ($bootstrapper) {
15 return $bootstrapper === 'Illuminate\Foundation\Bootstrap\ConfigureLogging'
16 ? 'App\Bootstrap\ConfigureLogging'
17 : $bootstrapper;
18 }, $this->bootstrappers);
19 }
20}

Conclusion

This is, up until Laravel 5.2.35, the only way to swap out the logging configuration within Laravel.

In the process of writing this post, and subsequent tweet from Taylor, I discovered that this was added to the framework in early May. If you're still using the LTS (5.1) version of Laravel, you'll need to continue to follow the approach outlined in this post.

If you're using Laravel >= 5.2.35, you can simply add log_level into config/app.php at your desired level, and the log level will be applied. To make it switchable per-environment, set this value via an environment variable and you're all set.

1// config/app.php
2return [
3 // ...
4 'log_level' => env('APP_LOG_LEVEL', 'error'),
5];

In doing this, calls to Log::debug() can be left in your code, without worrying about them filling your production log files.

Another option you could take would be to add an environment variable to manage the log level. In doing so, you could easily turn debug on and off in production, allowing you to diagnose an issue.

Further reading

I'm a real developer ™
Michael Dyrynda

@michaeldyrynda

I am a software developer specialising in PHP and the Laravel Framework, and a freelancer, blogger, and podcaster by night.

Proudly hosted with Vultr

Syntax highlighting by Torchlight