接上篇说道spring-boot在使用logback的坑之后又发现一个坑,自定义的appender以及原生的appender的stop()方法不会调用,导致部分清理工作无法完成。
原因:上篇说到去掉了LoggingApplicationListener使spring-boot不再管理logback,导致没有add一个shutdownhook
首先我们去掉上篇的方法,让spring-boot依旧管理logback,这样理论上就可以调用stop做清理工作了,但是最后发现还是不行,debug跟踪代码,如下:
private void registerShutdownHookIfNecessary(Environment environment,
LoggingSystem loggingSystem) {
boolean registerShutdownHook = new RelaxedPropertyResolver(environment)
.getProperty(REGISTER_SHUTDOWN_HOOK_PROPERTY, Boolean.class, false);
if (registerShutdownHook) {
Runnable shutdownHandler = loggingSystem.getShutdownHandler();
if (shutdownHandler != null
&& shutdownHookRegistered.compareAndSet(false, true)) {
registerShutdownHook(new Thread(shutdownHandler));
}
}
LoggingSystem loggingSystem) {
boolean registerShutdownHook = new RelaxedPropertyResolver(environment)
.getProperty(REGISTER_SHUTDOWN_HOOK_PROPERTY, Boolean.class, false);
if (registerShutdownHook) {
Runnable shutdownHandler = loggingSystem.getShutdownHandler();
if (shutdownHandler != null
&& shutdownHookRegistered.compareAndSet(false, true)) {
registerShutdownHook(new Thread(shutdownHandler));
}
}
}
registerShutdownHook这个变量的值默认是false,所以不会加hook,查看REGISTER_SHUTDOWN_HOOK_PROPERTY,发现该值为:logging.register-shutdown-hook,那么这个应该是spring-boot中的application.properties中的配置项,将该值配置为true
结果发现果真可以调用stop。
那么这样又会引入之前上篇说到的初始化两次的问题,其实spring-boot在logback初始化之后会reset一次,自己再重新初始化,对程序没有影响,但是自定义的appender的log会打印两次,总是觉得不舒服。所以该问题应该从logback入手,让logback加一个hook去做清理工作
我们模仿spring-boot中的方法,看它如何实现,上面代码第6行调用getShutdownHandler(),我们进入该方法,LogbackLoggingSystem中:
@Override
public Runnable getShutdownHandler() {
return new ShutdownHandler();
}
public Runnable getShutdownHandler() {
return new ShutdownHandler();
}
private final class ShutdownHandler implements Runnable {
@Override
public void run() {
getLoggerContext().stop();
}
}
@Override
public void run() {
getLoggerContext().stop();
}
}
一直到现在的代码还是包在spring-boot中,我们不想引入spring的包,所以继续往下看,
private LoggerContext getLoggerContext() {
ILoggerFactory factory = StaticLoggerBinder.getSingleton().getLoggerFactory();
Assert.isInstanceOf(LoggerContext.class, factory,
String.format(
"LoggerFactory is not a Logback LoggerContext but Logback is on "
+ "the classpath. Either remove Logback or the competing "
+ "implementation (%s loaded from %s). If you are using "
+ "WebLogic you will need to add 'org.slf4j' to "
+ "prefer-application-packages in WEB-INF/weblogic.xml",
factory.getClass(), getLocation(factory)));
return (LoggerContext) factory;
}
ILoggerFactory factory = StaticLoggerBinder.getSingleton().getLoggerFactory();
Assert.isInstanceOf(LoggerContext.class, factory,
String.format(
"LoggerFactory is not a Logback LoggerContext but Logback is on "
+ "the classpath. Either remove Logback or the competing "
+ "implementation (%s loaded from %s). If you are using "
+ "WebLogic you will need to add 'org.slf4j' to "
+ "prefer-application-packages in WEB-INF/weblogic.xml",
factory.getClass(), getLocation(factory)));
return (LoggerContext) factory;
}
终于LoggerContext是logback中的类,我们只要拿到这个context,然后调用它的stop方法就可以了。
偶然发现logback-core包中有一个hook的package,那么更简单了,直接注册这个hook里面的某个类应该更简单,那么可以在自定义的appender中的start方法加入如下代码:
DelayingShutdownHook shutdownHook = new DelayingShutdownHook();
Runtime.getRuntime().addShutdownHook(new Thread(shutdownHook));
Runtime.getRuntime().addShutdownHook(new Thread(shutdownHook));
启动,果真ok~~