Using The Log Facade to Debug Production Bugs in Laravel 11
One of the features of Laravel that makes it a real joy to work with is its built-in logging functionality. This can be a real boon if we’re trying to troubleshoot a problem in production because we can safely log information about running processes without worrying about it affecting our users. The downside is that if we have a lot of concurrent requests, we’ll have a hard time making sense of it, but thankfully, there are ways to get around this.
In this article, we’ll discuss how to use Laravel’s Log facade to troubleshoot problems with our application.
An Intro to Laravel Logging
Laravel provides the Illuminate\Support\Facades\Log
to help us log information in our request. One of the most basic functionalities the Log facade provides are several static functions that provide different levels of logging.
We can use these to express different levels of “interest” into whatever were interested in so emergency level problems that need immediate focus can bubble to the top while the info or debug ones might never be looked at.
use Illuminate\Support\Facades\Log;
$message = 'test';
Log::emergency($message);
Log::alert($message);
Log::critical($message);
Log::error($message);
Log::warning($message);
Log::notice($message);
Log::info($message);
Log::debug($message);
By default Laravel stores our logged data to “storage/logs/laravel.log”. If we run the above functions we’ll get the following output. As you can see each level of static function really only effects the text on the line of the logging. This can be used later to filter out just specific information we want to see.
[2024-11-25 00:35:17] local.EMERGENCY: test
[2024-11-25 00:35:17] local.ALERT: test
[2024-11-25 00:35:17] local.CRITICAL: test
[2024-11-25 00:35:17] local.ERROR: test
[2024-11-25 00:35:17] local.WARNING: test
[2024-11-25 00:35:17] local.NOTICE: test
[2024-11-25 00:35:17] local.INFO: test
[2024-11-25 00:35:17] local.DEBUG: test
As part of the built-in logging features, we can also log information about the request that will be added to the log line so we can view it. For example, if we want to output the user ID, we can do the following.
use Illuminate\Support\Facades\Log;
Log::info('User access denied', ['id' => $user->id]);
// output
// [2024-11-25 00:36:09] local.INFO: User access denied {"id":42}
Storage
Laravel’s logging feature is very simple but also allows for a lot of control over where we send our logged information.
Laravel uses classes called drivers that allow us to specify where the log gets stored. Laravel supports logging to files, a syslog server, and even Slack using the drivers that come with the default setup. We can also add third-party drivers to log to other solutions.
We can configure our default driver by editing “config/logging.php”. The line looks like the following.
'default' => env('LOG_CHANNEL', 'stack'),
We can then define various channels using the ‘channels’ key. The items below are the default.
'channels' => [
'stack' => [
'driver' => 'stack',
'channels' => ['single'],
'ignore_exceptions' => false,
],
'single' => [
'driver' => 'single',
'path' => storage_path('logs/laravel.log'),
'level' => env('LOG_LEVEL', 'debug'),
'replace_placeholders' => true,
],
'daily' => [
'driver' => 'daily',
'path' => storage_path('logs/laravel.log'),
'level' => env('LOG_LEVEL', 'debug'),
'days' => 14,
'replace_placeholders' => true,
],
'slack' => [
'driver' => 'slack',
'url' => env('LOG_SLACK_WEBHOOK_URL'),
'username' => 'Laravel Log',
'emoji' => ':boom:',
'level' => env('LOG_LEVEL', 'critical'),
'replace_placeholders' => true,
],
'papertrail' => [
'driver' => 'monolog',
'level' => env('LOG_LEVEL', 'debug'),
'handler' => env('LOG_PAPERTRAIL_HANDLER', SyslogUdpHandler::class),
'handler_with' => [
'host' => env('PAPERTRAIL_URL'),
'port' => env('PAPERTRAIL_PORT'),
'connectionString' => 'tls://'.env('PAPERTRAIL_URL').':'.env('PAPERTRAIL_PORT'),
],
'processors' => [PsrLogMessageProcessor::class],
],
'stderr' => [
'driver' => 'monolog',
'level' => env('LOG_LEVEL', 'debug'),
'handler' => StreamHandler::class,
'formatter' => env('LOG_STDERR_FORMATTER'),
'with' => [
'stream' => 'php://stderr',
],
'processors' => [PsrLogMessageProcessor::class],
],
'syslog' => [
'driver' => 'syslog',
'level' => env('LOG_LEVEL', 'debug'),
'facility' => LOG_USER,
'replace_placeholders' => true,
],
'errorlog' => [
'driver' => 'errorlog',
'level' => env('LOG_LEVEL', 'debug'),
'replace_placeholders' => true,
],
'null' => [
'driver' => 'monolog',
'handler' => NullHandler::class,
],
'emergency' => [
'path' => storage_path('logs/laravel.log'),
],
],
Using the Alternative Channels
Now you might be asking yourself, “how do I use those alternative channels?”. Well the solution is as easy as setting the channel when we log our information.
Log::channel('daily')->info('This goes into a file with the date');
The daily channel uses the daily driver so our logged entry will actually end up in the “storage/logs/” directory with file name based on the date the entry is logged. For example, when I wrote this the data was stored in “storage/logs/laravel-2024-11-25.log”.
We prefer to use the “daily” driver as the default in production because it automatically rotates the file out after a set number of days, so we don’t have a log file that’s tens or hundreds of gigabytes in size (talking from personal experience).
Creating Our Own Channel
A great way to troubleshoot problems in production is to create our own channel so we log information about a specific problem/situation to it’s own log file to make it easier to troubleshoot.
We can create the channel on the fly inside our code.
Log::build([
‘driver’ => ‘single’,
‘path’ => storage_path(“logs/user-{$user->id}.log”),
])->info(‘Logged in’);
We can also add a custom channel inside the configuration.
‘channels’ => [
// ..
‘custom-channel’ => [
‘driver’ => ‘daily’,
‘path’ => storage_path(‘logs/custom-channel.log’),
‘level’ => ‘debug’,
‘days’ => 14,
],
],
Log::channel('custom-channel')
->info('This goes into custom-channel');
Using the Logs to Debug a Problem
Now that we understand the basics, we can use the logs to solve a problem.
The simplest way to do this is to use the tail
command to watch the log file.
tail -f storage/logs/laravel.log
Then, as items are logged into our system, they’ll show in the terminal for us to view.
One of the downsides to how the logging system works is that, by default, all of the requests are mixed in together. We could log the user id in every request but it could get really messy to do so. Thankfully, Laravel allows us to specify the information that’s logged with every log request by using the Log::withContext()
function.
Our favorite solution is to generate a UUID for the request and then set it in a service provider.
// in a service provider
$uuid = (string) Str::uuid();
Log::withContext([
'uuid' => $uuid,
]);
Then we can use our logging calls as normal.
// in our application
Log::info('User attempted to login');
Log::info('Password checked');
Log::info('User access denied');
Then, inside our log, our uuid will be displayed in every line.
[2024-11-25 00:39:26] local.INFO: User attempted to login {"uuid":"d9770a55-2019-4095-92e5-66b097c387fb"}
[2024-11-25 00:39:26] local.INFO: User attempted to login {"uuid":"3d3e3769-a4a2-417b-a278-d3a1b80837d1"}
[2024-11-25 00:39:26] local.INFO: Password checked {"uuid":"d9770a55-2019-4095-92e5-66b097c387fb"}
[2024-11-25 00:39:26] local.INFO: User access denied {"uuid":"d9770a55-2019-4095-92e5-66b097c387fb"}
[2024-11-25 00:39:26] local.INFO: Password checked {"uuid":"3d3e3769-a4a2-417b-a278-d3a1b80837d1"}
[2024-11-25 00:39:26] local.INFO: User access denied {"uuid":"3d3e3769-a4a2-417b-a278-d3a1b80837d1"}
We can then use grep to find just the entries for one request.
$ grep "d9770a55-2019-4095-92e5-66b097c387fb" storage/logs/laravel.log
[2024-11-25 00:39:26] local.INFO: User attempted to login {"uuid":"d9770a55-2019-4095-92e5-66b097c387fb"}
[2024-11-25 00:39:26] local.INFO: Password checked {"uuid":"d9770a55-2019-4095-92e5-66b097c387fb"}
[2024-11-25 00:39:26] local.INFO: User access denied {"uuid":"d9770a55-2019-4095-92e5-66b097c387fb"}
This process is particularly helpful because you can create a UUID for each request and display it on your application’s error page. When the user reports a problem, you can quickly search for the results.
What You Need to Know
- Laravel ships with a full-featured logging facade
- We can use it to troubleshoot problems in production
- Use tail and grep to narrow down what we’re looking for
Leave a comment
Use the form below to leave a comment: