问题描述

项目使用Spring Boot框架,在pom文件中添加了如下配置:

  1. <dependency>
  2. <groupId>org.slf4j</groupId>
  3. <artifactId>slf4j-api</artifactId>
  4. <version>1.7.36</version>
  5. </dependency>
  6. <dependency>
  7. <groupId>org.slf4j</groupId>
  8. <artifactId>slf4j-simple</artifactId>
  9. <version>1.7.30</version>
  10. </dependency>
  11. <dependency>
  12. <groupId>org.springframework.boot</groupId>
  13. <artifactId>spring-boot-starter-log4j2</artifactId>
  14. </dependency>

使用SLF4J的API进行日志输出,并且也明确配置了log4j2写日志文件。

  1. import org.slf4j.Logger;
  2. import org.slf4j.LoggerFactory;
  3. private Logger log = LoggerFactory.getLogger(TestController.class);

但是在项目代码中输出的日志信息始终不输出到文件中,只在控制台输出。

一开始我以为是log4j的配置问题:只输出到控制台,不输出到文件,但是反复确认配置没问题。

解决步骤

由于这是一个新介入的老项目,一开始并没有从“配置依赖可能有问题”这个角度去考虑,另外一点就是项目的启动日志太多了,在启动的时候很快就产生许多信息,把关键的的错误信息错过了。

后来经过反复查看启动日志才发现,原来是因为项目中同时添加了slf4j-simple配置,项目启动时默认加载它作为日志实现。因此,log4j2的配置就不生效了。

  1. SLF4J: Class path contains multiple SLF4J bindings.
  2. SLF4J: Found binding in [jar:file:/D:/.m2/repository/org/slf4j/slf4j-simple/1.7.30/slf4j-simple-1.7.30.jar!/org/slf4j/impl/StaticLoggerBinder.class]
  3. SLF4J: Found binding in [jar:file:/D:/.m2/repository/org/apache/logging/log4j/log4j-slf4j-impl/2.13.3/log4j-slf4j-impl-2.13.3.jar!/org/slf4j/impl/StaticLoggerBinder.class]
  4. SLF4J: See http://www.slf4j.org/codes.html#multiple_bindings for an explanation.
  5. SLF4J: Actual binding is of type [org.slf4j.impl.SimpleLoggerFactory] // 这里是关键日志,明确了项目启动时加载的日志实现
  6. [restartedMain] INFO org.apache.coyote.http11.Http11NioProtocol - Initializing ProtocolHandler ["http-nio-8080"]
  7. [restartedMain] INFO org.apache.catalina.core.StandardService - Starting service [Tomcat]
  8. [restartedMain] INFO org.apache.catalina.core.StandardEngine - Starting Servlet engine: [Apache Tomcat/9.0.44]
  9. [restartedMain] INFO org.apache.catalina.core.ContainerBase.[Tomcat].[localhost].[/] - Initializing Spring embedded WebApplicationContext

定位到是因为同时加载了slf4j-simple的缘故,只要去除该依赖即可。

虽然已经解决了问题,但同时也不禁让我疑惑,难道Slf4j会优先加载slf4j-simple吗?带着这个疑问,继续追踪一下源码。

原因追踪

追踪slf4j-api的源码发现,当classpath路径存在slf4j-simple时,是一定会优先加载其中的org.slf4j.impl.StaticLoggerBinder类的。

也就是说,当slf4j-simple存在classpath下时,总是优先使用它作为slf4j-api的默认实现;此时,即使同时配置了log4j,也无法使用log4j进行日志输出。

