背景

在项目中经常被log4j的各种依赖冲突搞的焦头烂额,久病成良医啊,在这里记录一下我对log4j的理解与分析

log4j 与 log4j2

log4j2是log4j的升级版,二者互不兼容,据说log4j2带来了十倍的性能提升,所以基本上不再使用log4j1

那么log4j 1代的依赖长什么样呢?

<artifactId>log4j</artifactId>
<groupId>log4j</groupId>

log4j2的依赖

        <dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-api</artifactId>
<version>2.11.0</version>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-core</artifactId>
<version>2.11.0</version>
</dependency>

如果你的项目是web项目,最好加上

 <dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-web</artifactId>
<version>2.11.0</version>
</dependency>

slf4j

slf4j全称为Simple Logging Facade for JAVA,java简单日志门面。类似于Apache Common-Logging,是对不同日志框架提供的一个门面封装,可以在部署的时候不修改任何配置即可接入一种日志实现方案。但是,他在编译时静态绑定真正的Log库。使用SLF4J时,如果你需要使用某一种日志实现,那么你必须选择正确的SLF4J的jar包的集合(各种桥接包)。

slf4j静态绑定原理:SLF4J 会在编译时会绑定import org.slf4j.impl.StaticLoggerBinder; 该类里面实现对具体日志方案的绑定接入。任何一种基于slf4j 的实现都要有一个这个类。如:org.slf4j.slf4j-log4j12-1.5.6: 提供对 log4j 的一种适配实现。注意:如果有任意两个实现slf4j 的包同时出现,那么就可能出现问题。

slf4j 的依赖我们后面再分析,不同的实现对应不同的依赖

        <dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.25</version>
</dependency>

common-logging

common-logging是apache提供的一个通用的日志接口。用户可以自由选择第三方的日志组件作为具体实现,像log4j,或者jdk自带的logging, common-logging会通过动态查找的机制,在程序运行时自动找出真正使用的日志库。当然,common-logging内部有一个Simple logger的简单实现,但是功能很弱。所以使用common-logging,通常都是配合着log4j来使用。使用它的好处就是,代码依赖是common-logging而非log4j, 避免了和具体的日志方案直接耦合,在有必要时,可以更改日志实现的第三方库。

common-logging的依赖

<artifactId>commons-logging</artifactId>
<groupId>commons-logging</groupId>

slf4j 与 common-logging 比较

common-logging通过动态查找的机制,在程序运行时自动找出真正使用的日志库。由于它使用了ClassLoader寻找和载入底层的日志库, 导致了象OSGI这样的框架无法正常工作,因为OSGI的不同的插件使用自己的ClassLoader。 OSGI的这种机制保证了插件互相独立,然而却使Apache Common-Logging无法工作。

slf4j在编译时静态绑定真正的Log库,因此可以再OSGI中使用。另外,SLF4J 支持参数化的log字符串,避免了之前为了减少字符串拼接的性能损耗而不得不写的if(logger.isDebugEnable()),现在你可以直接写:logger.debug(“current user is: {}”, user)。拼装消息被推迟到了它能够确定是不是要显示这条消息的时候,但是获取参数的代价并没有幸免(可以通过lambda达到延迟生成参数的效果)。

    public static void delayDebug(Supplier<String> message) {
if (log.isDebugEnabled()) {
log.debug(message.get());
}
}

LogBack

Logback是由log4j创始人设计的又一个开源日记组件。logback当前分成三个模块:logback-core,logback- classic和logback-access。logback-core是其它两个模块的基础模块。logback-classic是log4j的一个 改良版本。此外logback-classic完整实现SLF4J API使你可以很方便地更换成其它日记系统如log4j或JDK14 Logging。logback-access访问模块与Servlet容器集成提供通过Http来访问日记的功能。

logback的依赖

<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
</dependency>

(听上去logback比log4j2更好,日后可以考虑切换到logback)

Log4j 与 LogBack 比较

LogBack作为一个通用可靠、快速灵活的日志框架,将作为Log4j的替代和SLF4J组成新的日志系统的完整实现。LOGBack声称具有极佳的性能,“ 某些关键操作,比如判定是否记录一条日志语句的操作,其性能得到了显著的提高。这个操作在LogBack中需要3纳秒,而在Log4J中则需要30纳秒。 LogBack创建记录器(logger)的速度也更快:13微秒,而在Log4J中需要23微秒。更重要的是,它获取已存在的记录器只需94纳秒,而 Log4J需要2234纳秒,时间减少到了1/23。跟JUL相比的性能提高也是显著的”。 另外,LOGBack的所有文档是全面免费提供的,不象Log4J那样只提供部分免费文档而需要用户去购买付费文档。

