点击上方蓝字,关注凌云时刻微信公众号

凌云时刻 · 技术

导读:随着互联网和大数据的蓬勃发展,分布式日志系统以及日志分析系统得到了广泛地应用。目前,几乎在所有应用程序中,都会用到各种各样的日志框架来记录程序的运行信息。鉴于此,工程师十分有必要熟悉主流的日志记录框架。

作者 | 书澜
来源 | 凌云时刻(微信号:linuxpk)

前言

日志的有无虽然不影响应用程序的运行结果,但是没有日志的应用程序是不完整的,甚至可以说是有缺陷的。优雅的日志系统可以记录操作轨迹,监控系统运行状况以及回溯系统故障。在工作中,部分工程师对主流的日志框架仍然是一知半解,日常应用还停留在复制粘贴的层面,因此写作本文,希望对读者有所帮助。

本系列文章分为上、中、下三篇,将全面系统地介绍 Java 日志框架,主要内容有:

  • 日志的意义与价值

  • Java 日志框架进化史

  • 日志门面与日志系统

  • 日志框架的使用选择

  • 日志使用中需要遵循的规范及注意事项

  • 日志使用示例及常见报错

本篇为上篇,将详细解读主流 Java 日志框架

日志的意义与价值

  • 在开发调试阶段: 日志系统有助于更快的定位问题。

  • 在应用运维阶段: 日志系统有助于记录大部分的异常信息,通过收集日志信息可以对系统的运行状态进行实时监控预警。

  • 在数据分析阶段: 日志中通常包含大量的用户数据,包括点击行为、兴趣偏好等,基于这些数据可以对用户进行“画像”,进而助力战略决策。随着大数据技术日渐成熟,海量日志分析已经在互联网公司得到广泛应用。

Java 日志框架进化史

在开发过程中,工程师不得不面对一个很现实的问题:Java “混乱”的日志框架体系。为什么说“混乱”呢?原因在于早期 Java 日志框架没有制定统一的标准,使得很多应用程序会同时使用多种日志框架。Java 日志框架的发展历程大致可分为以下几个阶段:

  Log4j

Apache Log4j 是一种基于 Java 的日志记录工具,它是 Apache 软件基金会的一个项目。在 jdk1.3 之前,还没有现成的日志框架,Java 工程师只能使用原始的 System.out.println(), System.err.println() 或者 e.printStackTrace()。通过把 debug 日志写到 StdOut 流,错误日志写到 ErrOut 流,以此记录应用程序的运行状态。这种原始的日志记录方式缺陷明显,不仅无法实现定制化,而且日志的输出粒度不够细。鉴于此,1999 年,大牛 Ceki Gülcü 创建了 Log4j 项目,并几乎成为了 Java 日志框架的实际标准。

  JUL

Log4j 作为 Apache 基金会的一员,Apache 希望将 Log4j 引入 jdk,不过被 sun 公司拒绝了。随后,sun 模仿 Log4j,在 jdk1.4 中引入了 JUL(java.util.logging)。

  Commons Logging

为了解耦日志接口与实现,2002 年 Apache 推出了 JCL(Jakarta Commons Logging),也就是 Commons Logging。Commons Logging 定义了一套日志接口,具体实现则由 Log4j 或 JUL 来完成。Commons Logging 基于动态绑定来实现日志的记录,在使用时只需要用它定义的接口编码即可,程序运行时会使用 ClassLoader 寻找和载入底层的日志库,因此可以自由选择由 log4j 或 JUL 来实现日志功能。

  Slf4j&Logback

大牛 Ceki Gülcü 与 Apache 基金会关于 Commons-Logging 制定的标准存在分歧,后来,Ceki Gülcü 离开 Apache 并先后创建了 Slf4j 和 Logback 两个项目。Slf4j 是一个日志门面,只提供接口,可以支持 Logback、JUL、log4j 等日志实现,Logback 提供具体的实现,它相较于 log4j 有更快的执行速度和更完善的功能。

  Log4j 2

为了维护在 Java 日志江湖的地位,防止 JCL、Log4j 被 Slf4j、Logback 组合取代 ,2014 年 Apache 推出了 Log4j 2。Log4j 2 与 log4j 不兼容,经过大量深度优化,其性能显著提升。

日志门面与日志系统

