Logging

Improvements?

Let us know by posting here.

Logging in Concrete CMS

Logging is crucial in development. It provides insight into processes not easily observable, like command line or AJAX operations. Concrete features built-in logging for various purposes:

  1. Development Logging: Tracks values and states during code development to troubleshoot issues.
  2. Auditive Logging: Records events such as emails sent or pages deleted.
  3. Error Logging: Automatically logs serious system errors.

Access log entries via Dashboard > Reports > Logs

Error and email logging are automatic, but effective logging depends on its implementation in custom code or add-ons. Concrete supports channels and logging levels, allowing for differentiated handling of log entries based on severity or channel.

Basic Logging in Concrete CMS

Logging in Concrete CMS is straightforward. Here's how to log messages to the Dashboard:

\Log::addInfo('This is an informative message');
\Log::addWarning('Uh oh.');
\Log::addAlert('Red alert!');

Use the Log facade without the backslash by importing it:

use Log;

// ...

Log::addDebug('This is a debug level.');

Messages are logged with the specified severity level to the "Application" channel.

Severity Levels

Concrete uses the PSR-3 standard and Monolog library, supporting these severities:

  • Debug
  • Info
  • Notice
  • Warning
  • Error
  • Critical
  • Alert
  • Emergency

Use Log::add<Severity>('Message') to log with a specific severity. Dashboard search includes severity filtering.

Logging Objects

To log objects, convert them to strings first. Here is an example with a Concrete\Core\User\UserInfo object:

$ui = \UserInfo::getByID(1);
\Log::addInfo('Information about the admin user: ' . var_dump_safe($ui, false));

Use var_dump_safe for Concrete objects, setting the $echo argument to false to avoid crashing PHP with large object sizes.

Logger Usage in Classes

To use the default logger in classes with dependency injection:

namespace MyVendor\Something;

use Concrete\Core\Logging\Logger;

class MyClass {
    protected $logger;

    public function __construct(Logger $logger) {
        $this->logger = $logger;    
    }
}

Create the class via the application container:

$myClass = $this->app->make('MyVendor\Something\MyClass');

This gives MyClass a configured logger for the application channel.

Outside a controller, create an instance of the container:

$app = Facade::getFacadeApplication();

Or use:

$myClass = \Core::make('MyVendor\Something\MyClass');

Using a Different Logging Channel

To use a non-default channel when injecting the Logger:

class MyClass {
    protected $logger;

    public function __construct(Logger $logger) {
        $logger = $logger->withName('shopping_cart');
        $this->logger = $logger;    
    }
}

Or subclass the core logger:

class ShoppingCartLogger extends Concrete\Core\Logging\Logger {
    public function __construct() {
        parent::__construct('shopping_cart');
    }
}

class MyClass {
    protected $logger;

    public function __construct(ShoppingCartLogger $logger) {
        $this->logger = $logger;    
    }
}

Custom Handlers in Concrete CMS Logging

Concrete CMS's logging system supports custom handlers, allowing different handling of log messages. By default, Concrete uses a database handler for all logs, saving messages from debug level upwards to the Dashboard logs report. You can create custom handlers for specific needs.

For instance, to route alert severity messages to a file, add this in application/bootstrap/app.php or in a package's on_start() method:

$logger = Core::make('log');    
$logger->pushHandler(
    new \Monolog\Handler\RotatingFileHandler(
    '/path/to/file.log', 
    0, 
    \Monolog\Logger::ALERT, 
    false)
);

Messages with alert severity or higher will go into /path/to/file-date.log and skip the database. To log to both the file and database, omit the false in the RotatingFileHandler constructor.

Concrete's Logger, being a Monolog subclass, supports various handlers, including those for MongoDB, Slack, Sendgrid, and Hipchat. For more, see Monolog documentation.

Advanced Logging Configuration

In Advanced Mode (System > Environment > Logging Settings), you can use a custom configuration array in Monolog Cascade format. Unless specific loggers are defined in this array, simple configuration applies.

Example configuration to log all core channels to a file at DEBUG level and the email channel to the database at INFO level:

use Concrete\Core\Logging\Channels;

return [
    "log" => [
        "configuration" => [
            "advanced" => [
                "configuration" => [
                    'version' => 1,
                    'formatters' => [
                        'basic_database' => [
                            'format' => "%message%"
                        ],
                    ],
                    'loggers' => [
                        Channels::META_CHANNEL_ALL => [
                            'handlers' => [
                                'file'
                            ]
                        ],
                        Channels::CHANNEL_EMAIL => [
                            'handlers' => [
                                'database'
                            ]
                        ]
                    ],
                    'handlers' => [
                        'file' => [
                            'class' => 'Monolog\Handler\StreamHandler',
                            'level' => 'DEBUG',
                            'stream' => '/var/log/all.log'
                        ],
                        'database' => [
                            'class' => 'Concrete\Core\Logging\Handler\DatabaseHandler',
                            'level' => 'INFO',
                            'formatter' => 'basic_database'
                        ]
                    ]
                ]
            ]
        ]
    ]
];

This configuration details how core and custom channels can be managed differently for logging purposes.