实战

我们在项目中使用的log4j2,引入了log4j2的依赖,并且强制干掉了其它日志组件的依赖,同时使用slf4j桥接其它日志组件到log4j2的实现

log4j2的依赖

        <dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-api</artifactId>
<version>2.11.0</version>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-core</artifactId>
<version>2.11.0</version>
</dependency>

如果你的项目是web项目,最好加上

         <dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-web</artifactId>
<version>2.11.0</version>
</dependency>

强制干掉了其它日志组件的依赖

借助于idea的maven-helper组件,以及maven的maven-enforcer-plugin插件进行验证



maven-enforcer-plugin插件

配置

            <plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-enforcer-plugin</artifactId>
<version>1.4.1</version>
<executions>
<execution>
<id>enforce</id>
<goals>
<goal>enforce</goal>
</goals>
<configuration>
<rules>
<bannedDependencies>
<!--是否检查传递性依赖(间接依赖)-->
<searchTransitive>true</searchTransitive>
<excludes>
<!--groupId[:artifactId][:version][:type][:scope][:classifier]-->
<exclude>com.google.collections:google-collections</exclude>
<exclude>log4j</exclude>
<exclude>slf4j-log4j12</exclude>
<exclude>commons-logging</exclude>
<exclude>ch.qos.logback</exclude>
<exclude>org.slf4j:slf4j-jdk14</exclude>
<exclude>org.slf4j:slf4j-simple</exclude>
<exclude>org.slf4j:slf4j-nop</exclude>
<exclude>org.slf4j:slf4j-jcl</exclude>
<!--把log4j的日志都转发到slf4j,我们最终使用的是log4j实现,去掉这行的话slf4j就构成循环了-->
<exclude>org.apache.logging.log4j:log4j-to-slf4j</exclude>
</excludes>
<message>Must not use google-collections, use guava</message>
</bannedDependencies>
</rules>
</configuration>
</execution>
</executions>
</plugin>

slf4j桥接其它日志组件

        <!--commons logging到slf4j的实现-->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>jcl-over-slf4j</artifactId>
<version>1.7.25</version>
</dependency>
<!--log4j1到slf4j的实现-->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>log4j-over-slf4j</artifactId>
<version>1.7.25</version>
</dependency>
<!--java util logging-->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>jul-to-slf4j</artifactId>
<version>1.7.25</version>
</dependency>

slf4j到log4j2的实现

        <dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-slf4j-impl</artifactId>
<version>2.11.0</version>
</dependency>

slf4j与其它各种日志组件的桥接图

应用代码中使用slf4j接口,接入具体实现的方法

应用代码中使用别的日志接口,转成slf4j的方法

日志组件相关历史

Java 界里有许多实现日志功能的工具,最早得到广泛使用的是 log4j,许多应用程序的日志部分都交给了 log4j,不过作为组件开发者,他们希望自己的组件不要紧紧依赖某一个工具,毕竟在同一个时候还有很多其他很多日志工具,假如一个应用程序用到了两个组件,恰好两个组件使用不同的日志工具,那么应用程序就会有两份日志输出了。

为了解决这个问题,Apache Commons Logging (之前叫 Jakarta Commons Logging,JCL)粉墨登场,JCL 只提供 log 接口,具体的实现则在运行时动态寻找。这样一来组件开发者只需要针对 JCL 接口开发,而调用组件的应用程序则可以在运行时搭配自己喜好的日志实践工具。

所以即使到现在你仍会看到很多程序应用 JCL + log4j 这种搭配,不过当程序规模越来越庞大时,JCL的动态绑定并不是总能成功,具体原因大家可以 Google 一下,这里就不再赘述了。解决方法之一就是在程序部署时静态绑定指定的日志工具,这就是 SLF4J 产生的原因。

跟 JCL 一样,SLF4J 也是只提供 log 接口,具体的实现是在打包应用程序时所放入的绑定器(名字为 slf4j-XXX-version.jar)来决定,XXX 可以是 log4j12, jdk14, jcl, nop 等,他们实现了跟具体日志工具(比如 log4j)的绑定及代理工作。举个例子:如果一个程序希望用 log4j 日志工具,那么程序只需针对 slf4j-api 接口编程,然后在打包时再放入 slf4j-log4j12-version.jar 和 log4j.jar 就可以了。

