彻底解决SLF4J的日志冲突的问题

今天公司同事上线时发现,有的机器打印了日志,而有的机器则一条日志也没有打。以往都是没有问题的。

因此猜测是这次开发间接引入新的日志jar包,日志冲突导致未打印。

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

先上一张关系图:SLF4J框架、各种具体日志实现以及相应桥接包的关系图

彻底解决SLF4J的日志冲突的问题

一、起因

由于线上系统要接入很多中间件,因此系统中会有各种各样的日志打印形式(例如:log4j2、JCL、logback等等)。

为了能整合所有日志并进行统一打印,最常用的就是SLF4J框架。

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

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

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

彻底解决SLF4J的日志冲突的问题

彻底解决SLF4J的日志冲突的问题

彻底解决SLF4J的日志冲突的问题

二、为什么只有部分机器打印

因为每个SLF4J的桥接包都有org.slf4j.impl.StaticLoggerBinder

SLF4J则会 随机选择一个使用。当选择的跟系统配置的一样时就可以打印日志,否则就打印不出。

彻底解决SLF4J的日志冲突的问题

三、快速感知到多种SLF4J桥接包

如上图所示findPossibleStaticLoggerBinderPathSet方法,当有多个日志桥接包时会返回一个Set集合且提示一条信息。

由于这个信息提示并不强烈,不易感知。我们可以根据这一点, 使用反射来获取到系统中实际的桥接包数量,并做自定义的提示

1、实现spring的BeanFactoryPostProcessor,并将其交由spring管理。保证系统启动后,自动进行日志冲突校验
2、使用反射获取LoggerFactory的实例以及findPossibleStaticLoggerBinderPathSet方法的返回结果
3、根据桥接包数量判断是否异常,进行自定义报警
4、根据报警信息,进行排包


/**
 * 日志jar包冲突校验
 */
