Andrew Welch
Published , updated · 5 min read · RSS Feed
Please consider 🎗 sponsoring me 🎗 to keep writing articles like this.
Logging to a File with Craft CMS 4
Learn how to easily send logs from your custom module or plugin to a separate file in Craft CMS 4
Craft CMS 4 now uses Monolog for all of its logging, which is a very flexible & extendable way to handle logging to files, services, etc.
A common thing that you may want to do is have logs from your Craft CMS plugin or module log to a separate file, to make it easier to sift through.
Ben Croker wrote an article on this very topic: Adding Logging to Craft Plugins with Monolog.
But there’s an easier way, so read on!
Link FileLog helper class
First, you just need to add this helper class to your plugin or module (updating the namespace as appropriate):
<?php
namespace nystudio107\retour\helpers;
use Craft;
use craft\log\MonologTarget;
use Monolog\Formatter\LineFormatter;
use Psr\Log\LogLevel;
class FileLog
{
// Public Static Methods
// =========================================================================
/**
* Create an additional file log target named $filename.log that logs messages
* in the category $category
*
* @param string $fileName
* @param string $category
* @return void
*/
public static function create(string $fileName, string $category): void
{
// Create a new file target
$fileTarget = new MonologTarget([
'name' => $fileName,
'categories' => [$category],
'level' => LogLevel::INFO,
'logContext' => false,
'allowLineBreaks' => true,
'formatter' => new LineFormatter(
format: "%datetime% [%channel%.%level_name%] [%extra.yii_category%] %message% %context% %extra%\n",
dateFormat: 'Y-m-d H:i:s',
allowInlineLineBreaks: true,
ignoreEmptyContextAndExtra: true,
),
]);
// Add the new target file target to the dispatcher
Craft::getLogger()->dispatcher->targets[] = $fileTarget;
}
}
This is the exact technique used in our Retour plugin, so you’ll see references to that in the code and examples, but you can adapt it to any plugin or module.
Once you’ve added the FileLog helper class, to have it send logs to a separate file, you just do:
FileLog::create('retour-csv-import-errors', 'nystudio107\retour\*');
The first parameter is the name of the log file you want to send logs to, it will be saved in the standard storage/logs/ directory with a timestamp and the .log suffix added to it.
The second parameter is the category of messages that should be sent to that log file (more on that later).
Then to send log messages, you use the standard Craft logging commands:
- Craft::trace('Message', __METHOD__); – verbose, fine-grained annotations — sometimes temporary — used for support or debugging
- Craft::debug('Message', __METHOD__); – non-essential information that can be used for debugging issues
- Craft::info('Message', __METHOD__); – standard level for informative contextual details
- Craft::warning('Message', __METHOD__); – messages that indicate something problematic or unexpected even though everything continues to work
- Craft::error('Message', __METHOD__); – most urgent level before an exception, used to indicate that something didn’t function properly
The second parameter is the category of the log message. __METHOD__ is a PHP “Magic constant” that resolves to the fully namespaced method that is calling the various logging methods.
For instance, if we call Craft::info('Message', __METHOD__); from the init() method of the Retour plugin’s class, __METHOD__ will be:
nystudio107\retour\Retour::init
This is where the second category parameter in FileLog::create() comes in. We passed in 'nystudio107\retour\*' which means that it should match every category that begins with nystudio107\retour\ (the * is a wildcard).
Adjust this to whatever your plugin or module namespace is. For example, for the Craft Sprig plugin, you’d do:
FileLog::create('sprig', 'putyourlightson\sprig\*');
Then any calls to the Craft logging commands (e.g.: Craft::error('Oh, no!', __METHOD__);) from the Sprig plugin will automatically be logged to storage/logs/sprig-xxxx-xx-xx.log
Aside: If the CRAFT_STREAM_LOG PHP constant is set to true then Craft will send log output to stderr and stdout, instead of to log files.
Link Advantages of this technique
The advantages of using this technique are as follows:
- You don’t have to modify your code at all. The places where you are already doing Craft::error('Message', __METHOD__); or the like will “just work”
- It uses the existing Craft logging methods and log levels, rather than you having you add your own static methods to your plugin class
- Your logs will appear in the same standard format that Craft uses
- You don’t have to pass in your plugin’s handle for each log statement, __METHOD__ works universally
Link Customizing Further
You can, of course, customize things further to your liking.
You might change the 'level' in the new MonologTarget() config array to log all of the available PSR log levels:
'level' => LogLevel::DEBUG,
Or, if you want a super simple log format, you could add this in the new MonologTarget() config array:
'logContext' => false,
'allowLineBreaks' => false,
'formatter' => new LineFormatter(
format: "%datetime% %message%\n",
dateFormat: 'Y-m-d H:i:s',
),
You can configure the log formatting however you like; see the Monolog docs for examples.
Happy logging!
Link Craft CMS 3
What if you’re still maintaining a Craft CMS 3 plugin or module, and still need to log to a file there? No problem, here’s the same FileLog class (used the same way), but for Craft CMS 3:
<?php
namespace nystudio107\retour\helpers;
use Craft;
use craft\helpers\FileHelper;
use craft\log\FileTarget;
class FileLog
{
// Public Static Methods
// =========================================================================
/**
* Create an additional file log target named $filename.log that logs messages
* in the category $category
*
* @param string $fileName
* @param string $category
* @return void
*/
public static function create(string $fileName, string $category): void
{
// Create a new file target
$fileTarget = new FileTarget([
'categories' => [$category],
'levels' => ['error', 'warning', 'info'],
'logFile' => "@storage/logs/$fileName.log",
'logVars' => [],
]);
// Add the new target file target to the dispatcher
Craft::getLogger()->dispatcher->targets[] = $fileTarget;
}
}