Logger Channel Setup and Autowiring

Keep on Learning!

If you liked what you've learned so far, dive in!
Subscribe to get access to this tutorial plus
video, code and script downloads.

Start your All-Access Pass
Buy just this tutorial for $12.00

With a Subscription, click any sentence in the script to jump to that part of the video!

Login Subscribe

Here's our goal... and the end result is going to be pretty cool: leverage our middleware - and the fact that we're adding this unique id to every message - to log the entire lifecycle of a message to a file. I want to see when a message was originally dispatched, when it was sent to the transport, when it was received from the transport and when it was handled.

Adding a Log Handler

Before we get into the middleware stuff, let's configure a new logger channel that logs to a new file. Open up config/packages/dev/monolog.yaml and add a new channels key. Wait... that's not right. A logging channel is, sort of a "category", and you can control how log messages for each category are handled. We don't want to add it here because then that new channel would only exist in the dev environment. Nope, we want the channel to exist in all environments... even if we decide to only give those messages special treatment in dev.

To do that, directly inside config/packages, create a new file called monolog.yaml... though... remember - the names of these config files aren't important. What is important is to add a monolog key, then channels set to an array with one new one - how about messenger_audit.

monolog:
channels: [messenger_audit]

Thanks to this, we now have a new logger service in the container for this channel. Let's find it: at your terminal, run:

php bin/console debug:container messenger_audit

There it is: monolog.logger.messenger_audit - we'll use that in a minute. But first, I want to make any logs to this channel save to a new file in the dev environment. Back up in config/packages/dev/monolog.yaml, copy the main handler, paste and change the key to messenger... though that could be anything. Update the file to be called messenger.log and - here's the magic - instead of saying: log all messages except those in the event channel, change this to only log messages that are in that messenger_audit channel.

monolog:
handlers:
... lines 3 - 7
messenger:
type: stream
path: "%kernel.logs_dir%/messenger.log"
level: debug
channels: ["messenger_audit"]
... lines 13 - 25

Autowiring the Channel Logger

Cool! To use this service, we can't just autowire it by type-hinting the normal LoggerInterface... because that will give us the main logger. This is one of those cases where we have multiple services in the container that all use the same class or interface.

To make it wirable, back in services.yaml, add a new global bind: $messengerAuditLogger that points to the service id: copy that from the terminal, then paste as @monolog.logger.messenger_audit.

... lines 1 - 7
services:
... line 9
_defaults:
... lines 11 - 12
bind:
... lines 14 - 15
$messengerAuditLogger: '@monolog.logger.messenger_audit'
... lines 17 - 34

Thank to this, if we use an argument named $messengerAuditLogger in the constructor of a service or in a controller, Symfony will pass us that service. By the way, starting in Symfony 4.2, instead of binding only to the name of the argument, you can also bind to the name and type by saying Psr\Log\LoggerInterface $messengerAuditLogger. That just makes things more specific: Symfony would pass us this service for any arguments that have this name and the LoggerInterface type-hint.

Anyways, we have a new logger channel, that channel will log to a special file, and the logger service for that channel is wirable. Time to get to work!

Close up the monolog config files and go to AuditMiddleware. Add a public function __construct() with one argument LoggerInterface $messengerAuditLogger - the same name we used in the config. I'll call the property itself $logger, and finish this with $this->logger = $messengerAuditLogger.

... lines 1 - 4
use Psr\Log\LoggerInterface;
... lines 6 - 10
class AuditMiddleware implements MiddlewareInterface
{
private $logger;
public function __construct(LoggerInterface $messengerAuditLogger)
{
$this->logger = $messengerAuditLogger;
}
... lines 19 - 40
}

Setting up the Context

Down in handle(), remove the dump() and create a new variable called $context. In addition to the actual log message, it's a little-known fact that you can pass extra information to the logger... which is super handy! Let's create a key called id set to the unique id, and another called class that's set to the class of the original message class. We can get that with get_class($envelope->getMessage()).

... lines 1 - 10
class AuditMiddleware implements MiddlewareInterface
{
... lines 13 - 19
public function handle(Envelope $envelope, StackInterface $stack): Envelope
{
... lines 22 - 28
$context = [
'id' => $stamp->getUniqueId(),
'class' => get_class($envelope->getMessage())
];
... lines 33 - 39
}
}

Let's do the logging next! It's a bit more interesting than you might expect. How can we figure out if the current message was just dispatched or was just received asynchronously from a transport? And if it was just dispatched, how can we find out whether or not the message will be handled right now or sent to a transport for later? The answer... lies in the stamps!

Leave a comment!

This tutorial is built with Symfony 4.3, but will work well on Symfony 4.4 or 5.

What PHP libraries does this tutorial use?

// composer.json
{
    "require": {
        "php": "^7.1.3",
        "ext-ctype": "*",
        "ext-iconv": "*",
        "intervention/image": "^2.4", // 2.4.2
        "league/flysystem-bundle": "^1.0", // 1.1.0
        "sensio/framework-extra-bundle": "^5.3", // v5.3.1
        "symfony/console": "4.3.*", // v4.3.2
        "symfony/dotenv": "4.3.*", // v4.3.2
        "symfony/flex": "^1.1", // v1.4.4
        "symfony/framework-bundle": "4.3.*", // v4.3.2
        "symfony/messenger": "4.3.*", // v4.3.4
        "symfony/orm-pack": "^1.0", // v1.0.6
        "symfony/serializer-pack": "^1.0", // v1.0.2
        "symfony/validator": "4.3.*", // v4.3.2
        "symfony/webpack-encore-bundle": "^1.5", // v1.6.2
        "symfony/yaml": "4.3.*" // v4.3.2
    },
    "require-dev": {
        "symfony/debug-pack": "^1.0", // v1.0.7
        "symfony/maker-bundle": "^1.0", // v1.12.0
        "symfony/test-pack": "^1.0" // v1.0.6
    }
}