Spring Module-Wise Logs Using MDC In Logback

SiftingAppender And MDC

In this example you will see how to create or generate separate log file module-wise or for each separate functionality using SiftingAppender in logback with the help of ThreadedContext or MDC in Spring Boot applications. MDC also known as 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 you might have seen how to configure logback for Spring Boot Applications but here I will control multiple log files using SiftingtingAppender and similar kind of example we had seen using log4j API and log4j2/slf4j API.

A SiftingAppender can be used to separate (or sift) logging according to a given runtime attribute. For example, SiftingAppender can separate logging events according to user sessions, so that the logs generated by different users go into distinct log files, one log file per user.

Prerequisites

Spring Boot 2.2.2/3.2.2, Java 12/19 (Logging using logback in Spring Boot application)

logback.xml

Logback is intended as a successor to the popular log4j project, picking up where log4j leaves off.

The logback-core module lays the groundwork for the other two modules. The logback-classic module can be assimilated to a significantly improved version of log4j. Moreover, logback-classic natively implements the SLF4J API so that you can readily switch back and forth between logback and other logging frameworks such as log4j or java.util.logging (JUL).

Now I will create logback.xml file under the classpath directory src/main/resources. Having such name, the file is automatically picked up by the application on start up.

<?xml version="1.0" encoding="UTF-8"?>
<configuration scan="true">
    <property name="PATTERN" value="%d{yyyy.MM.dd HH:mm:ss.SSS} [%p] %c %m%n"/>
    <property name="LOG_LOC" value="logs"/>
    <property name="MAX" value="5"/>
    <property name="DEF_MODULE" value="main"/>

    <!-- ############################## Appender configurations ############################## -->
    <appender name="FILE" class="ch.qos.logback.classic.sift.SiftingAppender">
        <!-- This is MDC value, assigned via Java code -->
        <discriminator>
            <key>module</key>
            <defaultValue>${DEF_MODULE}</defaultValue>
        </discriminator>
        <sift>
            <appender name="FILE-${module}" class="ch.qos.logback.core.rolling.RollingFileAppender">
                <file>${LOG_LOC}/${module}.log</file>
                <rollingPolicy class="ch.qos.logback.core.rolling.FixedWindowRollingPolicy">
                    <fileNamePattern>${LOG_LOC}/${module}.%i.log</fileNamePattern>
                    <minIndex>1</minIndex>
                    <maxIndex>${MAX}</maxIndex>
                </rollingPolicy>
                <triggeringPolicy class="com.roytuts.spring.boot.logback.siftingappender.config.StartupTrigger"/>
                <encoder>
                    <pattern>${PATTERN}</pattern>
                </encoder>
            </appender>
        </sift>
    </appender>

    <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
        <encoder>
            <pattern>${PATTERN}</pattern>
        </encoder>
    </appender>
	
    <!-- ############################## Logger configurations ############################## -->
    <logger name="com.roytuts" level="debug"/>

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

    <logger name="raw" level="debug" additivity="false">
        <appender-ref ref="FILE"/>
    </logger>

    <root level="info">
        <appender-ref ref="FILE"/>
        <appender-ref ref="STDOUT"/>
    </root>
</configuration>

I have set some properties with key/value pairs in the above XML file.

SiftingAppender is responsible for managing the lifecycle of child appenders. For example, SiftingAppender will automatically close and remove any stale appender. A nested appender is considered stale when no accesses it beyond the duration specified by the timeout parameter.

When handling a logging event, SiftingAppender will select a child appender to delegate to. The selection criteria are computed at runtime by a discriminator. The user can specify the selection criteria with the help of a Discriminator.

When rolling over, FixedWindowRollingPolicy renames files according to a fixed window algorithm.

The fileNamePattern option represents the file name pattern for the archived (rolled over) log files. This option is required and must include an integer token %i somewhere within the pattern.

triggeringPolicy is overridden by custom class to avoid size based trigger.

TriggeringPolicy Class

Create triggering policy class to avoid size based trigger:

package com.roytuts.spring.boot.logback.siftingappender.config;

public class StartupTrigger<E> extends TriggeringPolicyBase<E> {

	private AtomicBoolean doRolling = new AtomicBoolean(true);

	@Override
	public boolean isTriggeringEvent(File activeFile, E event) {
		return doRolling.getAndSet(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 how I have written some log statements but did not put into any module and these log statements will be written to the main.log file.

package com.roytuts.spring.boot.logback.siftingappender;

@SpringBootApplication
public class SpringBootLogbackSiftingAppenderApp implements CommandLineRunner {

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

	public static void main(String[] args) {
		SpringApplication.run(SpringBootLogbackSiftingAppenderApp.class, args);
	}

	@Override
	public void run(String... args) throws Exception {
		printLog();
	}

	private static void printLog() {
		LOG.debug("Debug Message");
		LOG.warn("Warn Message");
		LOG.error("Error Message");
		LOG.info("Info Message");
		LOG.trace("Trace Message");
		
		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");		

		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");
	}

}

Source Code

Download

Leave a Reply

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