Log Handlers
Wonolog will collect log records coming from different sources:
- WordPress core, via default hook listeners
- Plugins/themes/packages that perform log via logging action hooks registered as Wonolog hook alias
- Plugins/themes/packages that perform logs via PSR-3 making use of Wonolog PSR-3 logger
- Plugins/themes/packages that perform logs via custom hook listeners
None of the sources is aware of how the log records they produce are handled. The how is, in fact, determined by Wonolog configuration for the underlying Monolog handlers.
Table of contents
- From channel to handlers
- Configuring handlers
- Fallback handler
- About auto-calculated logs folder
- About auto-calculated minimum log level
From channel to handlers
Every log record, regardless the source, will have one and only channel. Based on the channel, Wonolog will decide how to handle the log record.
An handler is any object implementing Monolog\Handler\HandlerInterface
interface.
Thanks to the dozen of ready-made Monolog handlers, there are countless possibilities on how to persist log records, but it is also quite straightforward to write a custom handler.
In Wonolog handlers are organized in two groups:
- Channel-specific handlers: used to handle records having specific channels
- Generic handlers, that are used to handle all records, regardless channel
When Wonolog encounters a log record:
- get all the channel-specific handlers assigned to log record channel
- get all generic handlers, but excluding those that have been specifically excluded for the log record channel
Obtained the list of handlers, Wonolog passes the log record to each of them for handling.
It worth noting that each handler can decide to stop the propagation of log record handling to other handlers, by retuning false
in their handle()
method. Most of the ready-made Monolog handlers have a “bubbling” property that if set to false
will make handle()
return false
and thus preventing the same record to be handler by other handlers.
Configuring handlers
To add/remove generic handlers it is necessary to use the wonolog.setup
action and call, respectively, pushHandler
or removeHandler
methods on the given Configurator
object.
To add/remove channel-specific handlers the Configurator
methods to use are pushHandlerForChannels
and removeHandlerFromChannels
.
For example:
add_action(
'wonolog.setup',
function (Inpsyde\Wonolog\Configurator $config) {
$config
->pushHandler(new HandlerOne)
->pushHandler(new HandlerTwo)
->pushHandlerForChannels(new SmsHandler, 'SECURITY', 'URGENT')
->pushHandler(new SlackHandler)
->removeHandlerFromChannels(SlackHandler::class, 'SECURITY')
->removeHandler(HandlerTwo::class);
}
);
Using the above configuration:
- the handler
HandlerOne
will be used for all logs - the handler
SmsHandler
will be used only for logs inSECURITY
andURGENT
channels - the handler
SlackHandler
will be used for all logs, only excluding those inSECURITY
channel - the handler
HandlerTwo
will not be used, because it is removed after being added.
Configurator
has two additional methods that help configuring the channel to handler mapping. Those are: enableHandlerForChannels
and enableHandlersForChannel
. Their scope is the same: assign existing handler(s) to existing channel(s).
Let’s imagine, for example, that one MU plugin/package adds the configuration in the snippet above, another MU plugin/package could do:
add_action(
'wonolog.setup',
function (Inpsyde\Wonolog\Configurator $config) {
$config->enableHandlerForChannels(SmsHandler::class, 'IMPORTANT', 'SMS');
}
);
The code above assign the SmsHandler
to two additional channels besides the two channels that it was already assigned to.
The method enableHandlersForChannel
works in a similar way, but has a different signature that takes a single channel as first parameter, and a variadic number of handler identifiers from second parameter.
About the identifier
The methods removeHandler
, removeHandlerFromChannels
, enableHandlerForChannels
, and enableHandlersForChannel
all accept handler identifiers.
Identifiers are used to uniquely identify an handler, and can be passed to pushHandler
as second parameter. When no custom identifier is passed, the fully-qualified class name of the added handler is used as identifier. That is the reason why the snippets above use the fully-qualified class name to identify an handler.
The class-name-as-identifier strategy does not work if multiple handlers of the same class are purposely added, for example two MailHandler
that send email to two different email addresses. In that case it is necessary to pass explicitly an unique identifier as second parameter to pushHandler
.
Fallback handler
Wonolog needs at least one handler per channel, otherwise log records will be lost.
This is why Wonolog instantiates a “fallback” handler for all the channels that have none. For example, when Wonolog is just required without any configuration the fallback handler is instantiated and assigned to all default channels (see “What is logged by default” chapter for list of default channels).
The “fallback handler” is a custom handler that ships with Wonolog, and write logs to files, its class is FileHandler
and it is a wrapper around Monolog StreamHandler
designed to auto-tune its configuration to work well in WordPress context.
Logs folder
FileHandler
uses Monolog StreamHandler
to write file. To determine the folder path, Wonolog looks at the WP_ERROR_LOG
constant, and when it contains a path to a file, Wonolog uses that file’s folder as the parent folder for its logs. For example, if wp-config.php
contains something like the following:
define('WP_DEBUG_LOG', '/tmp/wp-errors.log');
FileHandler
will use /tmp/wonolog/
as parent folder for all its log files.
If WP_DEBUG_LOG
is not defined or is a boolean FileHandler
fallbacks to a /wonolog
folder inside WordPress “upload” folder.
Considering that log files should not be publicly accessible, when FileHandler
writes file in the “uploads” folder, it adds in its base folder a .htaccess
file that prevents public access to it, but that only works if the web-server in use is Apache and it is configured to take into account .htaccess
files.
That is why when using Wonolog fallback handler it is essential to make sure the logs path is not publicly accessible.
To manually change the FileHandler
base folder it is necessary to either define WP_DEBUG_LOG
constant pointing to a file, as shown above, or alternatively call withFolder()
method on it.
That means that we need to access the FileHandler
instance for that.
The first obvious way is to manually instantiate FileHandler
and push it in Wonolog via Configurator::pushHandler()
. For example:
use Inpsyde\Wonolog\{Configurator, DefaultHandler\FileHandler};
add_action(
'wonolog.setup',
function (Configurator $config) {
$config->pushHandler(FileHandler::new()->withFolder('/logs/wp/'));
}
);
However, that means that FileHandler
is not anymore a “fallback” handler, because that is generic handler added to all channels (so Wonolog does not need to create any fallback handler).
This might be totally fine, but might not. The alternative is to use the hook wonolog.handler-setup
that is fired once for each handler being used. For example:
add_action(
'wonolog.handler-setup',
function (Monolog\Handler\HandlerInterface $handler) {
if ($handler instanceof Inpsyde\Wonolog\DefaultHandler\FileHandler) {
$handler->withFolder('/logs/wp/');
}
}
);
Logs file names
Regardless how the “base” folder is determined, inside it by default FileHandler
writes daily files, with the format /{$year}/{$month}/{$day}.log
.
The default format can be changed calling FileHandler::withDateBasedFileFormat()
method. For example:
add_action(
'wonolog.handler-setup',
function (Monolog\Handler\HandlerInterface $handler) {
if ($handler instanceof Inpsyde\Wonolog\DefaultHandler\FileHandler) {
$handler->withFolder('/logs/wp/')->withDateBasedFileFormat('Y-m-d');
}
}
);
By using configuration above, FileHandler
will write log files like /logs/wp/2021-05-26.log
.
Alternatively, there’s the method withFilename
that makes FileHandler
use the same file name, this is useful to implement custom ways to calculate the file name.
Minimum level
Many Monolog handlers have a “minimum log level”, and they ignore any log below that level. For example, a handler that sends logs via SMS might have a minimum level of “critical” to don’t disrupt anyone’s phone (and serenity of life).
FileHandler
has that. Its minimum level is, by default, calculated based on the value of the environment variable WONOLOG_DEFAULT_MIN_LEVEL
. If that is not defined, Wonolog checks the value of the WP_DEBUG_LOG
constant, and when that value is false
, the default handler’s minimum level will be “warning”, otherwise, it will be “debug”.
To explicitly set default handler minimum level it is possible to use its withMinimumLevel
method:
add_action(
'wonolog.handler-setup',
function (Monolog\Handler\HandlerInterface $handler) {
if ($handler instanceof Inpsyde\Wonolog\DefaultHandler\FileHandler) {
$handler->withMinimumLevel(Inpsyde\Wonolog\LogLevel::ERROR);
}
}
);
Note how the minimum level is set using a Wonolog LogLevel
class constant. The reason is that PSR-3 log levels have a string form, so it is not possible to determine which level is higher/lower programmatically. Wonolog LogLevel
class constants “map” PSR-3 log levels to numeric values to make comparison possible.
Disabling the fallback handler
The fallback handler is an instance of FileHandler
that Wonolog instantiates for those channels that have no other handlers attached, to ensure no log is lost.
It might be desirable to disable this behavior, so those log records having a channel not assigned to any handler will not be handled.
add_action(
'wonolog.setup',
function (Inpsyde\Wonolog\Configurator $config) {
$config->disableFallbackHandler();
}
);
Alternatively, it is possible to disable the fallback handler only for specific channels:
add_action(
'wonolog.setup',
function (Inpsyde\Wonolog\Configurator $config) {
$config->disableFallbackHandlerForChannels('DEBUG', 'SOME_PLUGIN');
}
);
About auto-calculated logs folder
When using in Wonolog a Monolog handler that writes files it might be desired to calculate the logs folder in the same way used by FileHandler
, including the creation of .htaccess
file inside the folder to prevent public access.
That is possible thanks to the Inpsyde\Wonolog\LogsFolder::determineFolder()
static method, the same internally used by FileHandler
. The method optionally accepts a custom folder path as parameter, in that case the method ensures the given folder is created (with proper access rights) and the .htaccess
file is placed inside it, in the case it is a subfolder of WP content or WP uploads folder.
About auto-calculated minimum log level
Many times when integrating Monolog handler for Wonolog it is desired to use the same log level used by FileHandler
, that is the one based on WONOLOG_MIN_LEVEL
environment variable or WP_DEBUG_LOG
constant.
In that case it is possible to use Inpsyde\Wonolog\LogLevel::defaultMinLevel()
static method, the same FileHandler
uses internally to determine the minimum level to use when none is explicitly configured.