现在还有一个问题,假如你正在开发应用程序所调用的组件当中已经使用了 JCL 的,还有一些组建可能直接调用了 java.util.logging,这时你需要一个桥接器(名字为 XXX-over-slf4j.jar)把他们的日志输出重定向到 SLF4J,所谓的桥接器就是一个假的日志实现工具,比如当你把 jcl-over-slf4j.jar 放到 CLASS_PATH 时,即使某个组件原本是通过 JCL 输出日志的,现在却会被 jcl-over-slf4j “骗到”SLF4J 里,然后 SLF4J 又会根据绑定器把日志交给具体的日志实现工具。过程如下

Component

|

| log to Apache Commons Logging

V

jcl-over-slf4j.jar --- (redirect) ---> SLF4j ---> slf4j-log4j12-version.jar ---> log4j.jar ---> 输出日志

看到上面的流程图可能会发现一个有趣的问题,假如在 CLASS_PATH 里同时放置 log4j-over-slf4j.jar 和 slf4j-log4j12-version.jar 会发生什么情况呢?没错,日志会被踢来踢去,最终进入死循环。

所以使用 SLF4J 的比较典型搭配就是把 slf4j-api、JCL 桥接器、java.util.logging(JUL)桥接器、log4j 绑定器、log4j 这5个 jar 放置在 CLASS_PATH 里。

不过并不是所有APP容器都是使用 log4j 的,比如 Google AppEngine 它使用的是 java.util.logging(JUL),这时应用 SLF4J 的搭配就变成 slf4j-api、JCL桥接器、logj4桥接器、JUL绑定器这4个 jar 放置在 WEB-INF/lib 里。

一组比较ok的log依赖,使用log4j2

        <dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-api</artifactId>
<version>2.11.0</version>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-web</artifactId>
<version>2.11.0</version>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-core</artifactId>
<version>2.11.0</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.25</version>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-slf4j-impl</artifactId>
<version>2.11.0</version>
</dependency>
<!--commons logging到slf4j的实现-->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>jcl-over-slf4j</artifactId>
<version>1.7.25</version>
</dependency>
<!--log4j1到slf4j的实现-->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>log4j-over-slf4j</artifactId>
<version>1.7.25</version>
</dependency>
<!--java util logging-->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>jul-to-slf4j</artifactId>
<version>1.7.25</version>
</dependency>

一组比较ok的log4j2的配置

<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="info">
<Properties>
<Property name="PRO_NAME">review-api</Property>
<Property name="LOG_HOME">/home/work/log/${PRO_NAME}</Property>
<Property name="PID">????</Property>
<Property name="LOG_PATTERN">
%clr{%d{yyyy-MM-dd HH:mm:ss.SSS}}{faint} %clr{%5p} %clr{${sys:PID}}{magenta}
%clr{---}{faint} %clr{[%t]}{faint} %clr{%c{1.}}{cyan} %clr{:}{faint} %m%n%xwEx
</Property>
</Properties> <Appenders> <Console name="Console" target="SYSTEM_OUT">
<PatternLayout>
<!--<Pattern>${LOG_PATTERN}</Pattern>-->
<Pattern>%d %p %c{1.} [%t] - %m%n</Pattern>
</PatternLayout>
</Console> <RollingRandomAccessFile name="rootFile"
fileName="${LOG_HOME}/${PRO_NAME}-rootFile.log"
filePattern="${LOG_HOME}/${PRO_NAME}-rootFile-%d{yyyy-MM-dd}.log.gz"
append="true">
<PatternLayout>
<Pattern>%d %p %c{1.} [%t] - %m%n</Pattern>
</PatternLayout> <Policies>
<!--在启动时-->
<OnStartupTriggeringPolicy/>
<!--或在最小时间单位变动时-->
<TimeBasedTriggeringPolicy interval="1" modulate="true"/>
</Policies>
<!--启动以下策略-->
<DefaultRolloverStrategy max="10">
<!--maxDepth:文件夹深度限制为2-->
<Delete basePath="${LOG_HOME}" maxDepth="2">
<IfFileName glob="**.log.gz"/>
<!--日志保存7天-->
<IfLastModified age="7d"/>
</Delete>
</DefaultRolloverStrategy>
</RollingRandomAccessFile> <RollingRandomAccessFile name="file"
fileName="${LOG_HOME}/${PRO_NAME}.log"
filePattern="${LOG_HOME}/${PRO_NAME}-%d{yyyy-MM-dd}.log.gz"
immediateFlush="true" append="true">
<PatternLayout>
<Pattern>%d %p %c{1.} [%t] - %m%n</Pattern>
</PatternLayout>
<Policies>
<TimeBasedTriggeringPolicy interval="1" modulate="true"/>
</Policies>
</RollingRandomAccessFile> <RollingRandomAccessFile name="error"
fileName="${LOG_HOME}/${PRO_NAME}-error.log"
filePattern="${LOG_HOME}/${PRO_NAME}-error-%d{yyyy-MM-dd}.log.gz"
immediateFlush="true" append="true">
<PatternLayout>
<Pattern>%d %p %c{1.} [%t] - %m%n</Pattern>
</PatternLayout>
<Policies>
<TimeBasedTriggeringPolicy interval="1" modulate="true"/>
</Policies>
</RollingRandomAccessFile> <ReviewLogAppender name="SELF_ELK" project-name="${PRO_NAME}"/> </Appenders> <Loggers> <logger name="com.company" level="error" additivity="false">
<AppenderRef ref="error" level="error"/>
</logger> <logger name="com.company.middle" level="${log4j_level}" additivity="false">
<AppenderRef ref="file"/>
<AppenderRef ref="SELF_ELK"/>
<AppenderRef ref="Console"/>
</logger> <logger name="com.company.browser" level="${log4j_level}" additivity="false">
<AppenderRef ref="file"/>
<AppenderRef ref="SELF_ELK"/>
<AppenderRef ref="Console"/>
</logger> <Root level="info">
<AppenderRef ref="rootFile"/>
</Root>
</Loggers>
</Configuration>

