日志不打印的问题,很让人头疼,也是我们经常遇到的问题。

日常站点状态巡检时发现有异常日志,定位到日志位置,看其上线文自定义输出的日志时却发现,自己加的日志都没输出。排查了一下初步定位到,这个类中日志输出使用的lombok@Slf4j注解的功能,浏览了一下其他使用该注解的类,自定义加的日志也都在线上没打印。而使用LoggerFactory.getLogger(Class<?> clazz)获取的Logger对象打印的日志,在线上能正常打印。

本地启动,使用的lombok@Slf4j注解的类,日志也能打印,测试环境也可以。就很奇怪!

有点经验的都会猜到,jar包冲突导致的。

怎么验证以及解决呢?

maven依赖树日志

首先可以用maven的命令mvn dependency:tree

如下,将maven依赖树输出到文件,方便查看和检索

mvn dependency:tree > log.txt

可以搜索log等关键词,看除了自己引的log包以外,还有没有通过其他第三方包间接引入了其他版本或其他日志实现的jar(如,你使用的log4j,你依赖的一个第三方包里依赖了logback等)。通过这种方式可以排查出大部分冲突的依赖。

如,我引入了zkClient的包,它依赖的log版本和我的不一样

我就可以通过如下方式排除它的jar里的log包。还有其他的也一样排除掉,这里没有意义列举。

<dependency><groupId>com.101tec</groupId><artifactId>zkclient</artifactId><version>0.4</version><exclusions><exclusion><groupId>log4j</groupId><artifactId>log4j</artifactId></exclusion></exclusions>
</dependency>

Slf4j绑定日志实现的原理

这种方式找起来其实不那么方便,看了这位老哥的博文,明白了冲突的原理。大概引用一下原文吧

系统使用的是SLF4J框架打印log4j2的日志。查看系统中引入的jar包发现果然有多个SLF4J的桥接包。于是排掉冲突jar包,然后上线时所有机器都正常打印日志。

我们都知道,SLF4J是一个日志门面,下图是SLF4J框架、各种具体日志实现以及相应桥接包的关系图,来源于那篇文章。


SLF4J框架作为门面框架,并没有日志的具体实现。而是通过和其他具体日志实现进行关联转换,并在系统中配置一种日志实现进行打印。

于是就很容易造成jar包引入冲突,导致有多个日志实现。当SLF4J框架选择的日志实现和我们配置的不一致时,就会打印不出日志。

SLF4J框架发现有多个日志实现时,是会打印提示信息的。但由于是标准错误输出,会在控制台(Tomcat的catalina.out)中打印【当业务日志文件中没有日志打印时,可以查看catalina.out是否有提示】



因为每个SLF4J的桥接包都有org.slf4j.impl.StaticLoggerBinder,而SLF4J则会随机选择一个使用。当选择的跟系统配置的一样时就可以打印日志,否则就打印不出。所以就会出现有的机器打印日志,而有的机器可能就不打印日志。

快速感知是否存在多个桥接包

刚才通过maven依赖树肉眼找的方式,不是太方便,了解了Slf4j绑定日志实现的原理,我们就可以通过调用其findPossibleStaticLoggerBinderPathSet方法的返回的Set集合获取当前有多少个桥接包,然后再通过再依赖树输出的日志里搜索具体的包名。

具体方式:

  1. 实现spring的BeanFactoryPostProcessor,并将其交由spring管理。保证系统启动后,自动进行日志冲突校验。
  2. 使用反射获取LoggerFactory的实例以及findPossibleStaticLoggerBinderPathSet方法的返回结果。
  3. 根据桥接包数量判断是否异常,进行自定义报警。
  4. 根据报警信息,在依赖树日志中搜索,看是从那个依赖中间接引入的,然后进行排包。
