Appenders are the sinks of Logback, where ILoggingEvent go to get serialized.
There's a lot to say about appenders, because appenders are central to Logback.
The first thing to say is that most of the time, you won't need custom appenders. You'll do fine with the out of the box ones.
However, if you do need to write custom appenders or the base ILoggingEvent API isn't doing it for you, you can use appenders to construct logging pipelines that are very different from anything you'll find out of the box.
The extensible part of appenders comes from the concept of an Attachable. This is not found anywhere in the Logback manual, but a good part of Logback's functionality depends on it.
Attaching Stuff To Appenders
If you want to add things that aren't strings to your appender, then you need to add an Attachable. There are examples of attachables in FilterAttachable and AppenderAttachable, but the general concept applies anywhere you want to attach to a parent element in Logback.
For example, the previous post discussed a Player which could be used to play audio in response to an event.
and then you can add it to your appender like this:
and have a section in the PlayerAction that accounts for it:
and then you can set up appenders by logger, so only messages going to "audio" logger will make a sound:
Or you can set up appenders with filters to play only when certain conditions are met:
Logback can nest appenders using AppenderAttachable. You can use this to compose appenders together:
This means you can do things like this, putting two appenders together:
You can leverage nesting to keep your filtering logic under control. For example, you may want to have several things happen when you hit an error in your logs. Appenders will always write when they receive an event, unless they are filtered.
Using nesting, you can declare the filter once, and have the child appenders "inherit" that filter:
This makes your appender logic much cleaner.
Not only can we compose appenders together, but we can also decorate the logging event at the same time. This involves using the decorator pattern to add extra information to data.
So, given DecoratingAppender like so:
So, imagine that we want to extend ILoggingEvent so that we can include a unique id along with it.
We can create a UniqueIdEventAppender by extending DecoratingAppender:
And then use a converter to pull out directly:
And then refer to it in the layout:
This lets us enrich events seamlessly in the logback context.
Different appenders are useful in different environments.
Want colorized output on their consoles, with line oriented logs.
Would also like to be able to read through logs with debug, info and warnings in them, to track control flow. If you have the logs seperated, that makes it harder.
Generally don't want to run a local ELK stack or TCP appenders to see their logs.
Really want centralized logging, and a way to drill out on it. Structured logging especially.
May want to have everything write to STDOUT, as is case for Docker / 12 Factor Apps.
May have duplicate logs from the underlying architecture, that need to be dedupped.
May not want redundant / repeated messages, which developers are not as sensitive to.
Really hate getting paged with the same error repeatedly.
Logback is not aware of different environments. There's no out of the box way to say "in this environment I want these sets of appenders, but in this other environment I want these other sets of appenders."
Fortunately, adding this is pretty easy, by leveraging AppenderAttachable and pulling a key to select on:
The logback appenders under selection must have the name defined as an element, because Logback only looks for the name attribute at the top level, but otherwise they're the same. Here, we select the set of appenders we want based on the LOGBACK_ENVIRONMENT environment variable.
This is a much cleaner way to organize appenders than putting Janino logic into the configuration.
Appenders are a lot more flexible than you'd believe, and you can do more with them than you think.