解决Slf4j日志不打印问题

2023-05-16

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

日常站点状态巡检时发现有异常日志,定位到日志位置,看其上线文自定义输出的日志时却发现,自己加的日志都没输出。排查了一下初步定位到,这个类中日志输出使用的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 {
    @Override
    public 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

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

解决Slf4j日志不打印问题 的相关文章

随机推荐

  • centos6离线升级至centos7

    1 说明 本文用于离线状态下centos跨大版本升级 xff08 centos6升级至centos7 xff09 xff0c 系统环境为刚安装的纯净环境 xff0c 升级前没有安装外部源的包 centos跨版本升级只能升级到centos7
  • RHEL8.0升级包后图形化界面进入异常,提示 “Oh no! Something has gone wrong.Logout!”

    1 问题背景 近期遇到一个问题 xff0c RHEL8 0因为扫出漏洞 xff0c 根据漏洞做了更新 xff0c 重启后 xff0c 发现图形化界面进入异常 xff0c 提示 Oh no Something has gone wrong L
  • nfs关于用户写权限的配置

    1 说明 nfs是很常用的网络共享盘方式 xff0c 访问客户端数量多了之后 xff0c 为确保安全 xff0c 会做权限控制 xff0c 一般都是放开读权限 xff0c 而控制写权限 本文将在几种场景下说明 nfs 关于用户写权限的配置
  • root用户和root用户组在权限上的区别

    1 root 用户 在系统中是超级管理员 xff0c uid 61 0 xff0c 拥有最高权限 xff0c 除了系统特殊文件 有 i a 权限的文件和目录 文件系统问题等情况 xff0c 能够操作任何文件和服务进程 2 root 用户组
  • RedHat免费订阅账号注册方式

    免费订阅账号来自于开发者账号的免费订阅功能 xff0c 可以阅读基本上所有产品的文章 xff0c 也可以下载官方所更新的最新的补丁包 xff0c 对于有漏洞修复需求来说比较实用 官方站点 xff1a developers redhat co
  • 使用 lvreduce 对逻辑卷做减容缩容操作及注意要点

    1 背景 Linux 系统运维管理过程中 xff0c 有时候会遇到某个逻辑卷所分配的容量较大 xff0c 实际使用量又不多 xff0c 导致剩余较多浪费了 此时如果有其他的逻辑卷需要扩容 xff0c 出于资源利用最大化的考虑 xff0c 会
  • ps aux 进程状态为 I (大写i)

    系统使用 ps aux 查看进程时 xff0c 发现有状态为 I 大写i 的进程 xff0c 暂无发现由于这个状态导致的问题 进程状态 I xff0c 表示task idle xff0c 即空闲的任务 xff08 进程 xff09 xff0
  • android网络框架OkHttp之get请求(源码初识)

    转载请标明出处 xff1a http blog csdn net iamzgx article details 51477877 xff1b 本文出自 iGoach的博客 概括 OkHttp现在很火呀 于是上个星期就一直在学习OkHttp框
  • 学会Retrofit+OkHttp+RxAndroid三剑客的使用,让自己紧跟Android潮流的步伐

    转载请标明出处 xff1a http blog csdn net iamzgx article details 51607387 本文出自 iGoach的博客 概括 在上一篇博客android网络框架OkHttp之get请求 xff08 源
  • 手动缓存Retrofit+OkHttp响应体,不再局限于Get请求缓存

    转载请标明出处 xff1a http blog csdn net iamzgx article details 51764848 本文出自 iGoach的博客 概括 这篇博客是接着上一篇博客学会Retrofit 43 OkHttp 43 R
  • 哪个才是Android工程构建时的gradle版本?gradle-wrapper.properties还是build.gradle的buildscript说了算?

    先说谜底 xff0c gradle wrapper properties说了算 xff08 1 xff09 build gradle的buildscript里的buildscript dependencies的com android too
  • cocos2d-js之入门篇

    转载请标明出处 xff1a http blog csdn net iamzgx article details 54232599 本文出自 iGoach的博客 cocos2d js xff0c 至今日 xff0c 也马马虎虎算接触了一个星期
  • 微信小程序-入门篇

    转载请标明出处 xff1a http blog csdn net iamzgx article details 72615506 本文出自 iGoach的博客 前言 2017年1月9日 xff0c 微信小程序发布 这也标志着FaceBook
  • 使用OkHttp上传图片

    简介 上传图片是一个APP的常见功能 xff0c 可以是通过OOS上传到阿里云 xff0c 也可以直接上传到Server后台 xff0c OOS有提供相应的SDK xff0c 此处忽略 下面通过OkHttp来实现图片的上传 代码 直接上代码
  • 简单说说如何把json或者txt文件转换为db

    前言 最近在Github上找到一个中国城市的json文件 xff0c 虽然也有db文件 xff0c 但是想通过这个json文件生成自己的数据库表 下面就简单来转换成自己的数据库 准备json 在Github找到一个下面json表 34 sp
  • 2018年7月面试记录

    MRCM 聊天缓存如何实现的聊天重发如何实现如何保持长连接心跳简述TCP协议写一个自己最擅长的设计模式 ZHYT finish会立马销毁activity吗view的生命周期activity结束了HandlerQueue如何处理Handler
  • Android面向AOP之AspectJ的使用篇

    前言 AOP xff0c 它不是一门新语言 xff0c 是一种面向切面的思想 它主要的作用是把一些具有相同属性或者相同功能的代码抽离出来形成一个切面 xff0c 从而实现面向切面编程 xff01 而AspectJ就是基于Java语言实现AO
  • 仿拉勾首页之Behavior的学习

    前言 最近在找工作 xff0c 于是打开拉勾 xff0c 看了看首页 xff0c 交互做的还是不错的 先来看看拉勾效果 然后最终实现的效果 布局是图片直接用 xff0c 所以会失真 实现思路 首先这个是一个MD的效果 xff0c 可以使用自
  • Java中Collections类方法常用合集

    目录 1 Collections sort list 2 Collections reverse list 3 Collections shuffle list 4 Collections swap List list int i int
  • 解决Slf4j日志不打印问题

    日志不打印的问题 xff0c 很让人头疼 xff0c 也是我们经常遇到的问题 日常站点状态巡检时发现有异常日志 xff0c 定位到日志位置 xff0c 看其上线文自定义输出的日志时却发现 xff0c 自己加的日志都没输出 排查了一下初步定位