public class LogJarConflictCheck implements BeanFactoryPostProcessor {
    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory configurableListableBeanFactory) throws BeansException {
        try {
            Class loggerFactoryClazz = LoggerFactory.class;
            Constructor constructor = loggerFactoryClazz.getDeclaredConstructor();
            constructor.setAccessible(true);
            LoggerFactory instance = constructor.newInstance();
            Method method = loggerFactoryClazz.getDeclaredMethod("findPossibleStaticLoggerBinderPathSet");
            // 强制进入
            method.setAccessible(true);
            Set staticLoggerBinderPathSet = (Set)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 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();

        //TODO 使用自定义报警通知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,就不会打入包中。
从而实现需要的包以我们声明的为准,需要排掉的包也不会被间接依赖影响


  1.7.7
    1.2.3
    1.2.17
    2.3
    1.2

        org.slf4j
        slf4j-api
        ${slf4j.version}

        org.apache.logging.log4j
        log4j-core
        ${log4j2.version}

        org.apache.logging.log4j
        log4j-api
        ${log4j2.version}

        log4j
        log4j
        ${log4j.version}
        provided

        ch.qos.logback
        logback-classic
        ${logback.version}
        provided

        commons-logging
        commons-logging
        ${jcl.version}
        provided

        org.apache.logging.log4j
        log4j-to-slf4j
        ${log4j2.version}
        provided

        org.slf4j
        log4j-over-slf4j
        ${slf4j.version}

        org.slf4j
        jcl-over-slf4j
        ${slf4j.version}

        org.slf4j
        jul-to-slf4j
        ${slf4j.version}

        org.apache.logging.log4j
        log4j-slf4j-impl
        ${log4j2.version}

        org.slf4j
        slf4j-log4j12
        ${slf4j.version}
        provided

        org.slf4j
        slf4j-jdk14
        ${slf4j.version}
        provided

        org.slf4j
        slf4j-jcl
        ${slf4j.version}
        provided

总结

第三步的方案:启动时感知系统是否存在日志jar包冲突,冲突后手动排包

第四步的方案:一次声明所需的所有日志jar包配置,无需在担心冲突问题

Original: https://www.cnblogs.com/mlwy/p/15511382.html
Author: zy苦行僧
Title: 彻底解决SLF4J的日志冲突的问题

原创文章受到原创版权保护。转载请注明出处:https://www.johngo689.com/576372/

转载文章受原作者版权保护。转载请注明原作者出处!

(0)

大家都在看

  • Spark学习(2) RDD编程

    RDD(Resilient Distributed Dataset)叫做分布式数据集,是Spark中最基本的数据抽象,它代表一个不可变、可分区、弹性、里面的元素可并行计算的集合 R…

    数据库 2023年6月16日
    0114
  • DistSQL 深度解析:打造动态化的分布式数据库

    一、背景 自 ShardingSphere 5.0.0 版本发布以来,DistSQL 为 ShardingSphere 生态带来了强大的动态管理能力,通过 DistSQL,用户可以…

    数据库 2023年6月16日
    076
  • MySQL 用 limit 为什么会影响性能?

    1.前言 首先说明一下MySQL的版本: mysql> select version(); +———–+ | version() | +———–+…

    数据库 2023年5月24日
    076
  • 正则表达式

    正则表达式:REGEXP,REGular EXPression。正则表达式分为两类: Basic REGEXP(基本正则表达式 Extended REGEXP(扩展正则表达式) 元…

    数据库 2023年6月15日
    0143
  • 记一次血淋淋的MySQL崩溃修复案例

    摘要:今天给大家带来一篇MySQL数据库崩溃的修复案例 问题描述 研究MySQL源代码,调试并压测MySQL源代码时,MySQL崩溃了!问题是它竟然崩溃了!而且还损坏了InnoDB…

    数据库 2023年5月24日
    0121
  • Linux常用命令总结(二)

    1.Netstat 命令 用于显示各种网络相关信息,如网络连接,路由表,接口状态等待。 例如 统计IP110.120.119.XXX的连接数: netstat | grep 110…

    数据库 2023年6月16日
    089
  • Mybatis-Plus 实现乐观锁

    是指在读取一行数据时,记下它的版本号、最近修改的时间戳或校验和。然后,你可以在修改记录之前检查版本有没有发生变化。 适用场景 适用于读多写少的场景,乐观锁相信事务之间的数据竞争概率…

    数据库 2023年6月6日
    096
  • Mysql8设置允许root用户远程访问

    按照mysql8.0以前的方法修改报错 mysql> grant all privileges on *.* to ‘root’@’%’ identified by ‘PAS…

    数据库 2023年6月6日
    0117
  • JUC自定义线程池练习

    JUC自定义线程池练习 首先上面该线程池的大致流程 自定义阻塞队列 首先定义一个双向的队列和锁一定两个等待的condition 本类用lock来控制多线程下的流程执行 take和p…

    数据库 2023年6月11日
    0103
  • HA: FORENSICS靶机练习

    ubuntu拿到手,没有恢复模式,不好绕密码,仿真软件又会更改所有用户的密码,怕影响后续操作,先不采用,先试试用john跑一下看看能不能跑出一两个来。 刚好跑出来一个,用户 &lt…

    数据库 2023年6月11日
    095
  • DDD(Domain Driver Design)领域驱动模型

    Domain Primitive(DP) DP概念DP 是 DDD 中的一个基础概念,是 DDD 中可以执行的一个最小单元,最直接的体现是,将业务相关的参数定义在一个特定的领域中(…

    数据库 2023年6月6日
    0133
  • 谁再说学不会 MySQL 数据库,就把这个给他扔过去!

    大家好,我是民工哥。 又是新的一年奋斗路的开启,相信有不少人农历新年之后,肯定会有所变动(跳槽加薪少不了)。所以,我把往期推送过的MySQL技术文章做了一个相关的整理,基础不好的可…

    数据库 2023年5月24日
    081
  • 类加载器ClassLoader

    1.双亲委派模型 java是根据双亲委派模型的加载类的,当一个类加载器加载类时,会先尝试委托给父类加载器去加载,直到到达启动类加载器顶层若加载不了,则再让子类加载器去加载直到类成功…

    数据库 2023年6月16日
    0110
  • mysql基本数据类型

    概述 要想学好mysql,了解其支持的基本数据类型以及内部原理是极为重要的,只有这样,我们才能根据不同的业务要求来选择不同的数据类型,实现最佳的存储效果和查询性能,因而本文就着重总…

    数据库 2023年5月24日
    0134
  • == 和 equals 的区别

    1. == 概念:==是一个比较运算符 == 既可以判断基本类型,又可以判断引用类型 ==判断基本类型时,判断的是值是否相等。示例:int i = 10; ==判断引用类型时,判断…

    数据库 2023年6月11日
    099
  • Redis-缓存和数据库一致性问题

    三种策略 Cache Aside 只读缓存模式,即读操作命中缓存直接返回,未命中从后端数据库加载到缓存再返回。写操作直接更新数据库,并删除缓存。👍一切以后端数据库为准,最常用的方式…

    数据库 2023年6月11日
    0100
亲爱的 Coder【最近整理,可免费获取】👉 最新必读书单  | 👏 面试题下载  | 🌎 免费的AI知识星球