Logback File Appender Example
Logging is a common and essential issue for software development. Logging allows you to analyse the program execution flow, to detect the bugs and warnings in the code. So logs are frequently the best (and sometimes the only) source of information about a running program.
In this example, we are going to show you how to record log messages in files using Logback framework. After a brief introduction to the overall Logback framework and File Appender in the Logback, we will discuss the implementation details of our example.
1. What is Logback?
Logback is the most recent and modern logging framework in the Java enterprise world. It gained the decade of experience in the logging systems. Log4j, Java logging API, Apache Commons Logging are some other alternatives. As a successor to the popular Log4j project, Logback is also designed by Ceki Gülcü, Log4j’s founder.
1.1 Logback Architecture
Logback is divided into three modules, logback-core, logback-classic and logback-access. The core module provides the groundwork for the other two modules. The classic module corresponds to a significantly improved version of Log4j. The logback-access module integrates with Servlet containers, such as Tomcat and Jetty, to provide rich and powerful HTTP-access log functionality. But this module is out of scope in our example.
It is a good practice to write the log statements with SLF4J API that enables simple abstraction for various logging frameworks. At runtime, SLF4J codes are bound to the preferred logging framework in the classpath. Logback-classic module natively implements the SLF4J API. So this flexible architecture allows the end user to switch back and forth between other logging systems and Logback.
1.2 Logback Configuration
Logback can be configured either programmatically or with a configuration file expressed in XML or Groovy format. Logback follows these steps to try to configure itself:
1) Logback tries to find a file called logback.groovy
in the classpath.
2) If no such file is found, logback tries to find a file called logback-test.xml
in the classpath.
3) If no such file is found, it checks for the file logback.xml
in the classpath.
4) If neither file is found, logback configures itself automatically using the BasicConfigurator
which will cause logging output to be directed to the console.
Each log event for a given logger is forwarded to the relevant appender. Appenders are responsible for writing the event data to the target destination system such as console, file, e-mail, syslog. In our example, we use file appender in order to forward the log messages to the files.
1.3 File Appender in the Logback
File Appender which is the major topic of this example appends log events into a file. We display the file appender properties in a table below and explain them in a nutshell:
Property Name | Description |
---|---|
append | If this boolean type property is true, messages are appended at the end of an existing file. Otherwise, any existing file is truncated. Default append option is true. |
file | It indicates the name of the file to write to. If the file does not exist, it is created. If the parent directory of the file does not exist, FileAppender will automatically create it, including any necessary but nonexistent parent directories. |
encoder | Determines the behaviour in which an event is written to the underlying OutputStreamAppender. At the present time, PatternLayoutEncoder is the only really useful encoder. Pattern layouts express the log message format with some fields such as length, thread name, log level… In previous versions of Logback, PatternLayout is nested within a FileAppender. Since Logback 0.9.19, FileAppender and sub-classes expect an encoder and no longer take a layout. |
prudent | If the value of this boolean type option is true, logs are appended in prudent mode. The prudent mode in Logback serializes IO operations between all JVMs writing to the same file, potentially running on different hosts. Thus, it provides safely writing to the specified file with file locks. Default option is false. |
2. Overview
We design a simple printer interface. Consider a print
method of the interface that accepts two parameters: A message
to print and message id
number. Think of an exceptional case: When the message id
number is divisible by three, the print
method of the Printer
class always throw an error.
In the main
method of the application, we invoke the print
method of the Printer
class in a loop from one to ten. We send loop counter as a message id
number. As a result, we expect to get exceptions when the method is called with message id numbers of 3, 6 and 9 which are divisible by three. Thereby this scenario, we can illustrate logging errors in the exceptional situations.
You may skip project creation and jump directly to the beginning of the example below.
3. Create a new Maven project
Go to File -> New -> Project ->Maven -> Maven Project.
In the next screen, accept the default options and click on Next
In the next screen, select the maven-archetype-quickstart option and click Next
In the next screen, type the Group Id, Artifact Id and Package, as in the following screen and click on Finish
As you see, the project is created in your workspace.
3.1 Adding Maven dependencies
Before we execute some code, we need to add logback dependencies in the Maven’s pom.xml
file. It is enough to add only logback-classic
artifact. Logback-core
and slf4j-api
packages are transitive dependencies of the logback-classic
artifact. By the power of the Maven Dependency Management, they are automatically added to the classpath. We would like to show groovy-based configuration, so we add the groovy package in the pom.xml
.
pom.xml
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.javacodegeeks.examples</groupId> <artifactId>logbackfileappenderexample</artifactId> <version>0.0.1-SNAPSHOT</version> <packaging>jar</packaging> <name>logbackfileappenderexample</name> <url>http://maven.apache.org</url> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> </properties> <build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <version>3.2</version> <configuration> <source>1.7</source> <target>1.7</target> </configuration> </plugin> </plugins> </build> <dependencies> <dependency> <groupId>ch.qos.logback</groupId> <artifactId>logback-classic</artifactId> <version>1.1.3</version> </dependency> <dependency> <groupId>org.codehaus.groovy</groupId> <artifactId>groovy</artifactId> <version>2.4.3</version> </dependency> </dependencies> </project>
4. Implementation
In this example, we configure the Logback with Groovy script. We are creating two file appenders: One of them is for audit reports that includes all of the log messages, the other one is only for error logs. Audit log messages file path is c:/logs/printerdebug.log
, error logs file path is c:/logs/printererror.log
. As a good practice, we may keep the error logs in a different private file in order to explore straight when a code defect is reported to us.
logback.groovy
import ch.qos.logback.classic.encoder.PatternLayoutEncoder import ch.qos.logback.core.FileAppender def logHomeDirectory = "c:/logs/printer" appender("AUDIT_FILE", FileAppender) { file = "${logHomeDirectory}debug.log" encoder(PatternLayoutEncoder) { pattern = "%-5level %logger{36} - %msg%n" } } appender("ERROR_FILE", FileAppender) { file = "${logHomeDirectory}error.log" encoder(PatternLayoutEncoder) { pattern = "%-5level %logger{36} - %msg%n" } } logger("com.javacodegeeks.examples.logbackfileappenderexample.exception", ERROR , ["ERROR_FILE"]) logger("com.javacodegeeks.examples.logbackfileappenderexample", DEBUG , ["AUDIT_FILE"])
Groovy script is not used widely as xml configuration. So if you intend to prefer xml configuration, the xml equivalent of the groovy script is below. But please note that as we explain in the configuration section, logback.groovy
file has higher priority than logback.xml
file. Logback takes into account the logback.groovy
file configuration, if both of the them are in the classpath of the code.
logback.xml
<?xml version="1.0" encoding="UTF-8"?> <configuration> <property name="LOG_HOME" value="c:/logs/printer" /> <appender name="AUDIT_FILE" class="ch.qos.logback.core.FileAppender"> <file>${LOG_HOME}debug.log</file> <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder"> <Pattern> %-5level %logger{36} - %msg%n </Pattern> </encoder> </appender> <appender name="ERROR_FILE" class="ch.qos.logback.core.FileAppender"> <file>${LOG_HOME}error.log</file> <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder"> <Pattern> %-5level %logger{36} - %msg%n </Pattern> </encoder> </appender> <logger name="com.javacodegeeks.examples.logbackfileappenderexample.exception" level="ERROR"> <appender-ref ref="ERROR_FILE" /> </logger> <logger name="com.javacodegeeks.examples.logbackfileappenderexample" level="DEBUG"> <appender-ref ref="AUDIT_FILE" /> </logger> </configuration>
We create a custom Exception class for our special “divide by three” error case. We write error
level log, when this exception occurs.
PrinterDivideByThreeException.java
package com.javacodegeeks.examples.logbackfileappenderexample.exception; import org.slf4j.Logger; import org.slf4j.LoggerFactory; public class PrinterDivideByThreeException extends Exception { private static final Logger LOGGER = LoggerFactory.getLogger( PrinterDivideByThreeException.class ); private static final long serialVersionUID = 445670554417085824L; public PrinterDivideByThreeException( final String message, final int id ) { super( message ); LOGGER.error( "Printing was failed. Message id : {}, Error message: {}", id, message ); } }
In the print
method of our Printer
class, we create logs in debug level for every call at the beginning of the method. Then we check whether the message id number is divisible by three. If so, we throw our custom Exception
, furthermore we provide the logging in error
level. If the message id number is not divisible by three, we follow with info logging and return “success”.
Printer.java
package com.javacodegeeks.examples.logbackfileappenderexample; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.javacodegeeks.examples.logbackfileappenderexample.exception.PrinterDivideByThreeException; public class Printer { private static final Logger LOGGER = LoggerFactory.getLogger( Printer.class ); public String print( final String message, final int id ) throws PrinterDivideByThreeException { LOGGER.debug( "Message was received to print. Message : {}, Message id : {}", message, id ); // If the message id is divisible by three, then throw exception. if ( id % 3 == 0 ) { throw new PrinterDivideByThreeException( "Message id can not be divided by three", id ); } LOGGER.info( "Printing is success. Message id : {}", id ); return "success"; } }
In the ApplicationStarter
class that holds the main
method, we invoke the print interface ten times in a for
loop.
ApplicationStarter.java
package com.javacodegeeks.examples.logbackfileappenderexample; import com.javacodegeeks.examples.logbackfileappenderexample.exception.PrinterDivideByThreeException; public class ApplicationStarter { public static void main( final String[] args ) { final Printer printer = new Printer(); // Send ten messages for ( int i = 1; i <= 10; i++ ) { try { printer.print( "Message" + i, i ); } catch ( final PrinterDivideByThreeException e ) { } } } }
When the execution of the application is completed, we obtain the two log files as below.
printerdebug.log
DEBUG c.j.e.l.Printer - Message was received to print. Message : Message1, Message id : 1 INFO c.j.e.l.Printer - Printing is success. Message id : 1 DEBUG c.j.e.l.Printer - Message was received to print. Message : Message2, Message id : 2 INFO c.j.e.l.Printer - Printing is success. Message id : 2 DEBUG c.j.e.l.Printer - Message was received to print. Message : Message3, Message id : 3 ERROR c.j.e.l.exception.PrinterException - Printing was failed. Message id : 3, Error message: Message id can not be divided by three DEBUG c.j.e.l.Printer - Message was received to print. Message : Message4, Message id : 4 INFO c.j.e.l.Printer - Printing is success. Message id : 4 DEBUG c.j.e.l.Printer - Message was received to print. Message : Message5, Message id : 5 INFO c.j.e.l.Printer - Printing is success. Message id : 5 DEBUG c.j.e.l.Printer - Message was received to print. Message : Message6, Message id : 6 ERROR c.j.e.l.exception.PrinterException - Printing was failed. Message id : 6, Error message: Message id can not be divided by three DEBUG c.j.e.l.Printer - Message was received to print. Message : Message7, Message id : 7 INFO c.j.e.l.Printer - Printing is success. Message id : 7 DEBUG c.j.e.l.Printer - Message was received to print. Message : Message8, Message id : 8 INFO c.j.e.l.Printer - Printing is success. Message id : 8 DEBUG c.j.e.l.Printer - Message was received to print. Message : Message9, Message id : 9 ERROR c.j.e.l.exception.PrinterException - Printing was failed. Message id : 9, Error message: Message id can not be divided by three DEBUG c.j.e.l.Printer - Message was received to print. Message : Message10, Message id : 10 INFO c.j.e.l.Printer - Printing is success. Message id : 10
printererror.log
ERROR c.j.e.l.exception.PrinterException - Printing was failed. Message id : 3, Error message: Message id can not be divided by three ERROR c.j.e.l.exception.PrinterException - Printing was failed. Message id : 6, Error message: Message id can not be divided by three ERROR c.j.e.l.exception.PrinterException - Printing was failed. Message id : 9, Error message: Message id can not be divided by three
5. Download the Eclipse Project
This project demonstrates how to log messages in the files using Logback.
You can download the full source code of this example here : logbackfileappenderexample