详细源码解读如下:

  1. // slf4j-api.jar
  2. // org.slf4j.LoggerFactory
  3. public final class LoggerFactory {
  4. private static String STATIC_LOGGER_BINDER_PATH = "org/slf4j/impl/StaticLoggerBinder.class";
  5. // bind()方法是绑定日志实现的入口
  6. private final static void bind() {
  7. try {
  8. Set<URL> staticLoggerBinderPathSet = null;
  9. // skip check under android, see also
  10. // http://jira.qos.ch/browse/SLF4J-328
  11. if (!isAndroid()) {
  12. staticLoggerBinderPathSet = findPossibleStaticLoggerBinderPathSet();
  13. reportMultipleBindingAmbiguity(staticLoggerBinderPathSet);
  14. }
  15. // 这一句是最关键的,当classpath路径下存在slf4j-simple时,总是会优先加载slf4j-simple中定义的StaticLoggerBinder
  16. // the next line does the binding
  17. StaticLoggerBinder.getSingleton();
  18. INITIALIZATION_STATE = SUCCESSFUL_INITIALIZATION;
  19. reportActualBinding(staticLoggerBinderPathSet);
  20. } catch (NoClassDefFoundError ncde) {
  21. // 省略部分代码...
  22. }
  23. }
  24. // 在findPossibleStaticLoggerBinderPathSet()方法中加载slf4j接口的日志实现类
  25. static Set<URL> findPossibleStaticLoggerBinderPathSet() {
  26. // use Set instead of list in order to deal with bug #138
  27. // LinkedHashSet appropriate here because it preserves insertion order
  28. // during iteration
  29. Set<URL> staticLoggerBinderPathSet = new LinkedHashSet<URL>();
  30. try {
  31. ClassLoader loggerFactoryClassLoader = LoggerFactory.class.getClassLoader();
  32. Enumeration<URL> paths;
  33. // 使用类加载器加载类定义
  34. // 有意思的是在slf4j-simple和log4j-slf4j-impl包中都同时存在org.slf4j.impl.StaticLoggerBinder类
  35. // 所以当使用路径“org/slf4j/impl/StaticLoggerBinder.class”加载类时,会同时把2个类都加载出来
  36. // 但是只会使用slf4j-simple中的StaticLoggerBinder
  37. if (loggerFactoryClassLoader == null) {
  38. paths = ClassLoader.getSystemResources(STATIC_LOGGER_BINDER_PATH);
  39. } else {
  40. paths = loggerFactoryClassLoader.getResources(STATIC_LOGGER_BINDER_PATH);
  41. }
  42. while (paths.hasMoreElements()) {
  43. URL path = paths.nextElement();
  44. staticLoggerBinderPathSet.add(path);
  45. }
  46. } catch (IOException ioe) {
  47. Util.report("Error getting resources from path", ioe);
  48. }
  49. return staticLoggerBinderPathSet;
  50. }
  51. }

另外:当使用logback作为slf4j的日志实现组件时,不再允许依赖其他日志实现组件,即:logback-classic不能与slf4j-simplelog4j-slf4j-impl共存,

这是因为在加载logback时了做了检查:

  1. private LoggerContext getLoggerContext() {
  2. ILoggerFactory factory = StaticLoggerBinder.getSingleton().getLoggerFactory();
  3. // 判断加载的日志工厂类是否为logback的LoggerContext,如果不是则抛出异常
  4. Assert.isInstanceOf(LoggerContext.class, factory,
  5. () -> String.format(
  6. "LoggerFactory is not a Logback LoggerContext but Logback is on "
  7. + "the classpath. Either remove Logback or the competing "
  8. + "implementation (%s loaded from %s). If you are using "
  9. + "WebLogic you will need to add 'org.slf4j' to "
  10. + "prefer-application-packages in WEB-INF/weblogic.xml",
  11. factory.getClass(), getLocation(factory)));
  12. return (LoggerContext) factory;
  13. }

如果使用logback作为slf4j的日志实现组件,则只允许添加slf4j-apilogback-classic依赖,此时如果还添加了slf4j-simplelog4j-slf4j-impl依赖,则项目无法启动。

