Logback Custom Appender Example
This article discusses creating a custom Appender for logback
, a logging framework for the Java application.
1. Introduction to Logback
Logback
is designed to be the successor for Log4j
. It has been developed by the same development community. These are some of the advantages logback has over log4j
- Faster implementation – ~10x faster on some critical areas
- Automatic Reloading of configuration files
- Ability to configure in groovy
- Gracefully recover from I/O failures
- Conditional processing of configuration files
- Native Support for SLF4J
SLF4J is expanded as Simple Logging Facade for Java. It provides a logging facade to Java applications enabling the option to switch out logging framework. Currently, it supports Log4J
, Logback
and java.util.logging
.
Logback
uses Appenders
to write to the logging destination. Appender has configurable properties which can be used to fine-tune it and also supply the logging destination.
2. Appenders
SLF4J provides a Logger
interface which must be implemented by concrete implementation. Logback provides a concrete implementation for the Logger
interface specified in SLF4J. Logback implementation of logging various levels use appenders to log the event to a specified destination.
In Logback, all Appenders must implement the Appender interface which specifies that doAppend
method must be implemented along with setting a name for the appender. The destination also depends on the appender being used. For convenience, two abstract implementations of appender have been provided in Logback.
AppenderBase
– It provides basic functionality such as getting or settingAppender
name, activation status, layout and filters.UnsynchronizedAppenderBase
– This is similar toAppenderBase
except that it does not handle thread synchronization and theAppender
class extending must handle it, if needed.
In this section, we will take a look at an existing appender with the help of a simple project. The first step is to look at the gradle file used to manage the project.
build.gradle
apply plugin: 'java' apply plugin: 'idea' group = 'com.jcg' version = '0.0.1-SNAPSHOT' sourceCompatibility = 1.8 repositories { mavenCentral() } dependencies { compile 'ch.qos.logback:logback-classic:1.2.3' compile 'org.slf4j:slf4j-api:1.7.25' }
- In lines 1-2, we specify plugins as java and idea to indicate we are running IntelliJ Idea Java project. Eclipse plugin can be used in place of idea.
- We specify
group
,version
information along withsourceCompatibility
to indicate the Java version of 1.8. - We use
mavenCentral
as the repository to fetch dependencies to be used. - The dependencies for our project are just
logback
andslf4j
.
logback.xml
<configuration> <appender name="console" class="ch.qos.logback.core.ConsoleAppender"> <target>System.out</target> <encoder> <pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern> </encoder> </appender> <root level="info"> <appender-ref ref="console"/> </root> </configuration>
- An instance of
ConsoleAppender
is created with target as System.out. We can also use System.err as the destination. - An encoder with layout (
PatternLayout
) specified to affix logs with current time and thread executing it. - Logger has been specified with info level and reference to console appender is provided.
LoggerRoot.java
public class LoggerRoot { private static final Logger logger = LoggerFactory.getLogger(LoggerRoot.class.getSimpleName()); public static void main(String... args){ IntStream.rangeClosed(1,10).forEach(counter->{ logger.info("Counter:" + counter); }); } }
- This is a simple application to log the counter to the console.
- It runs the for loop from 1 to 10 and prints the number to console. The output is visible in the screenshot attached below.
3. Custom Appender
In this section, let us extend the previous example with our own custom appender. We will create a custom Appender to store the log messages in a concurrent map similar to an in-memory database. To store the concurrent map, we will create a MapHolder
singleton class.
MapHolder.java
public class MapHolder { private Map eventMap = new ConcurrentHashMap();; private MapHolder(){} private static MapHolder MAP_INSTANCE = null; public static MapHolder create(){ if(MAP_INSTANCE == null){ MAP_INSTANCE = new MapHolder(); } return MAP_INSTANCE; } public void putEvent(String key,String value){ eventMap.put(key,value); } public Map getEventMap(){ return eventMap; } }
- The above class follows the singleton pattern and always create the same instance of the class on any invocation.
- It has
putEvent
method to append events to the hashmap whilegetEvent
is used to get the hashmap for usage.
In the section below, we will see the implementation of our Custom appender
MapAppender.java
public class MapAppender extends AppenderBase { MapHolder holder = MapHolder.create(); @Override protected void append(LoggingEvent event) { holder.putEvent(String.valueOf(System.nanoTime()), event.getMessage()); } }
MapAppender
extendsAppenderBase
enabling synchronization with sensible defaults provided by theAppenderBase
class.- It has a holder variable to initialize the
MapHolder
class and stores the object inside its state. - It implements the
append
method which will be called by the logback logger at runtime to log theLoggingEvent
. append
method just puts the event into theMapHolder
class for storing the event inConcurrentHashMap
.
logback.xml
<configuration> ... <appender name="map" class="com.jcg.logbackappender.MapAppender"> </appender> <root level="info"> <appender-ref ref="console"/> <appender-ref ref="map"/> </root> </configuration>
- In the configuration, we create an instance of our new appender class.
- This created instance is tied up to the root logger for logging the messages.
To verify the capture of log messages in the map, We can leverage the following code.
LoggerRoot.java
MapHolder.create().getEventMap().values().forEach((value) -> { System.out.println(value); });
- Since
MapHolder
is singleton, we always get the same instance. - The above code gets the
eventMap
and logs the events to console. - One interesting thing seen from the output below is that since it is
HashMap
, the order of the logs is not guaranteed. The key stores the timestamp and can be used to order the log events.
Log Output
Counter:2 Counter:3 Counter:9 Counter:8 Counter:1 Counter:6 Counter:5 Counter:4 Counter:7 Counter:10
4. Download the Source Code
You can download the full source code of this example here: Logback Custom Appender Example