在上文中已经提及,目前常用的日志框架有 Log4j,Log4j 2,Commons Logging,Slf4j,Logback,JUL。这些日志框架可以分为两种类型:门面日志和日志系统。

  • 日志门面:只提供日志相关的接口定义,即相应的 API,而不提供具体的接口实现。日志门面在使用时,可以动态或者静态地指定具体的日志框架实现,解除了接口和实现的耦合,使用户可以灵活地选择日志的具体实现框架。

  • 日志系统:只提供日志相关的接口定义,即相应的 API,而不提供具体的接口实现。日志门面在使用时,可以动态或者静态地指定具体的日志框架实现,解除了接口和实现的耦合,使用户可以灵活地选择日志的具体实现框架。

如上图所示,Commons-Logging 和 Slf4j 属于日志门面框架,Log4j、Logback、和 JUL 则属于具体的日志系统框架。阅读至此,想必读者一定疑惑——为何如此设计?为何不简单一点?为何分成了门面和实现?

在回答上述问题之前,我们先一起简单回顾一下门面模式(软件设计模式的一种,也称外观模式、正面模式)。门面模式的核心为:外部客户端与一个子系统的通信,必须通过一个统一的外观对象进行,使得子系统更易于使用,其本质就是为子系统中的一组接口提供一个统一的高层接口,如下图所示:

门面模式的核心是门面对象 Facade,它有如下几个特点:

  • 知道所有子模块的责任和功能

  • 将客户端发来的请求委派到子系统中,本身没有具体业务逻辑

  • 不参与子系统内业务逻辑的实现

了解过门面模式的基本信息,再回到最初的问题——为什么日志框架要使用门面模式呢?其实答案很简单,在工程开发中常遇到这样的场景:

  1. 我们自己的系统中使用了 Logback 这个日志系统

  2. 我们的系统使用了 A.jar,A.jar 中使用的日志系统为 Log4j

  3. 我们的系统又使用了 B.jar,而 B.jar 中使用的日志系统为 JUL

在上述场景中,我们的系统需要同时支持并维护 Logback、Log4j、JUL 三种日志框架,其繁琐程度不言而喻。为了解决这个问题,可以引入一个适配层,由适配层决定具体使用哪一种日志系统,应用程序中的调用者只管打印日志,而不必关心日志是如何被打印出来的,如此,问题迎刃而解。显然,Slf4j 和 Commons-Logging 就是这种适配层,而 JUL、Log4j 和 Logback 等就是打印日志的具体实现。换言之,日志门面(适配层)只需要提供日志的接口,日志系统的具体实现则交由其它日志框架,这样就避免了需要维护复杂日志系统的问题。

避免环形依赖

Slf4j 的作者 Ceki Gülcü 当年因为觉得 Commons-Logging 的 API 设计的不好,性能也不够高,因而设计了 Slf4j。而他为了 Slf4j 能够兼容各种类型的日志系统实现,还设计了相当多的 adapter 和 bridge 来连接,如下图所示:

这些 adapter 和 bridge 在此就不做详细介绍,读者需要时可自行查阅上图找到对应的 jar 包。这里只想引出一个由此产生的问题,那就是日志框架的循环依赖问题。具体而言,如果在应用中使用 Slf4j 作为日志门面,就需要引入 slf4j-api-xx.jar,如果同时又引入了 slf4j-log4j12-xx.jar,log4j-xx.jar,log4j-over-slf4j-xx.jar 这几个包,在这种情况下,调用 slf4j-api 就会出现死循环(如下图所示)。

鉴于此,在引入日志框架依赖的时候要尽力避免,比如以下组合就不能同时出现:

  1. jcl-over-slf4j 和 slf4j-jcl

  2. log4j-over-slf4j 和 slf4j-log4j12

  3. jul-to-slf4j 和 slf4j-jdk14

日志框架的使用选择

  日志框架之间的关系

在介绍日志框架的使用之前,简要回顾一下前面四节的内容。Commons Logging 和 Slf4j 是日志门面。Log4j 和 Logback 则是具体的日志实现方案。可以简单的理解为接口与接口的实现,调用者只需要关注接口而无需关注具体的实现,从而做到解耦。在整个日志框架中主要包括日志门面、日志适配器、日志库三个部分,它们之间的关系如下图所示:

  日志框架的使用选择

