1

There is a Spring application where I need to route different servlet requests into two different log files. I created a filter in Spring's filter chain and tried to setup a working log4j2 configuration.

Filter:

@Component
public class LogFilter extends OncePerRequestFilter {

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
            throws ServletException, IOException {

        if (request.getServletPath().contains("healthcheck")) {
            try (CloseableThreadContext.Instance context = CloseableThreadContext.put("health", "true")) {
                chain.doFilter(request, response);
            }
        } else {
            try (CloseableThreadContext.Instance context = CloseableThreadContext.put("health", "false")) {
                chain.doFilter(request, response);
            }
        }
    }
}

Log4j2 configuration:

<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="WARN" monitorInterval="30">
    <Appenders>
        <Routing name="Routing">
            <Routes pattern="$${health}==true">
                <Route>
                    <RollingRandomAccessFile name="FILE"
                                             fileName="logs/reference-service.log"
                                             filePattern="logs/reference-service.log.%d{yyyy-MM-dd-hh-mm}.gz">
                        <PatternLayout pattern="[%-5level] %d{yyyy-MM-dd HH:mm:ss.SSS} [%t] %c{1} - %msg%n"/>
                        <Policies>
                            <SizeBasedTriggeringPolicy size="1024 KB"/>
                        </Policies>
                        <DefaultRolloverStrategy max="30"/>
                    </RollingRandomAccessFile>
                </Route>
            </Routes>
            <Routes pattern="$${health}==false">
                <Route>
                    <RollingRandomAccessFile name="FILE_HEALTH"
                                             fileName="logs/reference-service-health.log"
                                             filePattern="logs/reference-service-health.log.%d{yyyy-MM-dd-hh-mm}.gz">
                        <PatternLayout pattern="[%-5level] %d{yyyy-MM-dd HH:mm:ss.SSS} [%t] %c{1} - %msg%n"/>
                        <Policies>
                            <SizeBasedTriggeringPolicy size="1024 KB"/>
                        </Policies>
                        <DefaultRolloverStrategy max="30"/>
                    </RollingRandomAccessFile>
                </Route>
            </Routes>
        </Routing>
    </Appenders>
    <Loggers>
        <Logger name="org.hibernate" level="info" additivity="false">
            <AppenderRef ref="FILE"/>
            <AppenderRef ref="FILE_HEALTH"/>
        </Logger>
        <Logger name="org.springframework" level="info" additivity="false">
            <AppenderRef ref="FILE"/>
            <AppenderRef ref="FILE_HEALTH"/>
        </Logger>
        <Root level="debug">
            <AppenderRef ref="FILE"/>
            <AppenderRef ref="FILE_HEALTH"/>
        </Root>
    </Loggers>
</Configuration>

With the configuration presented above logging is not working at all. The task is easy to implement using Logback, but unfortunately I must stay with Log4j2.

==================================== UPDATE

This is how it's implemented with logback.

@Component
public class LogFilter extends OncePerRequestFilter {

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
            throws ServletException, IOException {

        try {
            if (request.getServletPath().contains("healthcheck")) {
                MDC.put("health", "true");
            }
            chain.doFilter(request, response);
        } finally {
            MDC.remove("health");
        }
    }
}

public class MdcFilterHealth extends Filter<ILoggingEvent> {

    @Override
    public FilterReply decide(ILoggingEvent event) {
        String marked = event.getMDCPropertyMap().get("health");
        if (!"true".equals(marked)) {
            return FilterReply.DENY;
        } else {
            return FilterReply.NEUTRAL;
        }
    }
}

public class MdcFilterNonHealth extends Filter<ILoggingEvent> {

    @Override
    public FilterReply decide(ILoggingEvent event) {
        String marked = event.getMDCPropertyMap().get("health");
        if ("true".equals(marked)) {
            return FilterReply.DENY;
        } else {
            return FilterReply.NEUTRAL;
        }
    }
}

<configuration>
    <appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <filter class="myservice.config.MdcFilterNonHealth"/>
        <file>logs/reference-service.log</file>
        <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
            <Pattern>%d{dd-MM-yyyy HH:mm:ss.SSS} | [%thread] | %-5level | %-30.30M | %msg%n</Pattern>
        </encoder>
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <fileNamePattern>logs/archived/reference-service_%d{dd-MM-yyyy}.log</fileNamePattern>
            <maxHistory>10</maxHistory>
            <totalSizeCap>1024KB</totalSizeCap>
        </rollingPolicy>
    </appender>
    <appender name="FILE_HEALTH" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <filter class="myservice.config.MdcFilterHealth"/>
        <file>logs/reference-service-health.log</file>
        <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
            <Pattern>%d{dd-MM-yyyy HH:mm:ss.SSS} | [%thread] | %-5level | %-30.30M | %msg%n</Pattern>
        </encoder>
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <fileNamePattern>logs/archived/reference-service-health_%d{dd-MM-yyyy}.log</fileNamePattern>
            <maxHistory>10</maxHistory>
            <totalSizeCap>1024KB</totalSizeCap>
        </rollingPolicy>
    </appender>
    <Logger name="org.hibernate" level="info" additivity="false">
        <appender-ref ref="FILE"/>
        <appender-ref ref="FILE_HEALTH"/>
    </Logger>
    <Logger name="org.springframework" level="info" additivity="false">
        <appender-ref ref="FILE"/>
        <appender-ref ref="FILE_HEALTH"/>
    </Logger>
    <root level="debug">
        <appender-ref ref="FILE"/>
        <appender-ref ref="FILE_HEALTH"/>
    </root>
