io.bootique.logback.LogbackContextFactory Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of bootique-logback Show documentation
Show all versions of bootique-logback Show documentation
Provides Logback integration with Bootique
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