添加如下配置时启动正常:

  1. <!-- 使用loback作为slf4j的日志实现组件 -->
  2. <dependency>
  3. <groupId>org.slf4j</groupId>
  4. <artifactId>slf4j-api</artifactId>
  5. <version>1.7.36</version>
  6. </dependency>
  7. <dependency>
  8. <groupId>ch.qos.logback</groupId>
  9. <artifactId>logback-classic</artifactId>
  10. <version>1.2.10</version>
  11. </dependency>

同时添加logbacklog4j2时启动失败:

  1. <!-- logback无法与log4j2共存 -->
  2. <dependency>
  3. <groupId>org.slf4j</groupId>
  4. <artifactId>slf4j-api</artifactId>
  5. <version>1.7.36</version>
  6. </dependency>
  7. <dependency>
  8. <groupId>ch.qos.logback</groupId>
  9. <artifactId>logback-classic</artifactId>
  10. <version>1.2.10</version>
  11. </dependency>
  12. <dependency>
  13. <groupId>org.springframework.boot</groupId>
  14. <artifactId>spring-boot-starter-log4j2</artifactId>
  15. </dependency>

报错信息如下:

  1. # “/D:/.m2/repository/org/apache/logging/log4j/log4j-slf4j-impl/2.13.3/log4j-slf4j-impl-2.13.3.jar”是本地Maven仓库路径
  2. Caused by: java.lang.IllegalArgumentException: LoggerFactory is not a Logback LoggerContext but Logback is on the classpath. Either remove Logback or the competing implementation (class org.apache.logging.slf4j.Log4jLoggerFactory loaded from file:/D:/.m2/repository/org/apache/logging/log4j/log4j-slf4j-impl/2.13.3/log4j-slf4j-impl-2.13.3.jar). If you are using WebLogic you will need to add 'org.slf4j' to prefer-application-packages in WEB-INF/weblogic.xml: org.apache.logging.slf4j.Log4jLoggerFactory

同时添加lobackslf4j-simple时启动失败:

  1. <!-- logback无法与slf4j-simple共存 -->
  2. <dependency>
  3. <groupId>org.slf4j</groupId>
  4. <artifactId>slf4j-api</artifactId>
  5. <version>1.7.36</version>
  6. </dependency>
  7. <dependency>
  8. <groupId>ch.qos.logback</groupId>
  9. <artifactId>logback-classic</artifactId>
  10. <version>1.2.10</version>
  11. </dependency>
  12. <dependency>
  13. <groupId>org.slf4j</groupId>
  14. <artifactId>slf4j-simple</artifactId>
  15. <version>1.7.30</version>
  16. </dependency>

报错信息如下:

  1. # “/D:/.m2/repository/org/slf4j/slf4j-simple/1.7.30/slf4j-simple-1.7.30.jar”是本地Maven仓库路径
  2. Caused by: java.lang.IllegalArgumentException: LoggerFactory is not a Logback LoggerContext but Logback is on the classpath. Either remove Logback or the competing implementation (class org.slf4j.impl.SimpleLoggerFactory loaded from file:/D:/.m2/repository/org/slf4j/slf4j-simple/1.7.30/slf4j-simple-1.7.30.jar). If you are using WebLogic you will need to add 'org.slf4j' to prefer-application-packages in WEB-INF/weblogic.xml: org.slf4j.impl.SimpleLoggerFactory

但是!slf4j-simplelog4j-slf4j-impl是可以共存的,但是优先只会使用slf4j-simple作为slf4j的日志实现。

如下配置不会导致项目启动失败:

  1. <!-- slf4j-simple可以与log4j-slf4j-impl共存,但是优先使用slf4j-simple -->
  2. <dependency>
  3. <groupId>org.slf4j</groupId>
  4. <artifactId>slf4j-simple</artifactId>
  5. <version>1.7.30</version>
  6. </dependency>
  7. <dependency>
  8. <groupId>org.springframework.boot</groupId>
  9. <artifactId>spring-boot-starter-log4j2</artifactId>
  10. </dependency>

