All Downloads are FREE. Search and download functionalities are using the official Maven repository.

io.bootique.logback.LogbackContextFactory Maven / Gradle / Ivy

There is a newer version: 3.0.M2
Show newest version
package io.bootique.logback;

import ch.qos.logback.classic.Level;
import ch.qos.logback.classic.Logger;
import ch.qos.logback.classic.LoggerContext;
import ch.qos.logback.classic.jul.LevelChangePropagator;
import ch.qos.logback.core.status.OnConsoleStatusListener;
import ch.qos.logback.core.util.StatusListenerConfigHelper;
import io.bootique.annotation.BQConfig;
import io.bootique.annotation.BQConfigProperty;
import io.bootique.logback.appender.AppenderFactory;
import io.bootique.logback.appender.ConsoleAppenderFactory;
import io.bootique.shutdown.ShutdownManager;
import org.slf4j.ILoggerFactory;
import org.slf4j.bridge.SLF4JBridgeHandler;

import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;

@BQConfig
public class LogbackContextFactory {

    private LogbackLevel level;
    private Map loggers;
    private Collection appenders;
    private boolean useLogbackConfig;
    private boolean debugLogback;

    public LogbackContextFactory() {
        this.level = LogbackLevel.info;
        this.loggers = Collections.emptyMap();
        this.appenders = Collections.emptyList();

        // TODO: to write unit tests for this flag we are waiting for
        // https://github.com/bootique/bootique/issues/52 to be implemented.
        this.useLogbackConfig = false;
    }

    public Logger createRootLogger(ShutdownManager shutdownManager, Map defaultLevels) {

        LoggerContext context = createLogbackContext();
        shutdownManager.addShutdownHook(() -> {
            context.stop();
        });

        rerouteJUL();

        Logger root = context.getLogger(org.slf4j.Logger.ROOT_LOGGER_NAME);

        if (!useLogbackConfig) {

            Map loggers = mergeLevels(defaultLevels);

            configLogbackContext(context, root, loggers);
        }

        return root;
    }

    protected void configLogbackContext(LoggerContext context, Logger root, Map loggers) {
        context.reset();

        if(debugLogback) {
            StatusListenerConfigHelper.addOnConsoleListenerInstance(context, new OnConsoleStatusListener());
        }

        LevelChangePropagator propagator = new LevelChangePropagator();
        propagator.setContext(context);
        propagator.setResetJUL(true);

        context.addListener(propagator);

        root.setLevel(Level.toLevel(level.name(), Level.INFO));

        loggers.forEach((name, lf) -> lf.configLogger(name, context));

        if (appenders.isEmpty()) {
            setAppenders(Collections.singletonList(new ConsoleAppenderFactory()));
        }

        appenders.forEach(a -> root.addAppender(a.createAppender(context)));
    }

    /**
     * Merges a map of logging levels with this factory loggers configuration, returning a new map with combined
     * configuration. Factory logger levels take precedence over the provided levels argument (i.e. configuration
     * overrides code settings).
     *
     * @param levels a map of levels keyed by logger name.
     * @return a new map that is combination of factory loggers config and provided set of levels.
     */
    protected Map mergeLevels(Map levels) {

        if (levels.isEmpty()) {
            return this.loggers;
        }

        Map merged = new HashMap<>(loggers);

        levels.forEach((name, level) -> {

            LoggerFactory factory = loggers.get(name);
            if (factory == null) {
                factory = new LoggerFactory();
                factory.setLevel(mapJULLevel(level));


                merged.put(name, factory);
            }
        });

        return merged;
    }

    protected LogbackLevel mapJULLevel(java.util.logging.Level level) {
        return JulLevel.valueOf(level.getName()).getLevel();
    }

    // inspired by Dropwizard. See DW DefaultLoggingFactory and
    // http://jira.qos.ch/browse/SLF4J-167. Though presumably Bootique calls
    // this from the main thread, so we should not be affected by the issue.
    protected LoggerContext createLogbackContext() {
        long startTime = System.nanoTime();
        while (true) {
            ILoggerFactory iLoggerFactory = org.slf4j.LoggerFactory.getILoggerFactory();

            if (iLoggerFactory instanceof LoggerContext) {
                return (LoggerContext) iLoggerFactory;
            }

            if ((System.nanoTime() - startTime) > 10_000_000) {
                throw new IllegalStateException("Unable to acquire the logger context");
            }

            try {
                Thread.sleep(500);
            } catch (InterruptedException e) {
                throw new IllegalStateException(e);
            }
        }
    }

    void rerouteJUL() {
        SLF4JBridgeHandler.removeHandlersForRootLogger();
        SLF4JBridgeHandler.install();
    }

    /**
     * @return default log level.
     * @since 0.13
     */
    public LogbackLevel getLevel() {
        return level;
    }

    @BQConfigProperty("Root log level. Can be overridden by individual loggers. The default is 'info'.")
    public void setLevel(LogbackLevel level) {
        this.level = level;
    }

    /**
     * @return collection of log level configurations.
     * @since 0.12
     */
    public Map getLoggers() {
        return loggers;
    }

    @BQConfigProperty("Per-package or per-class loggers. Keys in the map are logger names.")
    public void setLoggers(Map loggers) {
        this.loggers = loggers;
    }

    /**
     * @return collection of appender configurations.
     * @since 0.12
     */
    public Collection getAppenders() {
        return appenders;
    }

    @BQConfigProperty("One or more appenders that will render the logs to console, file, etc. If the list is empty, " +
            "console appender is used with default settings.")
    public void setAppenders(Collection appenders) {
        this.appenders = appenders;
    }

    /**
     * If true, all other logback configuration present in YAML is ignored and
     * the user is expected to provide its own config file per
     * Logback
     * documentation.
     *
     * @param useLogbackConfig if true, all other logback configuration present in YAML is
     *                         ignored.
     * @since 0.9
     */
    @BQConfigProperty("If true, all Bootique logback settings are ignored and the user is expected to provide its own " +
            "config file per Logback documentation. This is only needed for a few advanced options not directly " +
            "available via Bootique config. So the value should stay false (which is the default).")
    public void setUseLogbackConfig(boolean useLogbackConfig) {
        this.useLogbackConfig = useLogbackConfig;
    }

    /**
     * Sets whether to debug Logback startup and configuration loading.
     *
     * @param debugLogback if true, turns on tracing of Logback startup.
     * @since 0.13
     */
    @BQConfigProperty("If true, Logback configuration debugging information will be printed to console. Helps to deal" +
            " with Logback configuration issues.")
    public void setDebugLogback(boolean debugLogback) {
        this.debugLogback = debugLogback;
    }

    private enum JulLevel {

        ALL(LogbackLevel.all),
        CONFIG(LogbackLevel.debug),
        FINE(LogbackLevel.debug),
        FINER(LogbackLevel.debug),
        FINEST(LogbackLevel.trace),
        INFO(LogbackLevel.info),
        OFF(LogbackLevel.off),
        SEVERE(LogbackLevel.error),
        WARNING(LogbackLevel.warn);

        private LogbackLevel level;

        JulLevel(LogbackLevel level) {
            this.level = level;
        }

        public LogbackLevel getLevel() {
            return level;
        }
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy