今天服务器上报错,想先去看一下日志进行排查,结果发现日志好久都没有输出过了。从上午排查到下午,刚刚解决,所以记录一下,但如今也只是知其然,并不知其因此然,因此若是你们有什么想法请在下方评论。 html
先说一下环境,服务器是linux,项目是运行在tomcat下的Spring项目,日志用的是log4j。java
首先,从10月13号开始便没有新的日志文件了。假设日志名为log.txt(若是你设置了DailyRollingFileAppender,那么你当天的日志文件就是log.txt),先备份该文件到其余目录下,而后删除该文件,从新启动tomcat。这是为了确认你的log4j配置是否有问题,由于这是最容易出错的地方。很遗憾,我不是这里出的问题,由于项目重启后,日志文件又从新生成了,但很奇怪的是,日志文件是空的,其大小为0.linux
感受本身碰上了很神奇的问题,所以我在本身的本地进行调试,启动项目后发现,正常的项目启动日志是有的:android
15:13:48:0253 INFO [RMI TCP Connection(3)-127.0.0.1] -Root WebApplicationContext: initialization completed in 18479 ms复制代码
但我本身的一些日志输出是不显示的,好比:web
private static final Logger log = LoggerFactory.getLogger(MyDomain.class);
log.info("show info log");复制代码
show info log
这句话就不打印,如今证实,个人日志配置没有问题,服务器也找到了个人日志文件,但应该是我本身的Logger是不对应正确的日志输出的,由于个人console(控制台)有显示。spring
接下来,我就是开始看源码了。先是LoggerFactory.getLogger(Class
clazz)
方法:api
public static Logger getLogger(Class<?> clazz) {
Logger logger = getLogger(clazz.getName());
if (DETECT_LOGGER_NAME_MISMATCH) {
Class<?> autoComputedCallingClass = Util.getCallingClass();
if (autoComputedCallingClass != null && nonMatchingClasses(clazz, autoComputedCallingClass)) {
Util.report(String.format("Detected logger name mismatch. Given name: \"%s\"; computed name: \"%s\".", logger.getName(),
autoComputedCallingClass.getName()));
Util.report("See " + LOGGER_NAME_MISMATCH_URL + " for an explanation");
}
}
return logger;
}复制代码
好吧,没什么用,看不出个人logger
变成了,继续看getLogger(String name)
方法:tomcat
public static Logger getLogger(String name) {
ILoggerFactory iLoggerFactory = getILoggerFactory();
return iLoggerFactory.getLogger(name);
}复制代码
这时我在return iLoggerFactory.getLogger(name);
这行打了断点,我看到了这样的东西:服务器
为何个人iLoggerFactory是用的logback中的实现?其实也是怪我本身大意,我其实依赖了一个基于Spring Boot的项目(虽然我只是用了里面的一些domain类,但由于设计不当,尚未把这些domain类单独提成一个_项目),而Spring Boot中通常默认就依赖的logback。经过gradle查看项目的依赖树,也证明了个人这一猜测(./gradlew 子项目名称:dependencies):dom
| +--- org.springframework.boot:spring-boot-starter-web:2.0.2.RELEASE
| | +--- org.springframework.boot:spring-boot-starter:2.0.2.RELEASE
| | | +--- org.springframework.boot:spring-boot:2.0.2.RELEASE
| | | | +--- org.springframework:spring-core:5.0.6.RELEASE (*)
| | | | \--- org.springframework:spring-context:5.0.6.RELEASE (*)
| | | +--- org.springframework.boot:spring-boot-autoconfigure:2.0.2.RELEASE
| | | | \--- org.springframework.boot:spring-boot:2.0.2.RELEASE (*)
| | | +--- org.springframework.boot:spring-boot-starter-logging:2.0.2.RELEASE
| | | | +--- ch.qos.logback:logback-classic:1.2.3
| | | | | +--- ch.qos.logback:logback-core:1.2.3
| | | | | \--- org.slf4j:slf4j-api:1.7.25复制代码
接下来就好办了,你排除掉ch.qos.logback
的依赖就能够了,在你的build.gradle
中增长:
configurations {
compile.exclude group: 'ch.qos.logback'
}复制代码
这个时候你再从新调试一下看看:
完美,如今是log4j中的实现,获得了我想要的操做。固然了,既然我知道以前项目中的slf4j是logback实现了,那么我天然也能够换成logback的配置,但这就须要我将项目换成用Spring Boot启动,这个改动有点大,若是之后有必要的话,我再将这个exclude删除,换成Spring Boot的形式。
此次Spring Boot帮咱们默认启用的是logback,那么有没有什么简单方法能够知道呢?若是你的项目出现了如下的日志输出,说明你的项目当前有不止一个SLF4J的实现组件:
SLF4J: Class path contains multiple SLF4J bindings.
SLF4J: Found binding in [jar:file:/project.war/WEB-INF/lib/logback-classic-1.2.3.jar!/org/slf4j/impl/StaticLoggerBinder.class]
SLF4J: Found binding in [jar:file:/project.war/WEB-INF/lib/slf4j-log4j12-1.7.21.jar!/org/slf4j/impl/StaticLoggerBinder.class]
SLF4J: See http://www.slf4j.org/codes.html#multiple_bindings for an explanation.
SLF4J: Actual binding is of type [ch.qos.logback.classic.util.ContextSelectorStaticBinder]复制代码
由于在org.slf4j.LoggerFactory
的bind
方法中有关于这方面的输出:
private final static void bind() {
try {
Set<URL> staticLoggerBinderPathSet = null;
// skip check under android, see also
// http://jira.qos.ch/browse/SLF4J-328
if (!isAndroid()) {
// 查找你的当前项目有几个slf4j的实现
staticLoggerBinderPathSet = findPossibleStaticLoggerBinderPathSet();
// 若是多余一个就打印
reportMultipleBindingAmbiguity(staticLoggerBinderPathSet);
}
// the next line does the binding
// 这个是具体选了哪个实现(重点关注)
StaticLoggerBinder.getSingleton();
INITIALIZATION_STATE = SUCCESSFUL_INITIALIZATION;
reportActualBinding(staticLoggerBinderPathSet);
fixSubstituteLoggers();
replayEvents();
// release all resources in SUBST_FACTORY
SUBST_FACTORY.clear();
} catch (NoClassDefFoundError ncde) {
String msg = ncde.getMessage();
if (messageContainsOrgSlf4jImplStaticLoggerBinder(msg)) {
INITIALIZATION_STATE = NOP_FALLBACK_INITIALIZATION;
Util.report("Failed to load class \"org.slf4j.impl.StaticLoggerBinder\".");
Util.report("Defaulting to no-operation (NOP) logger implementation");
Util.report("See " + NO_STATICLOGGERBINDER_URL + " for further details.");
} else {
failedBinding(ncde);
throw ncde;
}
} catch (java.lang.NoSuchMethodError nsme) {
String msg = nsme.getMessage();
if (msg != null && msg.contains("org.slf4j.impl.StaticLoggerBinder.getSingleton()")) {
INITIALIZATION_STATE = FAILED_INITIALIZATION;
Util.report("slf4j-api 1.6.x (or later) is incompatible with this binding.");
Util.report("Your binding is version 1.5.5 or earlier.");
Util.report("Upgrade your binding to version 1.6.x.");
}
throw nsme;
} catch (Exception e) {
failedBinding(e);
throw new IllegalStateException("Unexpected initialization failure", e);
}
}复制代码
特别要注意的是StaticLoggerBinder.getSingleton();
这行代码,StaticLoggerBinder
在logback-classic和slf4j-log4j12这两个jar包各有一个,所以,Spring boot是自动选择logback-classic(虽然我在本地运行的时候仍是默认进入的slf4j-log4j12,可是会提醒我Source code does not match the bytecode
,所以我判断依旧进的是logback-classic),因此只要把logback给exclude掉,就解决了这个问题。
如今看问题,更加关注源代码,由于这可让咱们更加快速定位问题,而且也能据此大体猜出其解决方案。但愿你们能一块儿看看源代码,若是你有什么发现,能够在下方留言,我将和你一块儿讨论。
有兴趣的话能够关注个人公众号或者头条号,说不定会有意外的惊喜。