</configuration>
sva605
  • 1,571
  • 3
  • 20
  • 34
  • 1
    Why don't you use named Loggers in your Log4j2 configuration and get them in your code to use them directly ? – Anthony BONNIER Aug 21 '18 at 09:22
  • I don't understand you – sva605 Aug 21 '18 at 09:26
  • Sorry ! If you define two appenders, to create your two files, then reference them in two loggers and setting specific names to them, you can call them directly by their names in your code, using `getLogger("loggerName");`. Have look to this documentation : https://logging.apache.org/log4j/2.0/manual/api.html – Anthony BONNIER Aug 21 '18 at 10:31
  • This is absolutely not what I need – sva605 Aug 21 '18 at 11:23
  • Don't you need a solution to log in separate files according to a functional condition (let's say 'health status' of something) ? Because this is what I understand in your question. In your current solution, I understand that you cannot select the correct logger using logging context like you did with logback. Otherwise, you need to explain your problem with more details then, because we only know in your question that "it is not working at all", as you wrote, but without explaining how it did not work. – Anthony BONNIER Aug 21 '18 at 12:43
  • I don't need to fill my code with loggers' conditional mess. – sva605 Aug 21 '18 at 12:44
  • > "but without explaining how it did not work" Not working at all means the logs are totally empty – sva605 Aug 21 '18 at 12:46
  • OK, I see what you mean. Have you try other solutions found in SO ? https://stackoverflow.com/questions/25114526/log4j2-how-to-write-logs-to-separate-files-for-each-user Your configuration file seems a bit different, using the context. Is the logging working without multiple routes or even with simple logging ? – Anthony BONNIER Aug 21 '18 at 12:58
  • Already found a solution in Log4j FAQ, will publish it soon – sva605 Aug 21 '18 at 13:12
  • Nice, happy to know you found a solution :) – Anthony BONNIER Aug 21 '18 at 13:22

1 Answers1

0

So, the found solution is:

@Component
public class LogFilter extends OncePerRequestFilter {

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
            throws ServletException, IOException {

        if (request.getServletPath().contains("healthcheck")) {
            try (CloseableThreadContext.Instance context = CloseableThreadContext.put("health", "true")) {
                chain.doFilter(request, response);
            }
        } else {
            chain.doFilter(request, response);
        }
    }
}

<Configuration status="WARN" monitorInterval="30">
    <Appenders>
        <Console name="CONSOLE" target="SYSTEM_OUT" follow="true">
            <PatternLayout
                    pattern="%d{${LOG_DATEFORMAT_PATTERN:-yyyy-MM-dd HH:mm:ss.SSS}} %highlight{${LOG_LEVEL_PATTERN:-%5p}}{FATAL=red blink, ERROR=red, WARN=yellow, INFO=green, DEBUG=blue, TRACE=white} %style{${sys:PID}}{magenta} [%15.15t] %style{%-40.40C{1.}}{cyan} : %m%n${LOG_EXCEPTION_CONVERSION_WORD:-%wEx}"/>
        </Console>
        <Routing name="ROUTING">
            <Routes pattern="$${ctx:health}">
                <Route key="true">
                    <RollingRandomAccessFile name="FILE_HEALTH"
                                             fileName="logs/reference-service-health.log"
                                             filePattern="logs/reference-service-health.log.%d{yyyy-MM-dd-hh-mm}.gz">
                        <PatternLayout pattern="[%-5level] %d{yyyy-MM-dd HH:mm:ss.SSS} [%t] %c{1} - %msg%n"/>
                        <Policies>
                            <SizeBasedTriggeringPolicy size="1024 KB"/>
                        </Policies>
                        <DefaultRolloverStrategy max="30"/>
                    </RollingRandomAccessFile>
                </Route>
                <Route>
                    <RollingRandomAccessFile name="FILE"
                                             fileName="logs/reference-service.log"
                                             filePattern="logs/reference-service.log.%d{yyyy-MM-dd-hh-mm}.gz">
                        <PatternLayout pattern="[%-5level] %d{yyyy-MM-dd HH:mm:ss.SSS} [%t] %c{1} - %msg%n"/>
                        <Policies>
                            <SizeBasedTriggeringPolicy size="1024 KB"/>
                        </Policies>
                        <DefaultRolloverStrategy max="30"/>
                    </RollingRandomAccessFile>
                </Route>
            </Routes>
        </Routing>
    </Appenders>
    <Loggers>
        <Logger name="org.hibernate" level="info" additivity="false">
            <AppenderRef ref="CONSOLE"/>
            <AppenderRef ref="ROUTING"/>
        </Logger>
        <Logger name="org.springframework" level="info" additivity="false">
            <AppenderRef ref="CONSOLE"/>
            <AppenderRef ref="ROUTING"/>
        </Logger>
        <Root level="debug" additivity="false">
            <AppenderRef ref="CONSOLE"/>
            <AppenderRef ref="ROUTING"/>
        </Root>
    </Loggers>
</Configuration>
sva605
  • 1,571
  • 3
  • 20
  • 34