Search code examples
javaspring-bootlogback

Logback - Layout & Pattern in logback.xml


I am using logback with slf4j for logging in Spring Boot application. I have created a custom layout class because all log statements are to be wrapped as a json. I have configured the logback-spring.xml as show below to take the custom layout. It works!

Issue is I am unable to apply the pattern. Only either the layout works (or) the pattern. What I want is always on log, go to layout class and then apply the pattern before logging.

<appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
    <file>${user.home}/logs/sample.log</file>
    <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
        <fileNamePattern>${user.home}/logs/sample_%d{yyyy-MM-dd}.%i.log</fileNamePattern>

        <timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
            <maxFileSize>10MB</maxFileSize>
        </timeBasedFileNamingAndTriggeringPolicy>
        <!-- how many days to keep the files -->
        <maxHistory>30</maxHistory>
    </rollingPolicy>

    <encoder class="ch.qos.logback.core.encoder.LayoutWrappingEncoder">
        <layout class="com.test.test.payment.core.logging.SampleLogLayout" >
        </layout>
    </encoder>
    *<!-- <encoder>
        <charset>UTF-8</charset>
        <Pattern>{"@timestamp": "%d{yyyy-MM-dd HH:mm:ss.SSS}", "priority": "%p", "application": "payment",
            "class": "%C", "file": "%F:%L", "payload": %m }%n
        </Pattern>
    </encoder>-->*
</appender>

Here's the SampleLogLayout class:

public class SampleLogLayout extends LayoutBase<LoggingEvent> {

    @Override
    public String doLayout(LoggingEvent event) {

        String renderedMessage = event.getMessage();

        if (!isJson(renderedMessage)) {
            Throwable throwable = null;

            if (event.getLevel().equals(Level.ERROR) || event.getLevel().equals(Level.WARN)) {

            final IThrowableProxy throwableProxy = event.getThrowableProxy();
            if ((throwableProxy != null) && (throwableProxy instanceof ThrowableProxy)) {
                ThrowableProxy proxy = (ThrowableProxy) throwableProxy;
                throwable = proxy.getThrowable();
            }
            String message = LogErrorMessage.create(CommonCoreErrors.GENERIC_ERROR)
                    .message(renderedMessage).exception(throwable).build();

            return message;
        } else {
                return LogMessage.create(renderedMessage).build();
        }

        }
        return renderedMessage;
    }

    private boolean isJson(String msg) {
        if (null == msg) {
            return false;
        } else {
            return msg.startsWith("{") && msg.endsWith("}");
        }
    }
}

Solution

  • I started to reproduce this in order to provide a complete solution but the absence of LogMessage and LogErrorMessage made that a bit tricky.

    However, it looks to me like you just want to log in JSON format, not just the message but also the metadata such as timestamp, priority etc.

    This can be achieved by using the JsonLayout. Here's an example of the layout configuration:

    <layout class="ch.qos.logback.contrib.json.classic.JsonLayout">
        <jsonFormatter class="ch.qos.logback.contrib.jackson.JacksonJsonFormatter">
            <prettyPrint>false</prettyPrint>
        </jsonFormatter>
        <timestampFormat>yyyy-MM-dd' 'HH:mm:ss.SSS</timestampFormat>
        <appendLineSeparator>true</appendLineSeparator>
        <includeContextName>false</includeContextName>
    </layout>
    

    With that configuration the following log invocation ...

    logger.info("{\"a\": 1, \"b\": 2}");
    

    ... will emit:

    {"timestamp":"2017-10-05 10:51:34.610","level":"INFO","thread":"main","logger":"com.stackoverflow.logback.LogbackTest","message":"{\"a\": 1, \"b\": 2}"} 
    

    You can include MDC too, for example ...

    MDC.put("application", "payment");
    logger.info("{\"a\": 1, \"b\": 2}");
    

    ... will emit:

    {"timestamp":"2017-10-05 10:52:56.088","level":"INFO","thread":"main","mdc":{"application":"payment"},"logger":"com.stackoverflow.logback.LogbackTest","message":"{\"a\": 1, \"b\": 2}"}
    

    That's quite close to your desired output and JsonLayout is extensible so you could ...

    • Override toJsonMap() to change names of the keys e.g. replace timestamp with @timestamp, replace message with payload
    • Implement addCustomDataToJsonMap() to add other key:value pairs to the log event

    So, I think you could achieve your desired output using the pre-existing JsonLayout rather than writing your own.

    More details on Logback JSON extensions here.

    The Maven coordinates are:

    <dependency>
        <groupId>ch.qos.logback.contrib</groupId>
        <artifactId>logback-json-classic</artifactId>
        <version>0.1.5</version>
    </dependency>
    <dependency>
        <groupId>ch.qos.logback.contrib</groupId>
        <artifactId>logback-json-core</artifactId>
        <version>0.1.5</version>
    </dependency>
    <dependency>
        <groupId>ch.qos.logback.contrib</groupId>
        <artifactId>logback-jackson</artifactId>
        <version>0.1.5</version>
    </dependency>