其中关于SELF_ELK的实现,我们将所有日志打到kibana上一份,方便我们查看

/**
*
* 收集所有log日志,打到elk上
*/
@Plugin(name = "ReviewLogAppender", category = "Core", elementType = "appender", printObject = true)
public class ReviewLogAppender extends AbstractAppender { private String project; public ReviewLogAppender(String name, Filter filter,
Layout<? extends Serializable> layout, String project) {
super(name, filter, layout);
this.project = project;
} @PluginFactory
public static ReviewLogAppender create(@PluginAttribute("name") String name,
@PluginElement("Layout") Layout<? extends Serializable> layout,
@PluginElement("Filter") final Filter filter,
@PluginAttribute("project-name") String project) { if (name == null) {
return null;
}
if (layout == null) {
layout = PatternLayout.createDefaultLayout();
} return new ReviewLogAppender(name, filter, layout, project);
} @Override
public void append(LogEvent logEvent) {
if (!EnvVar.isProduction()) {
return;
} MiddleReviewDebugLog middleReviewDebugLog = new MiddleReviewDebugLog(); String logBody = logEvent.getMessage().getFormattedMessage();
middleReviewDebugLog.setFrom(project);
middleReviewDebugLog.setLevel(logEvent.getLevel().toString());
middleReviewDebugLog.setTimestamp(System.currentTimeMillis());
middleReviewDebugLog.setLogbody(logBody);
middleReviewDebugLog.setServerIp(IPUtils.getHostIp()); if (logEvent.getLevel().equals(Level.ERROR)) {
StringBuilder stringBuilder = new StringBuilder();
final Throwable thrown = logEvent.getThrown();
if (thrown != null) {
stringBuilder.append("errorMsg:").append(thrown.getMessage()).append("\nstackTrace:").append(ExceptionUtil.stacktraceToString(thrown));
}
middleReviewDebugLog.setExt(stringBuilder.toString());
} LCS_LOGGER.write(TOPIC_AND_TEAM, middleReviewDebugLog);
}
}

参考

java日志组件介绍(common-logging,log4j,slf4j,logback )