最后总结

在使用Spring Boot框架时,默认使用的日志实现组件是logback,如果需要使用其他日志实现组件(如:log4j2),需要做2步:

第一,排除默认对spring-boot-starter-logging模块的依赖。

  1. <dependency>
  2. <groupId>org.springframework.boot</groupId>
  3. <artifactId>spring-boot-starter-web</artifactId>
  4. <exclusions>
  5. <exclusion>
  6. <!-- 排除Spring Boot默认使用的日志依赖 -->
  7. <groupId>org.springframework.boot</groupId>
  8. <artifactId>spring-boot-starter-logging</artifactId>
  9. </exclusion>
  10. </exclusions>
  11. </dependency>

第二,明确引入对log4j2的依赖配置。

  1. <dependency>
  2. <groupId>org.springframework.boot</groupId>
  3. <artifactId>spring-boot-starter-log4j2</artifactId>
  4. </dependency>

同时,需要确定在项目启动的classpath路径下有对应log4j2的配置文件存在,如:classpath:log4j2.xml。

如下是log4j2的简单配置示例。

  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <configuration status="warn" debug="true" packages="qs.config">
  3. <Properties>
  4. <!-- 配置日志文件输出目录 ${sys:user.home} -->
  5. <Property name="LOG_HOME">${sys:user.home}/test-springboot-simple</Property>
  6. <property name="PATTERN">%d{MM-dd HH:mm:ss.SSS} [%t-%L] %-5level %logger{36} - %msg%n</property>
  7. </Properties>
  8. <appenders>
  9. <Console name="Console" target="SYSTEM_OUT">
  10. <ThresholdFilter level="DEBUG" onMatch="ACCEPT" onMismatch="DENY"/>
  11. <PatternLayout pattern="[%d{HH:mm:ss.SSS}] %-5level %class{36} %L %M - %msg%xEx%n"/>
  12. </Console>
  13. <RollingFile name="RollingFileInfo" fileName="${LOG_HOME}/info.log" filePattern="${LOG_HOME}/$${date:yyyy-MM}/info-%d{yyyy-MM-dd}-%i.log">
  14. <ThresholdFilter level="info" onMatch="ACCEPT" onMismatch="DENY"/>
  15. <PatternLayout pattern="%d{yyyy-MM-dd HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n"/>
  16. <Policies>
  17. <TimeBasedTriggeringPolicy interval="1" modulate="true"/>
  18. <SizeBasedTriggeringPolicy size="100MB"/>
  19. </Policies>
  20. </RollingFile>
  21. </appenders>
  22. <loggers>
  23. <root level="info">
  24. <appender-ref ref="RollingFileInfo"/>
  25. <appender-ref ref="Console"/>
  26. </root>
  27. </loggers>
  28. </configuration>

【参考】

https://blog.csdn.net/death05/article/details/83618878 log4j日志不输出的问题

