Laravel Logging Tutorial

Kim Sia Developer Tips, Tricks & Resources

Regardless of what language and framework you use, proper logging is crucial to web development. Logging is key when it comes to debugging and performance monitoring. Knowing how to properly use logging frameworks is an essential part of creating high quality software that is easy to debug.

In this article, I will provide in-depth coverage on Laravel, which is the most popular PHP framework in 2018.

Fig 1: Google Trends comparing popular PHP frameworks

Specifically, I want to cover the following with respect to Laravel 5.7:

  1. Introducing Monolog, Laravel’s logging library
  2. Exploring the logging config file
  3. Writing log messages and using logging levels
  4. Formatting your log messages
  5. Sending your logs to Retrace

The first four sections will cover the basics of logging in Laravel. Lastly, I will take all the knowledge you’ve learned in the previous sections and put it to use by showing you how to send your log statements to an external service — Stackify’s very own Retrace.

Let’s get started!

1. Introducing Monolog, Laravel’s logging library


Laravel has this philosophy about using popular open source libraries to fulfill various common features required in a typical web framework: Logging is a common feature. Hence, by default, Laravel employs Monolog, a highly popular PHP logging library, for all its logging needs. The wonderful thing about Monolog is that it provides a common interface for you to write logs to anything, from standard text files to third-party log management services.

For details about using Monolog on its own, you can check out this post.

(Keep in mind that everything written here regarding Monolog applies to version 1.0 and above but excludes version 2.0, which is coming out in early 2019.)

Monolog is highly flexible. It can send your logs to files, sockets, email, databases, and various web services. I will show you in a later section how Monolog provides handlers that easily help you to send logs to these various destinations. Laravel typically sets up Monolog via a logging configuration file. Now I’m going to show you how the logging configuration file works.

2. Exploring the logging config file

In Laravel, there are various configuration files found in the config folder. This folder holds config files meant for database connections, email connections, caching, etc. You should expect your logging configuration also to be found here, and it’s path will be in config/logging.php.

Imports

Fig 2: First five lines of config/logging.php

When you create a Laravel app, the first few lines are the imports it uses. By default, you should see the two handlers shown above being imported. As explained in the earlier section, these are two of the typical handlers that Monolog provides.

Channels

Laravel Monolog uses a concept called channels. The channels are the different places where you can send your logs. For the rest of the config/logging.php, the config file returns an associative array with two main keys — default and channels.

Fig 3: Default channel is usually stack

Default represents the default channel that any logging is sent to. It’s crucial that the channel you pick as default is represented in the array under the key channels. As you can see above, stack is the default channel.

Fig 4: List of channels

Channels represent the full list of channels available for sending your logs, and stack is the first channel listed. For the rest of this article, whenever I mention channel list, I’m referring specifically to this list.

Configuration within each channel

In each channel stated under the channel list, you can see different keys being used. Knowing how each key affects logging will give you the maximum flexibility to configure the logging output you want.

The first type I want to cover is drivers. Before Laravel 5.6, Laravel only had drivers supporting four file-based outputs:

  1. Writing to a single file. By default, that’s usually storage/logs/laravel.log.
  2. Daily files. By default, that will give you files like storage/logs/laravel-2018-12-03.logstorage/logs/laravel-2018-12-04.log, and so on.
  3. Error log. The location of the error log depends on the web server software you are using, such as nginx or Apache, and the server OS. Typically this means that on Linux with Nginx, the file is at /var/log/nginx/error.log.
  4. System log, also known as syslog, and once again, the location depends on the server OS.
Drivers

With Laravel 5.6 and above, we have brand new drivers supporting a wider range of logging outputs. The old drivers are still available under the values “single,” “daily,” “errorlog,” and “syslog.” Now we have new values for new kinds of logging outputs. Some commonly used new drivers include:

Stack

This simply means you can stack multiple logging channels together. When you use the “stack” driver, you need to set an array of values for other channels under the key “channels.” See Fig 4 for the example of a stack driver in use.

Slack

