How to create separate log file for each module using RoutingAppender and MDC (Mapped Diagnostic Context) in log4j2/slf4j

Introduction

In this example we will see how to create or generate separate log file module-wise or for each separate functionality using RoutingAppender in log4j2 with the help of ThreadedContext or MDC. MDC or Mapped Diagnostic Context is a lighter technique consists of uniquely stamping each log request servicing a given client. To uniquely stamp each request, the user puts contextual information into the MDC.

In our previous example we have seen how to configure log4j2 for Java, Spring and Spring Boot applications but here we will control multiple log files using RoutingAppender and similar kind of example we had seen using log4j API.

The RoutingAppender evaluates LogEvents and then routes them to a subordinate Appender. The target Appender may be an appender previously configured and may be referenced by its name or the Appender can be dynamically created as needed. The RoutingAppender should be configured after any Appenders it references to allow it to shut down properly.

You can also configure a RoutingAppender with scripts: you can run a script when the appender starts and when a route is chosen for an log event.

Prerequisites

Please read Prerequisites and Create Build Configuration sections here.

Create log4j2.xml

Log4j is a logging library for Java based applications and purpose of inserting log statements into code is a low-tech method for debugging it. But make sure you follow the best practices while putting log statements.

Learn how to control logging into multiple files using log4j and Logging using logback in Spring Boot application.

Now we will create log4j2.xml file under the classpath directory src/main/resources.

As we are using log4j2 API, so the configuration file name is log4j2.xml or log4j2.properties. If you are using log4j API version 1, then your file name should be log4j.xml or log4j.properties.

Keeping the log4j2 XML or properties file in classpath will be picked up by the application automatically.

<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="OFF">

	<Properties>
		<Property name="LOG_LOC">logs</Property>
		<Property name="MAX">5</Property>
		<Property name="LOG_PATTERN">%d{yyyy.MM.dd HH:mm:ss.SSS} [%p] %c %x %m%n
		</Property>
	</Properties>

	<Appenders>
		<Routing name="Routing">
			<Routes pattern="$${ctx:module}">
				<Route key="$${ctx:module}">
					<RollingFile name="FILE" fileName="${LOG_LOC}/raw.log"
						filePattern="${LOG_LOC}/raw.%i.log">
						<PatternLayout>
							<Pattern>${LOG_PATTERN}</Pattern>
						</PatternLayout>

						<Policies>
							<OnStartupTriggeringPolicy />
						</Policies>

						<DefaultRolloverStrategy max="${MAX}" />
					</RollingFile>
				</Route>
				<Route>
					<RollingFile name="RAW"
						fileName="${LOG_LOC}/${ctx:module}.log"
						filePattern="${LOG_LOC}/${ctx:module}.%i.log">
						<PatternLayout>
							<Pattern>${LOG_PATTERN}</Pattern>
						</PatternLayout>

						<Policies>
							<OnStartupTriggeringPolicy />
						</Policies>

						<DefaultRolloverStrategy max="${MAX}" />
					</RollingFile>
				</Route>
			</Routes>
		</Routing>

		<Console name="STDOUT" target="SYSTEM_OUT" follow="true">
			<PatternLayout pattern="${LOG_PATTERN}" />
		</Console>
	</Appenders>

	<Loggers>
		<Logger name="com.roytuts" level="debug" />

		<Logger name="org.springframework.web" level="info"
			additivity="false" />

		<Logger name="file" level="debug" additivity="false">
			<appender-ref ref="Routing" />
		</Logger>

		<Root level="info">
			<AppenderRef ref="Routing" />
			<AppenderRef ref="STDOUT" />
		</Root>
	</Loggers>

</Configuration>

The status in the log4j2 configuration is used internally by log4j2 components. This is useful for troubleshooting the configuration issues. Here I have turned this off. You can set debug, warn etc. for the status.

Then we set some properties with key/value pairs.

The Routing is kept under Appender and it’s a RoutingAppender.

The Routes element accepts a single attribute named “pattern”. The pattern is evaluated against all the registered Lookups and the result is used to select a Route. Each Route may be configured with a key. If the key matches the result of evaluating the pattern then that Route will be selected. If no key is specified on a Route then that Route is the default. Only one Route can be configured as the default.

The Routes element may contain a Script child element. If specified, the Script is run for each log event and returns the String Route key to use.

You must specify either the pattern attribute or the Script element, but not both.

Each Route must reference an Appender. If the Route contains a ref attribute then the Route will reference an Appender that was defined in the configuration. If the Route contains an Appender definition then an Appender will be created within the context of the RoutingAppender and will be reused each time a matching Appender name is referenced through a Route.

In our example the Routes pattern is $${ctx:module}. This pattern matches with a key called module in the context.

We have two Route configured in the above log4j2.xml file configuration – one with a key another one without key which is default Route.

A Route with the key is resolved when there is no module found in the context and log statements are written to the file raw.log and backup files such as raw.1.log … raw.5.log are created depending upon the application start up.

Other log statements, when module is found in the context, are written to the file main.log and backup files such as main.1.log … main.5.log are created depending upon the application start up.

Each Route has RollingFile appender and writes logging to a file. Apart from the Routes we also have Console appender that writes log to the console.

We will see example how module in context gets resolved.

In RollingFile appender we set the file name and file name pattern. We have maximum backup file 5 apart from main.log file. The file name pattern will create main.1.log … main.5.log and another main.log file will be the current log file.

We have specified the pattern of the log statements.

Next we have specified policy to start a new log file whenever the application starts up. During start up the file name pattern comes into picture and maximum 5 files will be created apart from main log file.

Next we define some loggers with level of logging.

The root level of logging is info for file as well as console logging.

Avoid duplicate logging by adding aditivity=”false”.

Logging Example

To show how to create module-wise separate log file I have created below example class.

This class puts modules into MDC and ThreadedContext to segregate different modules. The log statements will be written to the log files according to the modules and separate log file will be created for each module.

Also notice we have written some log statements but did not put into any module and these log statements will be written to the raw.log file.

package com.roytuts.log4j2.java.spring.spring.boot;

import org.apache.logging.log4j.ThreadContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.slf4j.MDC;

public class Log4j2JavaApp {

	private static final Logger LOG = LoggerFactory.getLogger(Log4j2JavaApp.class.getName());

	public static void main(String[] args) {
		printLog();
	}

	private static void printLog() {
		MDC.put("module", "MDC");

		LOG.debug("Debug Message");
		LOG.warn("Warn Message");
		LOG.error("Error Message");
		LOG.info("Info Message");
		LOG.trace("Trace Message");

		MDC.remove("MDC");

		LOG.debug("Debug Message");
		LOG.warn("Warn Message");
		LOG.error("Error Message");
		LOG.info("Info Message");
		LOG.trace("Trace Message");

		ThreadContext.put("module", "Module1");
		LOG.debug("Module1 Debug Message");
		LOG.warn("Module1 Warn Message");
		LOG.error("Module1 Error Message");
		LOG.info("Module1 Info Message");

		ThreadContext.put("module", "Module2");
		LOG.debug("Module2 Debug Message");
		LOG.warn("Module2 Warn Message");
		LOG.error("Module2 Error Message");
		LOG.info("Module2 Info Message");

		ThreadContext.remove("module");
	}

}

Testing the Application

Running the above main class will produce raw.log, MDC.log, module.1.log, module.2.log with their respective log statements.

Now if the log file such as raw.log or module.1.log already exists then new log file for the same will be created as raw.1.log or module.1.1.log with their respective log statements.

Thanks for reading.

Leave a Reply

Your email address will not be published. Required fields are marked *