比较常用的组合使用方式是 Slf4j 与 Logback 组合使用,Commons Logging 与 Log4j 组合使用,Logback 必须配合 Slf4j 使用。由于 Logback 和 Slf4j 是同一个作者,其兼容性不言而喻。这里顺便介绍一个小故事:Apache 曾试图说服 Log4j 以及其它的日志来按照 Commons-Logging 的标准编写,但是由于 Commons-Logging 的类加载机制在实际应用中存在问题(它使用 ClassLoader 寻找和载入底层的日志库),实现起来也不友好,因此 Log4j 的作者便开发了 Slf4j,与 Commons-Logging 两分天下。

关于如何选择日志框架,如果是新的项目 (没有历史包袱,无需切换日志框架),建议使用 Slf4j 与 Logback 组合,这样有如下的几个优点:

1. Slf4j 实现机制决定 Slf4j 限制较少,使用范围更广。相较于 Commons-Logging,Slf4j 在编译期间便静态绑定本地的 Log 库,其通用性要好得多。

2. Logback 拥有更好的性能。Logback 声称:某些关键操作,比如判定是否记录一条日志语句的操作,其性能得到了显著的提高,这个操作在 Logback 中只需 3 纳秒,而在 Log4j 则需要 30 纳秒。

3. Slf4j 支持参数化,使用占位符号,代码更为简洁,如下例子。

// 在使用 Commons-Logging 时,通常的做法是
if(log.isDebugEnabled()){log.debug("User name:" + user.getName() + " buy goods id :" + good.getId());
}
// 在 Slf4j 阵营,你只需这么做:
log.debug("User name:{} ,buy goods id :{}", user.getName(),good.getId());

4. Logback 的所有文档是免费提供的,Log4j 只提供部分免费文档而需要用户去购买付费文档。

5. MDC (Mapped Diagnostic Contexts) 用 Filter,将当前用户名等业务信息放入MDC 中,在日志 format 定义中即可使用该变量。具体而言,在诊断问题时,通常需要打出日志。如果使用 Log4j,则只能降低日志级别,但是这样会打出大量的日志,影响应用性能;如果使用 Logback,保持原定日志级别而过滤某种特殊情况,如 Alice 这个用户登录,日志将打在 DEBUG 级别而其它用户可以继续打在 WARN 级别。实现这个功能只需加 4 行 XML 配置。

6. 自动压缩日志。RollingFileAppender 在产生新文件的时候,会自动压缩已经打出来的日志文件。压缩过程是异步的,因此在压缩过程中应用几乎不会受影响。

举例说明:如果直接使用 Slf4j 和 Logback 组合,可通过如下配置进行集成:

<dependency><groupId>org.slf4j</groupId><artifactId>slf4j-api</artifactId><version>${slf4j-api.version}</version>
</dependency>
<dependency><groupId>ch.qos.logback</groupId><artifactId>logback-classic</artifactId><version>${logback.version}</version>
</dependency>
<dependency><groupId>ch.qos.logback</groupId><artifactId>logback-core</artifactId><version>${logback.version}</version>
</dependency>

对于已有工程,需要根据所使用的日志库来确定门面适配器从而使用 Slf4j。Slf4j 的设计思想比较简洁,使用了 Facade 设计模式,Slf4j 本身只提供了一个 slf4j-api-version.jar 包,这个 jar 中主要是日志的抽象接口,jar 包中本身并没有对抽象出来的接口做实现。对于不同的日志实现方案(例如 Logback,Log4j 等),封装出不同的桥接组件(例如 logback-classic-version.jar,slf4j-log4j12-version.jar),这样使用过程中可以灵活地选取自己项目里的日志实现。

举例说明,如果已有工程中使用了 Log4j 日志库,可通过如下配置进行集成:

<dependency><groupId>org.slf4j</groupId><artifactId>slf4j-api</artifactId><version>${slf4j-api.version}</version>
</dependency>
<dependency><groupId>org.slf4j</groupId><artifactId>slf4j-log4j12</artifactId><version>${slf4j-log4j12.version}</version>
</dependency>
<dependency><groupId>log4j</groupId><artifactId>log4j</artifactId><version>${log4j.version}</version>
</dependency>

下面是 Slf4j 与其它日志组件调用关系图:

具体的接入方式参见下图:

如果老代码中直接使用非 Slf4j 日志库提供的接口打印日志,需要引入日志库适配器来桥接遗留的 api。在实际环境中我们经常会遇到不同的组件使用的日志框架不同的情况,例如 Spring Framework 使用的是日志组件是 Commons Logging,XSocket 依赖的则是 Java Util Logging。