This lets you send logs to the popular social channel, Slack. When you use this driver, you need to configure the URL as well. Optional Slack-related variables include username and emoji. This driver essentially allows you to output the log to a specific Slack channel. By default, when you use the Laravel command line to create a new project with 5.7, you will see an example of the Slack logging channel configuration (see Fig 5 below).

Fig 5: By default, Laravel 5.7 includes an example for a Slack channel

Monolog

It’s a bit weird to see Monolog show up again, this time as its own driver. The point to remember is that when you want to use Monolog’s native handlers, then you want to use the Monolog driver. Thankfully, the default logging config file provides two examples of using two different Monolog handlers. Instead of going into too much detail on the various Monolog handler types, I will leave you with a link to the full list of Monolog handlers so you can review at your own speed.

Fig 6: Default logging config file uses Monolog StreamHandler and SyslogHandler

Custom

There’s only a small write-up about this in the official Laravel documentation. Between the popular legacy drivers and the various Monolog handlers, it’s pretty hard to imagine someone writing up their own custom channel driver. Nevertheless, if that’s something you want to do, you can. Usually, a custom channel is for you to write logs to third-party services, like Apache Kafka and Logstash.

First, your custom channel needs to choose a custom driver and then add a via option to point to a logger factory class.

'channels' => [
    'custom' => [
        'driver' => 'custom',
        'via' => AppLoggingCustomLoggerFactory::class,
    ],  
],

After you setup the custom channel, you’re ready to define the factory class. Bear in mind that you must return a Monolog Logger instance via the __invoke method.

<?php
namespace AppLogging;
use MonologLogger;

class CustomLoggerFactory
{
    /**
    * This class will create a custom Monolog instance.
    *
    * @param array $config
    * @return MonologLogger
    */
    public function __invoke(array $config)
    {
        return new Logger(...);
    }
}

Try to use the custom driver as a last resort. If you search around a bit, usually you’ll find a standardized way to use existing drivers or handlers for your special logging needs. Even if you have to write your own custom logger driver, try to see if somebody has already written an open source version or an example of the Logger factory class you need. In other words: don’t reinvent the wheel if you can help it.

Drivers as a summary list

Just to recap, here’s a useful summary table of the various common drivers available and what they mean.

DriverDescription
stackFor creating “multi-channel” channels.
singleFor single-file or path-based logger channels. Uses Monolog’s StreamHandler.
dailyFor daily logs. Uses Monolog’s RotatingFileHandler.
slackFor sending to Slack channels. Uses Monolog’s SlackWebhookHandler.
syslogFor sending to the system log. Uses Monolog’s SyslogHandler.
errorlogFor sending to the web server’s error log. Uses Monolog’s ErrorLogHandler.
monologFor using any other of Monolog’s supported handlers.
customA driver calling a specified factory to create a channel for a third-party service.

Handlers

The next important thing you need to learn after channels and drivers is the concept of handlers. There is a complete list of handlers for Monolog to send logs to. You can even employ special handlers so you can build more advanced logging strategies. To save space, I will leave it to you to read the full list of handlers on your own and simply group Monolog’s wide range of handlers into the following types:

  • Logging to files and Syslog
  • Sending alerts (such as Slack) and emails
  • Logging to specific servers and networked logging
  • Logging during development (including sending logs to ChromePHP extension)
  • Sending logs to databases
  • Special handlers

Also, note that there’s a special option called handler_with that allows you to assign values to the individual handler’s constructor method.

For example, when you look at the SysLogHandler class and its constructor, you will notice this:

public function __construct(string $ident, $facility = LOG_USER, $level = Logger::DEBUG, bool $bubble = true, int $logopts = LOG_PID)

At the individual channel configuration, you can assign the values for the parameters of the constructor this way.

'syslog_channel_name' => [
    'driver' => 'monolog',
    'handler' => MonologHandlerSyslogHandler::class,
    'handler_with' => [
        'ident' => 'some value you want to assign to the ident parameter',
    ],
],

3. Writing log messages and levels

If you’ve made it this far, congratulations! You have now gone deep into the details of the configuration file for Laravel logging. Most developers have only a cursory understanding of this and didn’t let that stop them from writing log messages. Therefore, you are now more equipped than most developers at configuring Laravel logging for your team.

