LoggingFacade.java

/*
 * Copyright 2013 University of Glasgow.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package broadwick;

import ch.qos.logback.classic.Level;
import ch.qos.logback.classic.Logger;
import ch.qos.logback.classic.LoggerContext;
import ch.qos.logback.classic.encoder.PatternLayoutEncoder;
import ch.qos.logback.classic.filter.ThresholdFilter;
import ch.qos.logback.classic.spi.ILoggingEvent;
import ch.qos.logback.core.Appender;
import ch.qos.logback.core.ConsoleAppender;
import ch.qos.logback.core.FileAppender;
import ch.qos.logback.core.encoder.Encoder;
import ch.qos.logback.core.filter.Filter;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import org.slf4j.LoggerFactory;

/**
 * Facade class to the underlying logger to allow the loggers to be configured programmatically.
 */
@Slf4j
public final class LoggingFacade {

    /**
     * Create the logging facade, by default adding a console logger to output logging info to the screen (console). A
     * level of 'error' is configured by default, once the configuration file is read the logging level can be changed.
     */
    public LoggingFacade() {

        try {
            // assume SLF4J is bound to logback in the current environment
            loggerContext = (LoggerContext) LoggerFactory.getILoggerFactory();
            loggerContext.start();

            rootLogger = loggerContext.getLogger(Logger.ROOT_LOGGER_NAME);
            rootLogger.detachAndStopAllAppenders();
            rootLogger.setLevel(Level.OFF);

            // Create the layout pattern for the appenders.
            addConsoleLogger("error", logFormatThreadMsg);

        } catch (ClassCastException cce) {
            rootLogger.error("Apparently SLF4J is not backed by Logback. This is a requirement, thus an internal fault.");
        }
    }

    /**
     * Add a console logger to the list of loggers of the project. This will replace all the console loggers already
     * added using this method.
     * @param level   the logging level to be applied.
     * @param pattern the pattern for the logger.
     */
    public void addConsoleLogger(final String level, final String pattern) {

        final String consoleName = "Console";

        // remove the old console appender - it will be called "Console" if it was added using this method.
        rootLogger.detachAppender(consoleName);

        final ConsoleAppender<ILoggingEvent> appender = new ConsoleAppender<>();
        appender.setName(consoleName);
        appender.setEncoder(createPatternLayoutEncoder(pattern));
        appender.addFilter(createThresholdFilter(level));

        appender.setContext(loggerContext);
        appender.start();
        rootLogger.addAppender((Appender<ILoggingEvent>) appender);
    }

    /**
     * Add a file logger to the list of loggers of the project.
     * @param file      the name of the file to which the logging messages will be added.
     * @param level     the logging level to be applied.
     * @param pattern   the pattern for the logger.
     * @param overwrite if true, the contents of any previous log are overwritten. If false, the logs are appended.
     */
    public void addFileLogger(final String file, final String level, final String pattern, final Boolean overwrite) {

        // Delete the old log file.
        try {
            if (overwrite != null && overwrite.booleanValue()) {
                Files.delete(Paths.get(file));
            }
        } catch (IOException ex) {
            log.error("Could not delete old log file; {}", ex.getLocalizedMessage());
        }

        final FileAppender<ILoggingEvent> appender = new FileAppender<>();
        appender.setFile(file);
        appender.setEncoder(createPatternLayoutEncoder(pattern));
        appender.addFilter(createThresholdFilter(level));

        appender.setContext(loggerContext);
        appender.start();
        rootLogger.addAppender((Appender<ILoggingEvent>) appender);
    }

    /**
     * Create a pattern layout encoder for a given pattern.
     * @param pattern the pattern to apply.
     * @return the created encoder.
     */
    private Encoder<ILoggingEvent> createPatternLayoutEncoder(final String pattern) {
        // Set a default pattern if the supplied pattern is empty
        final String patt = (pattern == null) ? logFormatThreadMsg : pattern;

        final PatternLayoutEncoder encoder = new PatternLayoutEncoder();
        encoder.setContext(loggerContext);
        encoder.setPattern(patt);
        encoder.start();
        return (Encoder<ILoggingEvent>) encoder;
    }

    /**
     * Create a thresholdFilter for a given level.
     * @param level the level to set.
     * @return the created filter.
     */
    private Filter<ILoggingEvent> createThresholdFilter(final String level) {
        // Set a default level if the supplied level is empty
        final String lev = (level == null) ? "info" : level;
        setLoggingLevel(level);

        final ThresholdFilter filter = new ThresholdFilter();
        filter.setLevel(lev);
        filter.start();
        return (Filter<ILoggingEvent>) filter;
    }

    /**
     * Convert the string passed as argument to a Level. If the conversion fails, then this method returns Level.DEBUG.
     * @param level the logging level that should be used.
     */
    private void setLoggingLevel(final String level) {
        rootLogger.setLevel(Level.toLevel(level));
    }
    @Getter
    private Logger rootLogger;
    private LoggerContext loggerContext;
    private String logFormatThreadMsg = "[%thread] %msg\n";
}