Cartoon avatar of Charles Sprayberry
cspray.io

Marked Logs and Monolog Channels

I recently introduced a library called cspray/marked-logs that's designed to help you mark your logs with an identifier for easy log aggregation. The idea was inspired by my time working with Java and SLF4J that has a similar concept. I won't go into how marked-logs works, the repo has a README with a guide and the code itself is straightforward to read and understand.

I posted on my Mastodon host, phpc.social, and got a message asking, "Nice, but how does it differ from setting a 'channel' in Monolog?"

What are Monolog Channels?

To be quite frank, I haven't done a lot of advanced Monolog configurations. All the PHP projects I've worked on professionally have been legacy in the truest sense of the term. Logging had already been figured out long before I arrived, and you certainly weren't gonna start mucking around with it now! So, I had to do a little research into what a Monolog channel is. From the docs…

Every Logger instance has a channel (name) and a stack of handlers. Whenever you add a record to the logger, it traverses the handler stack. Each handler decides whether it fully handled the record, and if so, the propagation of the record ends there.

This allows for flexible logging setups, for example having a StreamHandler at the bottom of the stack that will log anything to disk, and on top of that add a MailHandler that will send emails only when an error message is logged.

You can create many Loggers, each defining a channel (e.g.: db, request, router, ..) and each of them combining various handlers, which can be shared or not. The channel is reflected in the logs and allows you to easily see or filter records.

It turns out that I did know what Monolog channels were, I just knew them by the term "logger name". The more you know! More importantly, it is immediately clear why channels would be useful. Database logs could be sent off to some service your DBA has access to, request logs could be sent to the networking gurus, and countless other possibilities.

Ok, so now I can talk about what channels are…why markers? But, first…

Our Mental JIRA Ticket

It is easier for me to talk about coding concepts if there's some requirement, even if it is completely made up! In our scenario, we're writing code for an e-commerce shop. There's been a small, but noticeable, decrease in customers completing the checkout process and some users have reported that the checkout process failed, and they were unable to complete their purchases. You've been tasked with analyzing the logs to figure out what's going on.

The rest of this article talks about how marked logs can help you in this situation, and those similar to it.

Markers in the Same Channel

Let's imagine a channel called request that receives logs for all the HTTP requests your app receives. In many apps this is gonna be a noisy log, especially if we're only concerned with a specific set of them. Marked logs can help with this problem. Created a marked-logs logger with a CheckoutMarker and associate specific routes to the checkout process. Now, when analyzing those request logs you can filter out more noise and see more signal.

Thinking outside the context of our Mental JIRA Ticket, we can imagine how adding an AuthenticationMarker to all login, logout, and password reset routes could be useful. When an authentication issue pops up it can be easier to see the related requests.

Markers in Different Channels

Chances are, complex problems require looking at logs from multiple channels. This is where a marker on your logs can be really helpful. Imagine the same CheckoutMarker used not just in your request channel, but your database channel and mailer channel and any other place where the checkout process might be pertinent. Now, when you search over the complete logs you can get an idea of what the checkout process looks like across your entire app!

Multiple Markers on Same Log Record

Thinking about how markers and channels can be used together made me think of an idea that I haven't implemented in marked-logs, but plan on doing so. That being the concept of multiple markers. Perhaps in looking into our checkout problem we decide we need more information. So, we add a CheckoutAbandonedMarker that we add to the abandon process, in addition to the existing CheckoutMarker. Now, we're able to drill down even further, taking that larger set of logs about checkout and filtering to just those dealing with the checkout abandonment process.

In Summary

I hope the examples above demonstrate how channels and markers in your logs can be useful. I'd like to answer the question from above, "How does it differ from setting a 'channel' in Monolog?" with a summary of my thoughts.

Monolog channels, among other functionality, provides a way to have coarsely filtered logs. Marked logs provides a way to have granular filtered logs and tie cohesive logs from different channels together. Channels and markers complement each other and, in a sufficiently complex application, you are likely to benefit from using both.