One key configuration detail I didn’t cover in the previous section is levels. This is partly because the previous section was already long enough, but mainly because it makes more sense to cover levels here.

Levels of logging

At the specific file that you want to write log messages, ensure that you import the Log facade before writing out your log message, like this:

use IlluminateSupportFacadesLog;

//.. and then somewhere in your php file
Log::emergency($message);
Log::alert($message);
Log::critical($message);
Log::error($message);
Log::warning($message);
Log::notice($message);
Log::info($message);
Log::debug($message);

Typically developers want to have different levels of severity when it comes to log messages. The different log levels stated here correspond to the RFC-5424 specification. So the levels and their names are not decided willy-nilly. These levels have an order of priority with the highest being emergency and the lowest being debug. At the channels list, you can specify the minimum level the individual channel will log any messages. In the case of Fig. 5, for example, this means the Slack channel will write any log messages with a level of critical and above when triggered.

Log facade sends to default channel

The level’s not the only factor that determines if Laravel will eventually write a specific message to the channel. The channel as a destination is another factor as well. Bear in mind when you use the Log facade to write log messages, Laravel will send these to the default channel stated in your config/logging.php.

If you leave the logging config file unchanged, and assuming you didn’t set the LOG_CHANNEL environment variable to a different value, your default channel will be the stack “multi-channel,” which in turn consists of just the “daily” channel as seen in Figs 3 and 4.

Sending log messages to alternative channels on the fly

This then begs the question: what if you want to send a specific message to a channel other than the default channel? You can deliberately choose an alternative channel on the fly this way:

Log::channel('suspicious')->warning($suspiciousActivity);

Remember to make sure that the alternative channel you choose exists in the channel list within the logging config file. Otherwise, you’ll encounter errors. In the example above, this means you need to declare a channel explicitly in the channel list called suspicious.

And if you want to send the same logging message to multiple channels, you can also perform “stacking.”

Log::stack(['suspicious', 'slack'])->info("I have a bad feeling about this!");

Sending contextual information

Sometimes you want to send contextual information, such as the specific file name, the line in the file, or even the current logged-in user.

You can write your individual log messages this way to add contextual information:

Log::info('User login failed.', ['id' => $user->id, 'file' => __FILE__, 'line' => __LINE__]);

This command will pass in an array of contextual data to the log methods. Laravel will then format the contextual data and display it along with the log message.

4. Formatting log messages

This section on formatting could have been placed alongside the drivers and handlers section under “Explaining the logging config file.” But I prefer to have “Formatting log messages” be its own standalone section, coming after everything else, because it tends to be less crucial.

The main reason formatting is often less crucial is that you can get away with the default formatter that Monolog driver uses—the Monolog LineFormatter. That’s usually more than good enough. However, there may be times when you wish to customize the formatter passed to the handler. This is a full list of formatters that Monolog natively provides. I have noticed that Monolog arranges this list by highest frequency of usage. So it’s a good list to reference should you want to select alternatives.

When you want to customize the formatter, you need to set the formatter option under the specific channel configuration. You will also notice the formatter_with option. It works in the similar way as the handler_with option explained in the “Handlers” section. This is a way to send values to the formatter class constructor method. Here’s an example where I use the next most popular formatter, the HtmlFormatter:

'browser_console' => [
    'driver' => 'monolog',
    'handler' => MonologHandlerBrowserConsoleHandler::class,
    'formatter' => MonologFormatterHtmlFormatter::class,
    'formatter_with' => [
        'dateFormat' => 'Y-m-d',
    ],
],

>
Sometimes a particular Monolog handler comes with its own formatter. In that case, you can simply instruct the formatter to use that handler’s default formatter.

If you are using a Monolog handler that is capable of providing its own formatter, you may set the value of the formatter configuration option to default:

'newrelic' => [
    'driver' => 'monolog',
    'handler' => MonologHandlerNewRelicHandler::class,
    'formatter' => 'default', 
],

5. Putting it all together: sending your logs to Retrace

