Andrew Welch · Insights · #craftcms #craft-4 #logs

Published , updated · 5 min read ·


Please consider 🎗 sponsoring me 🎗 to keep writing articles like this.

Logging to a File with Craft CMS 4

Learn how to eas­i­ly send logs from your cus­tom mod­ule or plu­g­in to a sep­a­rate file in Craft CMS 4

Craft CMS 4 now uses Monolog for all of its log­ging, which is a very flex­i­ble & extend­able way to han­dle log­ging to files, ser­vices, etc.

A com­mon thing that you may want to do is have logs from your Craft CMS plu­g­in or mod­ule log to a sep­a­rate file, to make it eas­i­er to sift through.

Ben Cro­ker wrote an arti­cle on this very top­ic: Adding Log­ging to Craft Plu­g­ins with Monolog.

But there’s an eas­i­er way, so read on!

Link FileLog helper class

First, you just need to add this helper class to your plu­g­in or mod­ule (updat­ing 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 tech­nique used in our Retour plu­g­in, so you’ll see ref­er­ences to that in the code and exam­ples, but you can adapt it to any plu­g­in or module.

Once you’ve added the FileLog helper class, to have it send logs to a sep­a­rate file, you just do:

        FileLog::create('retour-csv-import-errors', 'nystudio107\retour\*');

The first para­me­ter is the name of the log file you want to send logs to, it will be saved in the stan­dard storage/logs/ direc­to­ry with a time­stamp and the .log suf­fix added to it.

The sec­ond para­me­ter is the cat­e­go­ry of mes­sages that should be sent to that log file (more on that later).

Then to send log mes­sages, you use the stan­dard Craft log­ging com­mands:

  • Craft::trace('Message', __METHOD__); – ver­bose, fine-grained anno­ta­tions — some­times tem­po­rary — used for sup­port or debugging
  • Craft::debug('Message', __METHOD__); – non-essen­tial infor­ma­tion that can be used for debug­ging issues
  • Craft::info('Message', __METHOD__); – stan­dard lev­el for infor­ma­tive con­tex­tu­al details
  • Craft::warning('Message', __METHOD__); – mes­sages that indi­cate some­thing prob­lem­at­ic or unex­pect­ed even though every­thing con­tin­ues to work
  • Craft::error('Message', __METHOD__); – most urgent lev­el before an excep­tion, used to indi­cate that some­thing didn’t func­tion properly

The sec­ond para­me­ter is the cat­e­go­ry of the log mes­sage. __METHOD__ is a PHP Mag­ic con­stant” that resolves to the ful­ly name­spaced method that is call­ing the var­i­ous log­ging methods.

For instance, if we call Craft::info('Message', __METHOD__); from the init() method of the Retour plug­in’s class, __METHOD__ will be:

nystudio107\retour\Retour::init

This is where the sec­ond cat­e­go­ry para­me­ter in FileLog::create() comes in. We passed in 'nystudio107\retour\*' which means that it should match every cat­e­go­ry that begins with nystudio107\retour\ (the * is a wildcard).

Adjust this to what­ev­er your plu­g­in or mod­ule name­space is. For exam­ple, for the Craft Sprig plu­g­in, you’d do:

        FileLog::create('sprig', 'putyourlightson\sprig\*');

Then any calls to the Craft log­ging com­mands (e.g.: Craft::error('Oh, no!', __METHOD__);) from the Sprig plu­g­in will auto­mat­i­cal­ly be logged to storage/logs/sprig-xxxx-xx-xx.log

Aside: If the CRAFT_STREAM_LOG PHP con­stant is set to true then Craft will send log out­put to stderr and stdout, instead of to log files.

Link Advantages of this technique

The advan­tages of using this tech­nique are as follows:

  • You don’t have to mod­i­fy your code at all. The places where you are already doing Craft::error('Message', __METHOD__); or the like will just work”
  • It uses the exist­ing Craft log­ging meth­ods and log lev­els, rather than you hav­ing you add your own sta­t­ic meth­ods to your plu­g­in class
  • Your logs will appear in the same stan­dard for­mat that Craft uses
  • You don’t have to pass in your plug­in’s han­dle for each log state­ment, __METHOD__ works universally

Link Customizing Further

You can, of course, cus­tomize things fur­ther to your liking.

You might change the 'level' in the new MonologTarget() con­fig array to log all of the avail­able PSR log lev­els:

            'level' => LogLevel::DEBUG,

Or, if you want a super sim­ple log for­mat, you could add this in the new MonologTarget() con­fig array:

            'logContext' => false,
            'allowLineBreaks' => false,
            'formatter' => new LineFormatter(
                format: "%datetime% %message%\n",
                dateFormat: 'Y-m-d H:i:s',
            ),

You can con­fig­ure the log for­mat­ting how­ev­er you like; see the Monolog docs for examples.

Hap­py logging!

Link Craft CMS 3

What if you’re still main­tain­ing a Craft CMS 3 plu­g­in or mod­ule, and still need to log to a file there? No prob­lem, 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;
    }
}