Log4j实战,依赖分析的更多相关文章

  1. Maven依赖分析

    背景 昨天帮一位同事排查了一个依赖冲突的问题.问题的现象就是在IntelliJ IDEA运行项目正常,但是打包(Maven assembly jar)之后传到服务器运行失败,报错:Caused by: ...

  2. Salesforce学习之路-developer篇(五)一文读懂Aura原理及实战案例分析

    1. 什么是Lightning Component框架? Lightning Component框架是一个UI框架,用于为移动和台式设备开发Web应用程序.这是一个单页面Web应用框架,用于为Ligh ...

  3. SpringBoot Log4j 安全漏洞分析及解决方案

    一.序言 SpringBoot作为Java基础框架大行其道,前不久爆发出Log4j安全漏洞,大众更多关心Log4j的危害是多么严重,然而鲜有关心SpringBoot这一底层框架的安全性问题,换而言之, ...

  4. AMD and CMD are dead之KMDjs内核之依赖分析

    有人说js中有三座大三:this.原型链和scope tree,搞懂了他们就算是js成人礼.当然还有其他不同看法的js成人礼,如熟悉js的:OOP.AP.FP.DOP.AOP.当然还听说一种最牛B的j ...

  5. 揪出“凶手”——实战WinDbg分析电脑蓝屏原因

    http://www.appinn.com/blue-screen-search-code/ 蓝屏代码查询器 – 找出蓝屏的元凶 11 文章标签: windows / 系统 / 蓝屏. 蓝屏代码查询器 ...

  6. java 日志体系(四)log4j 源码分析

    java 日志体系(四)log4j 源码分析 logback.log4j2.jul 都是在 log4j 的基础上扩展的,其实现的逻辑都差不多,下面以 log4j 为例剖析一下日志框架的基本组件. 一. ...

  7. 高德JS依赖分析工程及关键原理

    一.背景 高德 App 进行 Bundle 化后,由于业务的复杂性,Bundle 的数量非常多.而这带来了一个新的问题——Bundle 之间的依赖关系错综复杂,需要进行管控,使 Bundle 之间的依 ...

  8. 高德APP全链路源码依赖分析工程

    一.背景 高德 App 经过多年的发展,其代码量已达到数百万行级别,支撑了高德地图复杂的业务功能.但与此同时,随着团队的扩张和业务的复杂化,越来越碎片化的代码以及代码之间复杂的依赖关系带来诸多维护性问 ...

  9. JetBrains官博:将从IntelliJ平台移除Log4j的依赖

    今早,DD注意到JetBrains在官方博客发文宣布要将log4j从IntelliJ平台移除了,该变化将在2022.1版本发布. 从博文看,本次移除log4j的漏洞,并非担心log4j2的漏洞问题,因 ...

随机推荐

  1. 使用MyQR模块生成二维码

    一.介绍 MyQR 模块是 python 的一个外部库,能够将文本或网址转为二维码,扫了之后就能查看文本或跳转到相应网站. 它是一个外部库,需要导入: pip install -i https://p ...

  2. 【.NET 与树莓派】数模转换

    在开始之前,需要说明一对很耳熟的概念--数字信号 & 模拟信号. 这些概念的理论有些复杂,你如果相当有兴趣,可以找来有关的文献细细研究:若你不关心那是啥只想知道咋用,那就通俗但不庸俗地理解一下 ...

  3. PHP学员分享:126个常用的正则表达式分享

    PHP学员分享:126个常用的正则表达式分享 电子邮件:/\w+([-+.']\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*/变量:/[a-zA-Z_\x7f-\xff][a-z ...

  4. hdu1839 二分最短路

    题意:       给你n个城市,m条双向边,每条边有自己的长度和最大运输量,让你找到一条时间小于等于T的运输能力最大的那条路... 思路:       刚开始以为是费用流呢,后来发现根本不是,因为根 ...

  5. 路由器逆向分析------binwalk工具的安装

    本文博客链接:http://blog.csdn.net/qq1084283172/article/details/65441110 一.binwalk工具运行支持的平台 binwalk工具安装支持的平 ...

  6. hdu3018 一笔画问题

    题意:       给你一幅画,这幅画由点和边构成,问你最少几笔能把这幅画画完. 思路:      这个题目的结论比较巧妙,首先我们考虑下,如果给的图是欧拉图,或者是条欧拉回路,那么我们一笔就搞定了, ...

  7. hdu4888 最大流(构造矩阵)

    题意:       让你构造一个矩阵,满足每一行的和,和每一列的和都等于他给的,还要判断答案是否唯一,还有一点就是矩阵内所有的数字都是[0,k]范围的. 思路:       这个题目看完就让我想起了h ...

  8. 0803-PyTorch的Debug指南

    0803-PyTorch的Debug指南 目录 一.ipdb 介绍 二.ipdb 的使用 三.在 PyTorch 中 Debug 四. 通过PyTorch实现项目中容易遇到的问题 五.第八章总结 py ...

  9. Redis数据结构—链表与字典的结构

    目录 Redis数据结构-链表与字典的结构 链表 Redis链表节点的结构 Redis链表的表示 Redis链表用在哪 字典 Redis字典结构总览 Redis字典结构分解 Redis字典的使用 Re ...

  10. Day003 彻底搞懂++、--

    彻底搞懂++.-- ++.--都是一目运算符 b=a++(把a的值先赋给b,a再自增1) b=++a(a先自增1,再赋给b) 通过一个例子理解 int a=1; int b=a++; int c=++ ...