How to Add extra Data to Log Messages via a Processor
Monolog allows you to process every record before logging it by adding some extra data. This is the role of a processor, which can be applied for the whole handler stack or only for a specific handler or channel.
A processor is a callable receiving the record as its first argument.
Processors are configured using the monolog.processor
DIC tag. See the
reference about it.
Adding a Session/Request Token
Sometimes it is hard to tell which entries in the log belong to which session and/or request. The following example will add a unique token for each request using a processor:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
// src/Logger/SessionRequestProcessor.php
namespace App\Logger;
use Monolog\LogRecord;
use Monolog\Processor\ProcessorInterface;
use Symfony\Component\HttpFoundation\Exception\SessionNotFoundException;
use Symfony\Component\HttpFoundation\RequestStack;
class SessionRequestProcessor implements ProcessorInterface
{
public function __construct(
private RequestStack $requestStack
) {
}
// method is called for each log record; optimize it to not hurt performance
public function __invoke(LogRecord $record): LogRecord
{
try {
$session = $this->requestStack->getSession();
} catch (SessionNotFoundException $e) {
return $record;
}
if (!$session->isStarted()) {
return $record;
}
$sessionId = substr($session->getId(), 0, 8) ?: '????????';
$record->extra['token'] = $sessionId.'-'.substr(uniqid('', true), -8);
return $record;
}
}
Next, register your class as a service, as well as a formatter that uses the extra information:
1 2 3 4 5 6 7 8 9 10
# config/services.yaml
services:
monolog.formatter.session_request:
class: Monolog\Formatter\LineFormatter
arguments:
- "[%%datetime%%] [%%extra.token%%] %%channel%%.%%level_name%%: %%message%% %%context%% %%extra%%\n"
App\Logger\SessionRequestProcessor:
tags:
- { name: monolog.processor }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
<!-- config/services.xml -->
<?xml version="1.0" encoding="UTF-8" ?>
<container xmlns="http://symfony.com/schema/dic/services"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:monolog="http://symfony.com/schema/dic/monolog"
xsi:schemaLocation="http://symfony.com/schema/dic/services
https://symfony.com/schema/dic/services/services-1.0.xsd
http://symfony.com/schema/dic/monolog
https://symfony.com/schema/dic/monolog/monolog-1.0.xsd">
<services>
<service id="monolog.formatter.session_request"
class="Monolog\Formatter\LineFormatter">
<argument>[%%datetime%%] [%%extra.token%%] %%channel%%.%%level_name%%: %%message%% %%context%% %%extra%%
</argument>
</service>
<service id="App\Logger\SessionRequestProcessor">
<tag name="monolog.processor"/>
</service>
</services>
</container>
1 2 3 4 5 6 7 8 9 10 11
// config/services.php
use App\Logger\SessionRequestProcessor;
use Monolog\Formatter\LineFormatter;
$container
->register('monolog.formatter.session_request', LineFormatter::class)
->addArgument('[%%datetime%%] [%%extra.token%%] %%channel%%.%%level_name%%: %%message%% %%context%% %%extra%%\n');
$container
->register(SessionRequestProcessor::class)
->addTag('monolog.processor');
Finally, set the formatter to be used on whatever handler you want:
1 2 3 4 5 6 7 8
# config/packages/prod/monolog.yaml
monolog:
handlers:
main:
type: stream
path: '%kernel.logs_dir%/%kernel.environment%.log'
level: debug
formatter: monolog.formatter.session_request
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
<!-- config/packages/prod/monolog.xml -->
<?xml version="1.0" encoding="UTF-8" ?>
<container xmlns="http://symfony.com/schema/dic/services"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:monolog="http://symfony.com/schema/dic/monolog"
xsi:schemaLocation="http://symfony.com/schema/dic/services
https://symfony.com/schema/dic/services/services-1.0.xsd
http://symfony.com/schema/dic/monolog
https://symfony.com/schema/dic/monolog/monolog-1.0.xsd">
<monolog:config>
<monolog:handler
name="main"
type="stream"
path="%kernel.logs_dir%/%kernel.environment%.log"
level="debug"
formatter="monolog.formatter.session_request"
/>
</monolog:config>
</container>
1 2 3 4 5 6 7 8 9 10 11
// config/packages/prod/monolog.php
use Symfony\Config\MonologConfig;
return static function (MonologConfig $monolog): void {
$monolog->handler('main')
->type('stream')
->path('%kernel.logs_dir%/%kernel.environment%.log')
->level('debug')
->formatter('monolog.formatter.session_request')
;
};
If you use several handlers, you can also register a processor at the handler level or at the channel level instead of registering it globally (see the following sections).
When registering a new processor, instead of adding the tag manually in your
configuration files, you can use the #[AsMonologProcessor]
attribute to
apply it on the processor class:
1 2 3 4 5 6 7 8 9 10
// src/Logger/SessionRequestProcessor.php
namespace App\Logger;
use Monolog\Attribute\AsMonologProcessor;
#[AsMonologProcessor]
class SessionRequestProcessor
{
// ...
}
The #[AsMonologProcessor]
attribute takes these optional arguments:
channel
: the logging channel the processor should be pushed to;handler
: the handler the processor should be pushed to;method
: the method that processes the records (useful when applying the attribute to the entire class instead of a single method).
3.8
The #[AsMonologProcessor]
attribute was introduced in MonologBundle 3.8.
Symfony's MonologBridge provides processors that can be registered inside your application.
- DebugProcessor
- Adds additional information useful for debugging like a timestamp or an error message to the record.
- TokenProcessor
- Adds information from the current user's token to the record namely username, roles and whether the user is authenticated.
- SwitchUserTokenProcessor
- Adds information about the user who is impersonating the logged in user, namely username, roles and whether the user is authenticated.
- WebProcessor
- Overrides data from the request using the data inside Symfony's request object.
- RouteProcessor
- Adds information about current route (controller, action, route parameters).
- ConsoleCommandProcessor
- Adds information about the current console command.
See also
Check out the built-in Monolog processors to learn more about how to create these processors.
Registering Processors per Handler
You can register a processor per handler using the handler
option of
the monolog.processor
tag:
1 2 3 4 5
# config/services.yaml
services:
App\Logger\SessionRequestProcessor:
tags:
- { name: monolog.processor, handler: main }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
<!-- config/services.xml -->
<?xml version="1.0" encoding="UTF-8" ?>
<container xmlns="http://symfony.com/schema/dic/services"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:monolog="http://symfony.com/schema/dic/monolog"
xsi:schemaLocation="http://symfony.com/schema/dic/services
https://symfony.com/schema/dic/services/services-1.0.xsd
http://symfony.com/schema/dic/monolog
https://symfony.com/schema/dic/monolog/monolog-1.0.xsd">
<services>
<service id="App\Logger\SessionRequestProcessor">
<tag name="monolog.processor" handler="main"/>
</service>
</services>
</container>
1 2 3 4 5 6
// config/services.php
// ...
$container
->register(SessionRequestProcessor::class)
->addTag('monolog.processor', ['handler' => 'main']);
Registering Processors per Channel
By default, processors are applied to all channels. Add the channel
option
to the monolog.processor
tag to only apply a processor for the given channel:
1 2 3 4 5
# config/services.yaml
services:
App\Logger\SessionRequestProcessor:
tags:
- { name: monolog.processor, channel: 'app' }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
<!-- config/services.xml -->
<?xml version="1.0" encoding="UTF-8" ?>
<container xmlns="http://symfony.com/schema/dic/services"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:monolog="http://symfony.com/schema/dic/monolog"
xsi:schemaLocation="http://symfony.com/schema/dic/services
https://symfony.com/schema/dic/services/services-1.0.xsd
http://symfony.com/schema/dic/monolog
https://symfony.com/schema/dic/monolog/monolog-1.0.xsd">
<services>
<service id="App\Logger\SessionRequestProcessor">
<tag name="monolog.processor" channel="app"/>
</service>
</services>
</container>
1 2 3 4 5 6
// config/services.php
// ...
$container
->register(SessionRequestProcessor::class)
->addTag('monolog.processor', ['channel' => 'app']);