Search code examples
javaloggingslf4jlogbackerror-logging

Dump debug logs if exception occured


I'm writing a java web application. I'm using slf4j & logback for logging. I use debug level to output some information which might be useful to find a bug but generally it's not useful and quite verbose.

I have a place in the application to catch all exceptions and log them. What I would like to do is to capture ALL previous logging output in this thread from the request start and save it all in the separate incident file. So I would have clean logs for system performing without errors and verbose incident log if exception occured.

Is there some ready solution for this problem? May be there's slightly different approach?

Right now I'm saving all debug output into file stored for 7 days but grepping this file is not the easiest task to do when it's required.


Solution

  • You can create a custom appender to store all the log messages, and then write to another appender when needed. Call clearLog at the beginning of the request, and writeLog if there's an error.

    import java.util.ArrayList;
    import java.util.List;
    
    import ch.qos.logback.core.Appender;
    import ch.qos.logback.core.AppenderBase;
    
    public class SavingAppender<E> extends AppenderBase<E> {
        private final ThreadLocal<List<E>> events = new ThreadLocal<List<E>>() {
            protected List<E> initialValue() {
                return new ArrayList<>();
            }
        };
    
        protected void append(E event) {
            events.get().add(event);
        }
    
        public void clearLog() {
            events.get().clear();
        }
    
        public void writeLog(Appender<E> other) {
            for(E event:events.get()) {
                other.doAppend(event);
            }
        }
    }
    

    Configure all the log messages to go to the appender, and create another appender for the incident logs:

    <configuration> 
        <appender name="savingAppender" class="SavingAppender"> 
            <encoder>
                <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} %-5level %logger{36} - %msg%n</pattern>
            </encoder>
        </appender>
    
        <appender name="outputError" class="ch.qos.logback.core.ConsoleAppender">
            <filter class="ch.qos.logback.classic.filter.ThresholdFilter"> 
                <level>error</level>
            </filter>
            <encoder>
                <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} %-5level %logger{36} - %msg%n</pattern>
            </encoder>
        </appender>
    
        <appender name="incidentAppender" class="ch.qos.logback.core.FileAppender">
            <file>output.log</file>
            <encoder>
                <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} %-5level %logger{36} - %msg%n</pattern>
            </encoder>
        </appender>
    
        <root level="info">
            <appender-ref ref="savingAppender"/>
            <appender-ref ref="outputError"/>
        </root>
    
        <logger name="incident">
            <appender-ref ref="incidentAppender"/>
        </logger>
    </configuration>
    

    Test class:

    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    
    import ch.qos.logback.classic.spi.ILoggingEvent;
    import ch.qos.logback.core.Appender;
    
    public class Main {
        public static void main(String[] args) {
            Logger rootLogger = LoggerFactory.getLogger(Logger.ROOT_LOGGER_NAME);
            Appender<ILoggingEvent> savingAppender = ((ch.qos.logback.classic.Logger)rootLogger).getAppender("savingAppender");
    
            Logger incidentLogger = LoggerFactory.getLogger("incident");
            Appender<ILoggingEvent> incidentAppender = ((ch.qos.logback.classic.Logger)incidentLogger).getAppender("incidentAppender");
    
            ((SavingAppender<ILoggingEvent>)savingAppender).clearLog();
    
            LoggerFactory.getLogger(Main.class).error("Error 1...");
            LoggerFactory.getLogger(Main.class).error("Error 2...");
            LoggerFactory.getLogger(Main.class).error("Error 3...");
            LoggerFactory.getLogger(Main.class).info("Info 1...");
            LoggerFactory.getLogger(Main.class).info("Info 2...");
            LoggerFactory.getLogger(Main.class).info("Info 3...");
    
            ((SavingAppender<ILoggingEvent>)savingAppender).writeLog(incidentAppender);
        }
    }