企业级的logging 通常有自己特殊的需求,因此就需要覆盖default spring boot 的logging 配置。 通常有三方面的需求
- 向后兼容性。 由于历史原因,企业用的log 配置文件通常不会是springboot 支持的名字, 那么在migrate 到spring boot 后,需要向后兼容。也要求springboot 能支持同样名字的log file。
- 动态变化logging level。 在启动阶段,期望能看到更低level的 log 比如info, 在启动结束后,为了避免太多的log, 需要动态改变log 的level到更高的level 比如warning。
- 在不同环境, 比如在本地开发和线上运行,需要的appender 是不一样的。 因此也需要根据情况来加载不同配置的log 文件
根据前文的分析,如果LoggingApplicationListener
在initialize的时候,如果环境变量已经设置。则就用设置过的log文件。 那么要覆盖LoggingApplicationListener 的逻辑,必须在它之前运行并且设置环境变量。
LoggingApplicationListener
的顺序定义为
public static final int DEFAULT_ORDER = Ordered.HIGHEST_PRECEDENCE + 20;
那么我们可以设计一个listener 顺序为Ordered.HIGHEST_PRECEDENCE + 19
public class LogbackInitializer implements ApplicationListener<ApplicationEnvironmentPreparedEvent>, Ordered {
@Override
public int getOrder() {
return Ordered.HIGHEST_PRECEDENCE + 19;
}
}
然后ApplicationEnvironmentPreparedEvent 时候来根据环境加载log文件同时设置环境变量
@Override
public void onApplicationEvent(ApplicationEnvironmentPreparedEvent event) {
init(event.getEnvironment());
}
private void init(Environment environment) {
/*
* Check to see if Spring Boot's "logging.config" property has already been set.
* If it is has, we do not configure Raptor's logger.
*/
String springLogbackConfigLocation = environment.getProperty(SPRING_LOGGING_CONFIG_PROPERTY);
/*
* If the "logging.config" property was not set, check the Logger component configuration.
* Fall-back to the default logback configuration if no external configuration was specified.
*/
if (springLogbackConfigLocation != null) {
initLogback(springLogbackConfigLocation, environment);
} else {
// Reset Logback's status manager to re-initialize.
resetLoggerContext();
String logbackConfigLocation = environment.getProperty(_LOGGER_CONFIG_PROPERTY);
initLogback(logbackConfigLocation != null ? logbackConfigLocation : getDefaultLogbackConfigLocation(environment), environment);
}
void initLogback(String logbackConfigLocation, Environment environment) {
//protection log setting it should not be set on logback-default.xml other than dev by default
if (DEFAULT_DEV_LOGBACK_CONFIG.equals(logbackConfigLocation) && !isDev(environment)) {
System.out.println(
"logback-default.xml configuration found in non-dev environment, override to default logback-raptor.xml!");
logbackConfigLocation = getDefaultLogbackConfigLocation(environment);
}
System.out.println("Initializing Logback with configuration: " + logbackConfigLocation);
// Always set both configuration properties to keep everything in sync.
System.setProperty(_LOGGER_CONFIG_PROPERTY, logbackConfigLocation);
System.setProperty(SPRING_LOGGING_CONFIG_PROPERTY, logbackConfigLocation);
initLogHome(environment);
}
}
在LoggingApplicationListener加载之后 需要动态改变log level
需要再写一个listener
public class StartupLogCapturer implements ApplicationListener<SpringApplicationEvent>, Ordered {
@Override
public int getOrder() {
return Ordered.HIGHEST_PRECEDENCE + 21;
}
}
在不同阶段来改变log level
@Override
public void onApplicationEvent(SpringApplicationEvent springEvent) {
if (springEvent instanceof ApplicationEnvironmentPreparedEvent) {
lowerRootLoggerLevel((ApplicationEnvironmentPreparedEvent) springEvent);
} else if (springEvent instanceof ApplicationReadyEvent) {
restoreRootLoggerLevel((ApplicationReadyEvent) springEvent);
}
}
具体逻辑
private void lowerRootLoggerLevel(ApplicationEnvironmentPreparedEvent event) {
Logger rootLogger = LoggerFactory.getLogger(Logger.ROOT_LOGGER_NAME);
if (rootLogger instanceof ch.qos.logback.classic.Logger) {
ch.qos.logback.classic.Logger logbackRootLogger = (ch.qos.logback.classic.Logger) rootLogger;
originalLevel = logbackRootLogger.getLevel();
if (originalLevel.toInt() > STARTUP_LOG_LEVEL.toInt()) {
logbackRootLogger.setLevel(STARTUP_LOG_LEVEL);
LOGGER.info("Lowering the ROOT logger level from {} to {} to capture Raptor's startup logs.", originalLevel,
STARTUP_LOG_LEVEL);
}
}
}
private void restoreRootLoggerLevel(ApplicationReadyEvent event) {
Logger rootLogger = LoggerFactory.getLogger(Logger.ROOT_LOGGER_NAME);
if (rootLogger instanceof ch.qos.logback.classic.Logger) {
ch.qos.logback.classic.Logger logbackRootLogger = (ch.qos.logback.classic.Logger) rootLogger;
if (originalLevel != null && originalLevel.toInt() > STARTUP_LOG_LEVEL.toInt()) {
LOGGER.info("Raptor's startup has completed. Restoring the ROOT logger level back to {}.", originalLevel);
logbackRootLogger.setLevel(originalLevel);
}
logbackRootLogger.detachAppender(STARTUP_APPENDER_NAME);
redirectStandout();
}
}
private void redirectStandout() {
Logger stdoutLogger = LoggerFactory.getLogger(STDOUT_LOGGER_NAME);
if (stdoutLogger != null && stdoutLogger instanceof ch.qos.logback.classic.Logger) {
ch.qos.logback.classic.Logger logger = (ch.qos.logback.classic.Logger) stdoutLogger;
if (logger.getAppender("STDOUT-APPENDER") != null) {
try {
System.setOut(new PrintStream(new LogbackOutputStream(logger, Level.toLocationAwareLoggerInteger(Level.INFO)),
true, "UTF-8"));
System.setErr(new PrintStream(
new LogbackOutputStream(logger, Level.toLocationAwareLoggerInteger(Level.ERROR)), true, "UTF-8"));
} catch (UnsupportedEncodingException e) {
LOGGER.error("Failed to redirect standout and standerr.", e);
}
}
}
}
其中
logbackRootLogger.detachAppender(STARTUP_APPENDER_NAME);
redirectStandout();
目的就是在运行时候把console logger 去掉,取而代之就是输出Error level 的到文件。