import org.apache.commons.collections4.CollectionUtils;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.stereotype.Component;import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.net.InetAddress;
import java.net.URL;
import java.util.Set;/*** 日志jar包冲突校验*/
@Component
public class LogJarConflictCheck implements BeanFactoryPostProcessor {@Overridepublic void postProcessBeanFactory(ConfigurableListableBeanFactory configurableListableBeanFactory) throws BeansException {try {Class<LoggerFactory> loggerFactoryClazz = LoggerFactory.class;Constructor<LoggerFactory> constructor = loggerFactoryClazz.getDeclaredConstructor();constructor.setAccessible(true);LoggerFactory instance = constructor.newInstance();Method method = loggerFactoryClazz.getDeclaredMethod("findPossibleStaticLoggerBinderPathSet");// 强制进入method.setAccessible(true);Set<URL> staticLoggerBinderPathSet = (Set<URL>)method.invoke(instance);if (CollectionUtils.isEmpty(staticLoggerBinderPathSet)) {handleLogJarConflict(staticLoggerBinderPathSet, "Class path is Empty.添加对应日志jar包");}if (staticLoggerBinderPathSet.size() == 1) {return;}handleLogJarConflict(staticLoggerBinderPathSet, "Class path contains multiple SLF4J bindings. 注意排包");} catch (Throwable t) {t.getStackTrace();}}/*** 日志jar包冲突报警* @param staticLoggerBinderPathSet jar包路径* @param tip 提示语*/private void handleLogJarConflict (Set<URL> staticLoggerBinderPathSet, String tip) {String ip = getLocalHostIp();StringBuilder detail = new StringBuilder();detail.append("ip为").append(ip).append("; 提示语为").append(tip);if (CollectionUtils.isNotEmpty(staticLoggerBinderPathSet)) {String path = JsonUtils.toJson(staticLoggerBinderPathSet);detail.append("; 重复的包路径分别为 ").append(path);}String logDetail = detail.toString();//可以自定义告警System.out.println("====>"+logDetail);}private String getLocalHostIp() {String ip;try {InetAddress addr = InetAddress.getLocalHost();ip = addr.getHostAddress();} catch (Exception var2) {ip = "";}return ip;}
}

通过配置一劳永逸

上面的方式也只是帮助我们快速感知到日志jar包冲突,仍需手动排包。

是否存在一种解决方法,能帮忙我们彻底解决这种问题呢?

答案是有。

即将我们需要引入的jar包和需要排掉的jar包声明到maven的最上层,将需要排掉的包声明为provided即可

这种方案是利用maven的扫包策略:

  1. 依赖最短路径优先原则;

  2. 依赖路径相同时,申明顺序优先原则

当我们将所有jar包声明为直接依赖后,会优先被使用。而我们需要排掉的包只要声明为provided,就不会打入包中。从而实现需要的包以我们声明的为准,需要排掉的包也不会被间接依赖影响

<properties><slf4j.version>1.7.7</slf4j.version><logback.version>1.2.3</logback.version><log4j.version>1.2.17</log4j.version><log4j2.version>2.3</log4j2.version><jcl.version>1.2</jcl.version>
</properties>
<dependencies><!--系统使用log4j2作为系统日志实现 slf4J作为门面 --><dependency><groupId>org.slf4j</groupId><artifactId>slf4j-api</artifactId><version>${slf4j.version}</version></dependency><!--使用log4j2作为实际的日志实现--><dependency><groupId>org.apache.logging.log4j</groupId><artifactId>log4j-core</artifactId><version>${log4j2.version}</version></dependency><dependency><groupId>org.apache.logging.log4j</groupId><artifactId>log4j-api</artifactId><version>${log4j2.version}</version></dependency><!--将log4j、logback、JCL的jar包设置为provided,不打入包中--><dependency><groupId>log4j</groupId><artifactId>log4j</artifactId><version>${log4j.version}</version><scope>provided</scope></dependency><dependency><groupId>ch.qos.logback</groupId><artifactId>logback-classic</artifactId><version>${logback.version}</version><scope>provided</scope></dependency><dependency><groupId>commons-logging</groupId><artifactId>commons-logging</artifactId><version>${jcl.version}</version><scope>provided</scope></dependency><!--为防止循环转换,排掉log4j2转slf4j的桥接包--><dependency><groupId>org.apache.logging.log4j</groupId><artifactId>log4j-to-slf4j</artifactId><version>${log4j2.version}</version><scope>provided</scope></dependency><!--声明log4j、JCL、JUL转slf4j的桥接包,代码中对应日志可以转成SLF4J--><dependency><groupId>org.slf4j</groupId><artifactId>log4j-over-slf4j</artifactId><version>${slf4j.version}</version></dependency><dependency><groupId>org.slf4j</groupId><artifactId>jcl-over-slf4j</artifactId><version>${slf4j.version}</version></dependency><dependency><groupId>org.slf4j</groupId><artifactId>jul-to-slf4j</artifactId><version>${slf4j.version}</version></dependency><!--声明slf4j转SLF4J的桥接包--><dependency><groupId>org.apache.logging.log4j</groupId><artifactId>log4j-slf4j-impl</artifactId><version>${log4j2.version}</version></dependency><!--排掉slf4j转log4j、JCL、JUL转slf4j的桥接包的桥接包,防止日志实现jar包冲突--><dependency><groupId>org.slf4j</groupId><artifactId>slf4j-log4j12</artifactId><version>${slf4j.version}</version><scope>provided</scope></dependency><dependency><groupId>org.slf4j</groupId><artifactId>slf4j-jdk14</artifactId><version>${slf4j.version}</version><scope>provided</scope></dependency><dependency><groupId>org.slf4j</groupId><artifactId>slf4j-jcl</artifactId><version>${slf4j.version}</version><scope>provided</scope></dependency>
</dependencies>

我没有采用这种方式,原因是改动太多,风险性太大。我更倾向于上面的方式,可以在每次引入第三方依赖时,手动检测一次,或通上面的方法进行自动检测,再手动进行排除。

欢迎关注关注:BiggerBoy


参考:https://blog.csdn.net/zy1817204670/article/details/121154660

解决Slf4j日志不打印问题相关推荐