如果在同一项目中使用不同的组件时,如何解决不同组件依赖的日志组件不一致的情况呢?这就需要统一日志方案,统一使用 Slf4j,把他们的日志输出重定向到 Slf4j,然后 Slf4j 又会根据绑定器把日志交给具体的日志实现工具。Slf4j 带有几个桥接模块,可以重定向 Log4j,JCL 和 java.util.logging 中的 Api 到 Slf4j。

举例说明:如果老代码中直接使用了 Log4j 日志库接口打印日志,需引入如下配置:

<dependency><groupId>org.slf4j</groupId><artifactId>log4j-over-slf4j</artifactId><version>${log4j-over-slf4j.version}</version>
</dependency>

桥接方式参加下图:

  排除项目中依赖的第三方包的日志依赖

在实际使用过程中,项目会根据需要引入一些第三方组件,例如常用的 Spring,而 Spring 本身的日志实现使用了 Commons Logging,如果想使用 Slf4j+Logback 组合,这时候需要在项目中将 Commons Logging 排除掉,通常会用到以下 3 种方案,各有利弊,可以根据项目的实际情况选择最适合自己项目的解决方案。

  • 方案一:采用 maven 的 exclusion 方案

<dependency><groupId>org.springframework</groupId><artifactId>spring-core</artifactId><exclusions><exclusion><groupId>commons-logging</groupId><artifactId>commons-logging</artifactId></exclusion></exclusions><version>${springframework.version}</version>
</dependency>

这种方案优点是 exclusion 是 maven 原生提供的,不足之处是如果有多个组件都依赖了 commons-logging,则需要在很多处增加 exclusion,比较繁琐。

  • 方案二:在 maven 声明 commons-logging 的 scope 为 provided

<dependency><groupId>commons-logging</groupId><artifactId>commons-logging</artifactId><version>1.1.1</version><scope>provided</scope>
</dependency>

这种方案虽然简洁,但也有缺点,在调试代码时有可能导致 IDE 将 commons-logging 放置在 classpath 下,从而导致程序运行时出现异常。

  • 方案三:在 maven 私服中增加类似于 99.0-does-not-exist 这种虚拟的版本号

<dependency>    <groupId>commons-logging</groupId>    <artifactId>commons-logging</artifactId>    <version>99.0-does-not-exist</version>
</dependency>

这种方案好处在于声明方式比较简单,用 IDE 调试代码时也不会出现问题,不足之处是 99.0-does-not-exist 这种版本是 maven 中央仓库中可能不存在,需要发布到自己的 maven 私服中。

明天本号将推出本系列中篇,延续日志话题,结合具体案例介绍日志使用中需要遵循的规范及注意事项,欢迎大家继续关注。

TBC

往期精彩文章回顾

一款云迁移产品的成长史

情感分析技术:让智能客服更懂人类情感

电子书免费下载!1天上手的蓝牙Mesh应用解决方案来袭

语雀的技术架构演进之路

下一代异步 IO 技术解密

io_uring 新异步 IO 机制,性能提升超 150%,堪比 SPDK

如何写好代码?

如何解决大规模高性能存储可靠性问题?

掌门教育微服务体系 Solar(下)

如何应对容器和云原生时代的安全挑战?

长按扫描二维码关注凌云时刻

每日收获前沿技术与科技洞见

