Logback

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 setting Appender name, activation status, layout and filters.
  • UnsynchronizedAppenderBase – This is similar to AppenderBase except that it does not handle thread synchronization and the Appender 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 with sourceCompatibility 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 and slf4j.

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.
Logback Custom Appender - Log Example
Log Example

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 while getEvent 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 extends AppenderBase enabling synchronization with sensible defaults provided by the AppenderBase 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 the LoggingEvent.
  • append method just puts the event into the MapHolder class for storing the event in ConcurrentHashMap.

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

Download
You can download the full source code of this example here: Logback Custom Appender Example

Rajagopal ParthaSarathi

Rajagopal works in software industry solving enterprise-scale problems for customers across geographies specializing in distributed platforms. He holds a masters in computer science with focus on cloud computing from Illinois Institute of Technology. His current interests include data science and distributed computing.
Subscribe
Notify of
guest

This site uses Akismet to reduce spam. Learn how your comment data is processed.

0 Comments
Inline Feedbacks
View all comments
Back to top button