  1. springboot 打印slf4_SpringBoot 整合 slf4j 日志打印

    划水时间,记录一下用到的相关slf4j 日志打印,如何实现配置输出.本地保存log日志文件... 我使用的是SpringBoot框架,slf4j 类库已经包含到了 SpringBoot 框架中,所有, ...

  2. 日志无法打印问题总结

    日志无法打印问题总结 现象: log4j2运行环境可以生成日志,但是没有任何打印信息. 1 日志无法打印 最近新开发的服务,k8s容器部署后,发现log4j2的日志无法打印,定义的日志都生成了相关的日 ...

  3. fastapi日志重复打印_【FastAPI】踩坑总结

    阅读目录 一.部署之殇 二.日志之殇 三.中间件之殇 四.配置文件之殇 五.其它 一.部署之殇 1 linux后台启动 nohup uvicorn main:app --host 0.0.0.0 -- ...

  4. Scala与Java混编译:java日志不打印的问题

    1.背景 我本地测试,大部分代码是scla开发,少部分是java代码,然后本地测试都是正确的. 19/09/04 20:01:32 INFO TopoSparkSubmitter: 加载Spark默认 ...

  5. slf4j没有在linux中生成日志,slf4j日志记录问题 - 未生成日志文件

    我正在使用slf4j通过java实用程序日志记录.我试图放置logging.properties文件,以便它会被我的web应用程序拾取.以下是我的logging.properties文件怎么样子:sl ...

  6. java slf4j日志级别_SLF4J日志级别以及使用场景

    为什么要使用日志 在项目开发的过程中, 添加合适的日志是一个必不可少的过程,给程序添加合适的日志有以下两个好处. 可以通过查看日志的输出,了解程序的运行状况,判断程序是否按预期进行运行. 程序出现bu ...

  7. 宝塔面板使用WWW用户执行计划任务命令 解决laravel日志权限问题 宝塔设置计划任务执行用户

    宝塔面板使用WWW用户执行计划任务命令 解决laravel日志权限问题 宝塔设置计划任务执行用户 问题背景 宝塔面板的计划任务默认执行用户是root,如果任务里有打印日志的操作,则自动创建的log文件 ...

  8. java http打印请求日志_spring打印http接口请求和响应

    在程序日志中打印出接口请求和响应的内容是一个基本的技术需求.如果在每个接口中实现请求响应的日志打印,程序编写会很繁琐,我们可以利用spring提供的机制,集中处理接口请求响应的日志打印. 具体的代码参 ...

  9. java日志优雅打印格式_优雅编程之日志排查Log4j

    背景 程序开发调试中,不可缺少的便是日志管理,常用的日志管理框架有如下几种: Log4j:Apache Log4j是一个基于Java的日志记录工具.它是由Ceki Gülcü首创的,现在则是Apach ...

最新文章

  1. [Android学习笔记]查看源代码
  2. 2017-2018-2 20165211 实验五《网络编程与安全》实验报告
  3. JSP页面空指针异常调错办法之weblogic
  4. graphpad做饼图_如此简单的饼图,这些点你可能还不知道
  5. Spring框架的事务管理及应用
  6. 利用ajax.dll进行Ajax的开发2007-07-15 15:38
  7. Java 方法、 流(Stream)、文件(File)和IO 总结
  8. verilog时钟翻转怎么写_verilog实时可调时钟代码
  9. Vue-cli项目中路由的基础用法,以及路由嵌套
  10. 突破”子网隔离”***C段
  11. ios查看帧率的软件_iOS开发-自己写一个实时显示fps帧数的小控件
  12. 自制超级精简版 360网盘6.5.2.1060(7文件,体积不到6M)
  13. exp在线计算机计算,Exp 数学表达式计算器算法分享
  14. Google Guava EventBus 消息发布-订阅异步调用使用
  15. 【LOJ6005】【网络流24题】最长递增子序列
  16. 【详细整理机房布线工艺,布出更快更漂亮的网线!】
  17. python画图大全_python画图教程
  18. 个人QQ免签,实现QQ收款
  19. 给程序员做几年老婆后的心得
  20. python decode ignore_python编解码,decode参数设置:ignore

热门文章

  1. python2.7安装requests linux_win与linux系统中python requests 安装
  2. scipy.stats.norm函数
  3. 烈火如歌手游找回服务器,烈火如歌手游服务器更换方法
  4. java jce 授权_java jce限制
  5. 无法回复桌面计算机,电脑开机后无法进入桌面怎么办?
  6. Elastic_Stack
  7. 第十一章 两个人的圣经
  8. wpf 带图像的文本框_注意WPF中带有图像PNG的DPI-图像比例奇怪或模糊
  9. angular11实现自定义语音播放器
  10. 电脑里本地连接没有了,不见了的解决方法