进阶之路:Java 日志框架全画传(上)相关推荐

  1. 获取日志的等级_进阶之路:Java 日志框架全画传(中)

    导读:随着互联网和大数据的蓬勃发展,分布式日志系统以及日志分析系统得到了广泛地应用.目前,几乎在所有应用程序中,都会用到各种各样的日志框架来记录程序的运行信息.鉴于此,工程师十分有必要熟悉主流的日志记 ...

  2. java log4j logback jcl_进阶之路:Java 日志框架全画传(下)

    导读:随着互联网和大数据的蓬勃发展,分布式日志系统以及日志分析系统得到了广泛地应用.目前,几乎在所有应用程序中,都会用到各种各样的日志框架来记录程序的运行信息.鉴于此,工程师十分有必要熟悉主流的日志记 ...

  3. 可能是全网最全,JAVA日志框架适配、冲突解决方案,可以早点下班了!

    点击上方"Java基基",选择"设为星标" 做积极的人,而不是积极废人! 每天 14:00 更新文章,每天掉亿点点头发... 源码精品专栏 原创 | Java ...

  4. 可能是全网最全,JAVA日志框架适配/冲突解决方案,可以早点下班了

    点击关注公众号,Java干货及时送达 你是否遇到过配置了日志,但打印不出来的情况? 你是否遇到过配置了logback,启动时却提示log4j错误的情况?像下面这样: log4j:WARN No app ...

  5. 可能是全网最全的 Java 日志框架适配、冲突解决方案

    作者:空无 juejin.cn/post/6945220055399399455 前言 你是否遇到过配置了日志,但打印不出来的情况? 你是否遇到过配置了logback,启动时却提示log4j错误的情况 ...

  6. 多种java 日志框架【超详细图文】

    一.目标 日志的作用和目的 日志的框架 JUL的使用 LOG4J的使用 JCL的使用 二.日志的概念 2.1 日志文件 日志文件是用于记录系统操作事件的文件集合,可分为事件日志和消息日志.具有处理历史 ...

  7. Java日志框架——Logback

    Java日志框架--Logback 简介 1.1 Logback概述 1.2 日志级别 1.3 组件 1.4 配置文件 1.5 日志输出格式 项目中应用步骤 2.1 依赖 2.2 日志输出到控制台 2 ...

  8. Java 日志框架适配/冲突解决方案(值得收藏)

    欢迎关注方志朋的博客,回复"666"获面试宝典 来源:juejin.cn/post/6945220055399399455 前言 你是否遇到过配置了日志,但打印不出来的情况? 你是 ...

  9. java日志框架JUL、JCL、Slf4j、Log4j、Log4j2、Logback 一网打尽

    为什么程序需要记录日志 我们不可能实时的24小时对系统进行人工监控,那么如果程序出现异常错误时要如何排查呢?并且系统在运行时做了哪些事情我们又从何得知呢?这个时候日志这个概念就出现了,日志的出现对系统 ...

  10. Java日志框架 -- 日志框架介绍、日志门面技术、JUL日志(JUL架构、JUL入门示例、JUL日志级别、JUL日志的配置文件)

    1. 日志的概念 日志文件是用于记录系统操作事件的文件集合,可分为事件日志和消息日志.具有处理历史数据.诊断问题的追踪以及理解系统的活动等重要作用. 2. Java日志框架 问题: 控制日志输出的内容 ...

最新文章

  1. 在动作观察,运动想象和站立和坐姿执行过程中解码脑电节律
  2. ie6 z-index bug
  3. wxpython可视化_使用wxPython的绘图模块wxPyPlot进行数据可视化
  4. 楼主考南师计算机学硕,【图片】2019南师大新传学硕考研经验贴【南京师范大学研究生吧】_百度贴吧...
  5. js类似matlab_JavaScript与MATLAB的计算性能差异对比研究
  6. 开课吧:Web开发要学习哪些基础知识?
  7. 【C语言】贪吃蛇游戏
  8. C#开发WebService实例和发布
  9. 北京发布《北京市交通出行数据开放管理办法(试行)》
  10. 小米路由修改服务器密码,小米路由器3G密码怎么重置? 小米3G路由器修改wifi密码的方法...
  11. Tableau-热力图
  12. Word打字很卡顿 Office打字时反应慢 延迟 Excel输入迟钝 PPT卡死的终极解决办法大全(24种方法)
  13. 乔治亚理工学院计算机专业,佐治亚理工学院计算机专业怎么样?
  14. JS-节点的属性 获取各种节点(全)
  15. 大数据起步之wormhole初识
  16. hive函数regexp_extract提取固定长度的数字信息(正则表达)
  17. API网关:开源Apinto网关-应用管理篇
  18. Java库:Jansi - 彩色日志输出体验
  19. dota2 java_电竞Dota2数据API接口 - 【战队列表】调用示例代码
  20. 启动SpringBoot报错:Field userService in com.sunshin.controller.UserController required a bean of type...

热门文章

  1. 招募贴:Hadoop专业解决方案招募义务翻译人员
  2. 苹果傲慢,售后服务中外有别
  3. 一道题目,检验一千个瓶子中哪个有毒
  4. 收到“【有奖话题】虚拟空间“筑梦师”,谈谈微软虚拟化 ”礼物一个
  5. linux mattrib 命令详解
  6. Java-重复性代码统计第一篇
  7. Maven-maven安装、Eclipse配置maven
  8. javascript 对后台返回的数据进行分类
  9. JZOJ 1259. 牛棚安排
  10. Linux源码编译nginx