In this tutorial, you will learn how to log the behaviour of your Laravel application. This will help you troubleshoot your application much more easily and quickly. Finding bugs can be frustrating and time-consuming. Luckily, Laravel comes with out of the box logging tools that will help a lot.
In application development, logging is crucial regardless of the platform or language. In this tutorial, we will take a look at one of the most popular PHP frameworks - Laravel.
Prerequisites
- Laravel installed.
- Basic knowledge of Laravel.
Step 1 — Getting To Know How Logging In Laravel Works
Laravel allows you to log messages about the behaviour of your application to files, the system of error logs, or even send them to Slack to notify your development team.
Laravel logging is based on channels. Channels are specific ways of writing a log message. Each channel may represent a different destination and you may send messages to multiple channels at once.
Monolog library
Laravel utilizes many popular open-source libraries. One of them is Monolog. It's a highly popular logging library among PHP developers, as it's easy to learn and very pleasant to work with. Monolog provides many ways to write logs such as writing them to files or sending them to email.
Feel free to learn more about Monolog in our Monolog basics logging tutorial.
Step 2 — Configuration
All the configuration options of your application's logging behaviour are
located in the config/logging.php
file. This file allows you to configure
logging channels by adding new ones or updating current ones.
The configuration file contains one multidimensional array with two main keys.
The first key is default
and it looks like this:
'default' => env('LOG_CHANNEL', 'stack')
This key sets the default logging channel. Any log that will be sent without
specifying the channel will be directed to the default channel. Out of the box,
stack
is set to be the default channel.
The second key is channels
. This key contains the array of channels that can
be used for logging. The default stack
channel mentioned above is defined
here. Out of the box, the array of channels looks like this (note that this is
just a part of the array, for full array, see the config file):
'channels' => [
'stack' => [
'driver' => 'stack',
'channels' => ['single'],
'ignore_exceptions' => false,
],
'single' => [
'driver' => 'single',
'path' => storage_path('logs/laravel.log'),
'level' => env('LOG_LEVEL', 'debug'),
],
'daily' => [
'driver' => 'daily',
'path' => storage_path('logs/laravel.log'),
'level' => env('LOG_LEVEL', 'debug'),
'days' => 14,
],
'slack' => [
'driver' => 'slack',
'url' => env('LOG_SLACK_WEBHOOK_URL'),
'username' => 'Laravel Log',
'emoji' => ':boom:',
'level' => env('LOG_LEVEL', 'critical'),
],
...
],
Channel Drivers
Every channel is powered by a driver. Driver sets where and how logs are recorded. You can think of drivers as Monolog handlers. Drivers are responsible for the actual logging to happen.
There are many built-in ready-to-use drivers or you can make your own. If you are familiar with the Monolog logging library, there is a monolog driver already created that can use any supported Monolog handler.
Most of those drivers are present in the configuration file out of the box, so it's recommended to take a look at them. This is the list of all channel drivers available in every Laravel application:
custom
: a driver that calls a specified custom factory driver to create a channeldaily
: aRotatingFileHandler
based Monolog driver which rotates dailyerrorlog
: aErrorLogHandler
based Monolog drivermonolog
: a Monolog factory driver that may use any supported Monolog handlernull
: a driver that discards all log messagespapertrail
: aSyslogUdpHandler
based Monolog driversingle
: a single file or path based logger channel (StreamHandler
)slack
: aSlackWebhookHandler
based Monolog driverstack
: a wrapper to facilitate creating "multi-channel" channelssyslog
: aSyslogHandler
based Monolog driver
Log Levels
Every channel has a level option. This option determines which log will or will not be processed based on the severity of the message. Less severe messages will not pass through the channel when the level is set high.
This is the full list of log levels
DEBUG
: detailed debug information (LOWEST)INFO
: interesting events. Examples: User logs in, SQL logsNOTICE
: normal but significant eventsWARNING
: exceptional occurrences that are not errors. Examples: Use of deprecated APIs, poor use of an API, undesirable things that are not necessarily wrongERROR
: runtime errors that do not require immediate action but should typically be logged and monitoredCRITICAL
: critical conditions. Example: Application component unavailable, unexpected exceptionALERT
: action must be taken immediatelyEMERGENCY
: emergency: system is unusable (HIGHEST)
Step 3 — Writing Log Messages
You can send messages to the log using the Log
facade. Let's take a look at
the following example:
use Illuminate\\Support\\Facades\\Log;
$message = "This is my first log";
Log::debug($message);
Log::warning($message);
Given the current un-edited configuration that comes out of the box with
Laravel, this code will create two entries in the storage/logs/laravel.log
that will look like this:
Output:
[2021-04-30 09:48:54] local.DEBUG: This is my first log
[2021-04-30 09:48:54] local.WARNING: This is my first log
Writing To Specific Channels
By default, all logs are directed to the stack
channel. If you want to send a
message to a specific channel that is not the default channel, you can do that
by calling channel()
method on the Log
facade.
use Illuminate\\Support\\Facades\\Log;
$message = "This is my first log";
Log::channel('daily')->debug($message);
Given the current configuration, this will create a new daily log
storage/logs/laravel-YYY-MM-DD.log
(based on the current date) that will be
active for 14 days. In this log file you will see the following entry:
Output:
[2021-04-30 10:07:40] local.DEBUG: This is my first log
Contextual Information
You can also provide contextual data in the form of an array. This data will be formatted and printed alongside the log message.
For example, you can provide information such as the user that triggered the error or the current URL:
use Illuminate\\Support\\Facades\\Log;
Log::error("Error happend :(", ["id" => "12345", "username" => "some_guy"]);
Output:
[2021-04-30 10:44:56] local.ERROR: Error happend :( {"id":"12345","username":"some_guy"}
Step 4 — Building Log Stack
As mentioned in the Channel drivers section, stack
driver allows us to stack
multiple channels together and send one message to all the channels at once. We
can make a stack of multiple channels where each has a different driver
therefore we can create a simple hierarchy. Let's take a look at the following
example:
'channels' => [
'stack' => [
'driver' => 'stack',
'channels' => ['debug-channel','error-channel'],
'ignore_exceptions' => false,
],
'debug-channel' => [
'driver' => 'single',
'path' => storage_path('logs/debug.log'),
'level' => env('LOG_LEVEL', 'error'),
],
'error-channel' => [
'driver' => 'single',
'path' => storage_path('logs/error.log'),
'level' => env('LOG_LEVEL', 'error'),
],
]
In the code above, we have created a stack of channels consisting of
debug-channel
and error-channel
.
The debug-channel
sends messages to the storage/logs/debug.log
file and its
level is set to debug
. That means that every log message sent to this stack
will be logged to this file.
On the other hand, the error-channel
logs only messages that have an error
level or higher and logs them to the storage/logs/error.log
file.
Channels with single
or daily
drivers have an optional configuration option
called bubble
. This option is by default set to true
and indicates if
messages should bubble up to other channels after being handled.
Now, let's take a look at the following code:
use Illuminate\\Support\\Facades\\Log;
Log::debug("Just a debug message ...");
try{
throw new Exception('Something terrible happened!');
}
catch(Exception $e){
Log::error($e->getMessage());
}
In this code, we sent debug
level message to the stack
. As it's a debug
level message and the bubble
option is by default set to true
, the message
passed through both channels.
Then, we threw an exception. This exception was handled in the catch block and
its message was sent to the stack
channel.
As it's an error
level message, it didn't pass through the debug-channel
,
but it passed through the error-channel
.
Code above created two entries in the storage/logs/debug.log
file:
Output:
[2021-04-30 10:20:00] local.DEBUG: Just a debug message ...
[2021-04-30 10:20:00] local.ERROR: Something terrible happened!
And one entry in the storage/logs/error.log
file:
Output:
[2021-04-30 10:20:00] local.ERROR: Something terrible happened!
Step 5 — Customizing Monolog Channels
Creating Monolog Handler Channels
Laravel doesn't include a channel driver for each of the Monolgs handlers.
Sometimes, when Laravel doesn't have a corresponding driver you may want to
create an instance of a specific Monolog handler. A channel like that can be
created using the monolog
driver.
When using the monolog
driver, the handler
option is used to specify which
of the Monologs handlers you want to use. As this option creates a new instance
of this handler, you may want to provide the constructor with the specific
parameters. These parameters are provided by the with
option where you specify
the name and the value of the parameter.
Let's say you want to create an instance of NativeMailerhandler
. Then you can
create a channel that may look like this:
'native-mail-channel' => [
'driver' => 'monolog',
'handler' => Monolog\\Handler\\NativeMailerHandler::class,
'with' => [
'to' => '[email protected]',
'subject' => 'Fatal run-time error',
'from' => '[email protected]',
],
],
The full list of all Monolog handlers can be found in the official Monolog documentation.
Using Monolog Formatters
Monolog has multiple built-in formatters that are used to transform your log message to the desired format. A full list of the built-in formatters can be found on the official GitHub documentation.
By default, every channel with the monolog
driver uses LineFormatter
. You
can change that using the formatter
and formatter_with
options.
'browser-output' => [
'driver' => 'monolog',
'handler' => Monolog\\Handler\\BrowserConsoleHandler::class,
'formatter' => Monolog\\Formatter\\HtmlFormatter::class,
'formatter_with' => [
'dateFormat' => 'Y-m-d',
],
],
If you are using a handler that comes with its own formatter, you can set the
formatter
option to default
.
Step 6 — Creating Custom Channel Using Factories
You can also create a completely custom channel with the custom
driver and
have complete control over Monolog's instantiation. In this case, your
configuration has to include via
option where you specify which factory class
will be invoked to create the Monolog instance.
'example-custom-channel' => [
'driver' => 'custom',
'via' => App\\Logging\\MyCustomLogger::class,
],
The factory class only needs a __invoke()
method which should return the
Monolog logger instance and takes the configuration array as its argument.
namespace App\\Logging;
use Monolog\\Logger;
class MyCustomLogger{
/**
* Create a custom Monolog instance.
*
* @param array $config
* @return \\Monolog\\Logger
*/
public function __invoke(array $config){
//Setting up the logger
// ...
//Returning logger isntance
return new Logger(...);
}
}
Conclusion
In this tutorial, you have learned how logging in Laravel works. You have learned how to create logging channels to send messages into, how to create channel stacks to send messages to multiple channels at once, how to customize Monolog channels, and how to create completely custom channels. Now, you can create a powerful logging system in your Laravel project.
dashboards with Grafana.
dashboards with Grafana.