排查log4j不输出日志到文件的问题的更多相关文章

  1. Log4j指定输出日志的文件

    在Log4j的配置文件中,有一个log4j.rootLogger用于指定将何种等级的信息输出到哪些文件中, 这一项的配置情况如下: log4j.rootLogger=日志等级,输出目的地1,输出目的地 ...

  2. log4j配置输出日志文件

    在测试程序时,有时候运行一次可能需要很久,把日志文件保存下来是很有必要的,本文给出了scala程序输出日志文件的方式,同时使用本人的另一篇博客中介绍的将log4j.properties放到程序jar包 ...

  3. 使用log4j无法输出日志

    前段时间在项目的过程中使用log4j来输出日志,但是在一个项目里我明明已经在src/main/resource目录下创建了log4j.properties.具体配置如下: log4j.rootLogg ...

  4. 3-log4j2之输出日志到文件

    一.添加maven依赖 <dependencies> <dependency> <groupId>org.apache.logging.log4j</grou ...

  5. log4j输出日志到文件

    输出端Appender Appender用来指定日志信息输出到哪个地方,可以同时指定多个输出目的地.Log4j允许将信息输出到许多不同的输出设备中,一个log信息输出目的地就叫做一个Appender. ...

  6. 记一次排查log4net 不输出日志的解决过程

    最近发现log4net 不输出日志了,重点排查几个地方,发现都没有问题. 1.[assembly: log4net.Config.XmlConfigurator(ConfigFile = " ...

  7. log4j不输出日志错误分析

    1.rootLogger不输出 代码如下: 配置文件代码: log4j.rootLogger=info, R,userLog log4j.appender.R=org.apache.log4j.Rol ...

  8. python3:logging模块 输出日志到文件

    python自动化测试脚本运行后,想要将日志保存到某个特定文件,使用python的logging模块实现 参考代码: import logging def initLogging(logFilenam ...

  9. PHP 输出日志到文件 DEMO

    首先需要确保输出文件有权限写入,一般设置权限为 chown -R nginx.nginx 输出的文件路径 如果以上方法还是无效,可以直接将文件设置有777,但是这种方式只能用于测试环境 chmod - ...

随机推荐

  1. 细谈 Java 匿名内部类 【分别 使用 接口 和 抽象类实现】

    1.前言 匿名内部类是什么东西? 没有名字的内部类就是匿名内部类. 什么场景使用? 匿名内部类适合创建那种只需要一次使用的类. 这是个很有用的东西,可想而知,如果不使用匿名内部类,哪些只需要使用一次的 ...

  2. spring boot --- 注解 @Bean 和@Component

    1.前言 @Bean是给方法注册bean @Component是给不好归类的类注册bean 2.可以达到一样的效果 (1)@Component 直接注册即可 完整源码 package com.exam ...

  3. react中使用charles实现本地数据mock

    首先下载charles软件地址,更详细的使用方法都包含在操作文档里,包含汉化版补丁(下载后查看) 链接:https://pan.baidu.com/s/1Q5rMbcX0Wus7AwdGUWa-Wg ...

  4. hisql ORM 查询语句使用教程

    HiSql 提供一个可以适合多种数据库的中间查询语法,不需要关注各个数据库的语法特性,通过HiSql写语句可以在常用的不同类型数据库中执行,且语法与Sql语境类似一看就懂一学就会 hisql.net ...

  5. 离线下载第三方Python包

    1.进入Python第三方包下载地(https://pypi.org/)搜索自己需要的包 2.下载需要的包的版本 3.将.whl格式的文件更改为.zip文件,并且解压 4.将解压的2个文件放到Pyth ...

  6. JAVA-JDK1.7-ConCurrentHashMap-测试和验证

    概述 上次记录了关于ConCurrentHashMap的原理以及源码,现在我对其进行有关功能的测试,下面就讲解一下我测试的内容和代码.这些测试主要针对JDK1.7版本. GET安全测试 上一篇写过ge ...

  7. MySQL索引失效的常见场景

    当然请记住,explain是一个好习惯! MySQL索引失效的常见场景 在验证下面的场景时,请准备足够多的数据量,因为数据量少时,MySQL的优化器有时会判定全表扫描无伤大雅,就不会命中索引了. 1. ...

  8. day 20 C语言顺序结构基础3

    (1).若有定义:int a=100:则语句printf("%d%d%d\n",sizeof("a"),sizeof(a),sizeof(3.14)); 则输出 ...

  9. golang中math常见数据数学运算

    package main import ( "fmt" "math" ) func main() { fmt.Println(math.Abs(-19)) // ...

  10. SharePoint Online 触发 Outlook 邮件内审批

    前言 我们在做SharePoint Online项目时, 经常会有客户问,我们能否在通知邮件中快速完成审批,而不是需要在邮件中打开系统,然后在系统中审批? 答案肯定是可以的,来!安排! 正文 1.我们 ...