Here comes the grand finale, where you can put everything you learned in the previous sections to use in a single exercise. In this section, I will attempt to send Laravel logging messages to Stackify’s own Retrace. Retrace allows you to glean insights from your logs by giving you a single place to perform code profiling, performance monitoring, and centralized logging.

And because Retrace is so comprehensive in its monitoring mandate, it looks at everything, from the server system and web server logs, to the application’s own database logs. So my first recommendation is that you keep your syslog and errorlog channels as they are in your channel list. Even so, you don’t have to add them into your default stack channel.

As for configuring your syslog and your web server error logs to work in tandem with Retrace, I recommend checking out the following documentation for details:

Setting up a channel for Retrace specifically

As you learned in the section “Explaining the logging config file,” the first step for sending your application log messages to Retrace is to have a specific log channel for Retrace and add it to the stack multi-channel.

Step 1. Use composer to install the Stackify Monolog handler package

Install the latest version with composer require stackify/monolog “~1.0”.

Or you can add a dependency to your composer.json file:

"stackify/monolog": "~1.0",
Step 2. Set the various Retrace variables in the environment variables file (.env)

The key variables are the API key, the application name, and the environment name. Make sure they are set in the .env file of your Laravel app. You should have different values for each different .env file depending which environment you wish to send logs from.

Step 3. Change your config/logging.php

I recommend altering the config file in 5 different places which I have broken up into comments using the notation Step 3.1, Step 3.2, etc.

// after the typical imports in the config file 
// Step 3.1 import the following and make sure you add 
use StackifyLogTransportExecTransport; 
use StackifyLogMonologHandler as StackifyHandler; 

// Step 3.2: assign your Retrace various variables as environment variable then call here 
$retrace_api_key = env('RETRACE_API_KEY', 'api_key'); 

// Step 3.3: create the transport instance here. You need this in Step 3.5 
$transport_for_retrace = new ExecTransport($retrace_api_key); 

return [ 
    // for brevity I removed the comments
    'default' => env('LOG_CHANNEL', 'stack'),
    'channels' => [ 
        'stack' => [ 
            'driver' => 'stack',
            // Step 3.4 add retrace channel to stack
            'channels' => ['daily', 'retrace'],
        ],
        // Step 3.5 setting up the retrace channel
        'retrace' => [
            'driver' => 'monolog',
            'level' => 'debug', 
            'handler' => StackifyHandler::class,
            // for clarity on the exact configuration look at the constructor method at
            // https://github.com/stackify/stackify-log-monolog/blob/master/src/Stackify/Log/Monolog/Handler.php#L20
            'handler_with' => [
                'appName' => env('RETRACE_APP_NAME', 'app_name'),
                'environmentName' => env('RETRACE_ENV_NAME', 'env_name'),
                'transport' => $transport_for_retrace, // set in Step 3.3
            ]
        ],
        // the rest of the channel list such as daily, syslog, etc are below

Steps 3.1 to 3.4 are pretty self-explanatory from the code above. The most complex would be step 3.5, where I create a brand new retrace channel. I’ve said it before, but I’ll repeat for clarity: because Stackify has a Monolog-compatible handler, I strongly recommend using the Monolog driver and using theStackify’s Monolog handler class under the handler option. Also, the possible keys chosen for the handler_with option follow the same parameter names given to the handler’s constructor method.

Recap

Whew! That was a pretty comprehensive look at Laravel logging. To recap, I have gone over what Monolog is and why Laravel uses it for its logging needs. I also explored the intricacies of some of Monolog’s concepts, including channels, drivers, and handlers. Plus I extensively covered writing and formatting log messages. I gave you examples of how to write to the default channel or alternative channels on the fly, and finally, I brought all these concepts together to teach you how to write a channel for sending your logs to Retrace.

Thanks for sticking with me. I hope this has been a useful guide for all your Laravel logging needs!

About Kim Sia

This post was written by KimSia. KimSia writes under the nom de plume T.J. Simmons. He started his own developer firm five years ago, building solutions for professionals in telecoms and the finance industry who were overwhelmed by too many Excel spreadsheets. He’s now proficient with the automation of document generation and data extraction from varied sources.