一个著名的日志系统是怎么设计出来的

原文:一个著名的日志系统是怎么设计出来的

1 前言

Java 帝国在诞生之初就提供了集合、线程、IO、网络等常用功能,从 C 和 C++ 领地那里吸引了大量程序员过来加盟,但是却有意无意地忽略了一个重要的功能:输出日志。

对于这一点,IO 大臣其实非常清楚,日志是个很重要的东西,因为程序运行起来以后,基本上就是一个黑盒子,如果程序的行为和预料的不一致,那就是出现 Bug 了,如何去定位这个 Bug 呢?

臣民们能用的工具有两个,第一个就是单步调试,一步步地跟踪,查看代码中变量的值,这种办法费时费力,并且只能在程序员的机器上才能用。

第二种就是在特定的地方打印日志,通过日志的输出,帮助快速定位。尤其是当代码在生产环境上跑起来以后,日志信息更是必不可少,要不然出了状况两眼一抹黑,上哪儿找问题去?总不能让臣民们把自己变成一个线程进入系统来执行吧?

但是 IO 大臣也有自己的小算盘:日志嘛,用我的 System.out.println(.....) 不就可以了?!我还提供了 System.err.println 不是?

在 IO 大臣的阻挠下,从帝国的第一代国王到第三代国王,都没有在 JDK 中提供日志相关的工具包,臣民们只好忍受着去使用 System.out.println 去输出日志,把所有的信息都输出到控制台,让那里变成一堆垃圾。

2 张家村

张家村的电子商务系统也不能幸免,自然也遇到了日志的问题。经验丰富的老村长已经烦透了 System.out.println 所输出的大量难于理解的无用信息,看着村民民整天苦逼地和这些 System.out 做斗争,他找来了小张,命令他设计一个通用的处理日志的系统。

小张在消息队列和 JMS 的设计上花了不少功夫,积累了丰富的经验,从那以后一直都是实现业务代码,一直都是 CRUD,张二妮整天笑话自己是 HTML 填空人员,这一回一定要让她看看自己的设计功力!

老村长给小张下达的需求是这样的:

  1. 日志消息除了能打印到控制台,还可以输出到文件,甚至可以通过邮件发送出去(例如生成环境出错的消息);

  2. 日志内容应该可以做格式化,例如变成纯文本,XML,HTML 格式等等;

  3. 对于不同的 Java class,不同的 package,还有不同级别的日志,应该可以灵活地输出到不同的文件中;

    例如对于 com.foo 这个 package,所有的日志都输出到 foo.log 文件中;

    对于 com.bar 这个 package,所有文件都输出到 bar. log 文件中;

    对于所有的 ERROR 级别的日志,都输出到 errors.log 文件中。

  4. 能对日志进行分级,有些日志纯属 debug,在本机或者测试环境使用,方便程序员的调试,生产环境完全不需要。有些日志是描述错误 (error) 的,在生产环境下出错的话必须要记录下来,帮助后续的分析。

小张仔细看了看,拍着胸脯对老村长说:“没问题,明天一定让您老看到结果。”

3 小张的设计

老村长走了以后,小张开始分析需求,祭出“面向对象设计大法”,试图从村长的需求中抽象出一点概念。

首先要记录日志,肯定需要一个类来表达日志的概念,这个类至少应该有两个属性,一个是时间戳,一个是消息本身,把它叫做 LoggingEvent 吧,记录日志就像记录一个事件嘛。

其次是日志可以输出到不同的地方,控制台、文件、邮件等等,这个可以抽象一下,不就是写到不同的目的地吗?可以叫做 LogDestination?

嗯,还是简单一点,叫做 Appender 吧,暗含了可以不断追加日志的意思。

img

至于第二条的日志内容可以格式化,完全可以比葫芦画瓢,定义一个 Formatter 接口去格式化消息。

img

对了,Appender 应该引用 Formatter,这样以来就可以对 LoggingEvent 记录格式化以后再发送。

第三条需求把小张给难住了,不同的 class,package 输出的目的地不同? “目的地”这个概念是由 Appender 来表达的,难道让不同的 class、package 和 Appender 关联?不不,不能这样!

还需要一个新的概念,这个概念是什么?

从用户角度想一下,村民们要想打印日志,必须得先获取个什么东西,这个东西是不是可以称为 Logger 啊?灵感的火花就闪了那么一下就被小张抓住了:获取 Logger 的时候要传入类名或者包名!

img

这样一来,不同的 class,package 就区分开了,然后让 Logger 和 Appender 关联,灵活地设置日志的目的地,并且一个 Logger 可以拥有多个 Appender,同一条日志消息可以输出到多个地方,完美!

img

小张迅速地画出了核心类的类图:

img

还算漂亮,小张陶醉着自我欣赏了一下。

再接再厉,把第四条需求也设计一下,日志要分级,这个简单,定义一个 Priority 的类,里边定义 5 个常量 DEBUG,INFO,WARN,ERROR,FATAL,表示 5 个不同的级别就 OK 了。当然这我 5 个级别有高低之分,DEBUG 级别最低,FATAL 级别最高。

还可以给 Logger 增加一些辅助编程的方法,如 Logger.debug(....),Logger.info(...),Logger.warn(...) 等等,这样村民们将来就可以轻松地输出各种级别的日志了。

等一下,老村长还说过“对于所有的 ERROR 级别的日志,都输出到 errors.log 文件中”类似这样的需求,好像给忽略了。

这也好办嘛,只要在 Appender 上增加一个属性,就叫做 Priority,如果用户要输出的日志是 DEBUG 级别,但是有个 FileAppender 的 Priority 是 ERROR 级别,那这个日志就不用在这个 FileAppender 中输出了,因为 ERROR 级别比 DEBUG 级别高嘛。

同理,在 Logger 类上也可以增加一个 Priority 的属性,用户可以去设置,如果一个 Logger 的 Priority 是 ERROR,而用户调用了这个 Logger 的 debug 方法,那这个 debug 的消息也不会输出。

小张全心全意地投入到设计当中,一看时间,都快半夜了,赶紧休息,明天向村长汇报去。

4 正交性

第二天,小张给老村长展示了自己设计的 LoggerEvent,Logger,Appender,Formatter,Priority 等类和接口,老村长捻着胡子满意地点点头:“不错不错,与上一次相比有巨大的进步。你知不知道我在需求中其实给了你引导?”

“引导?什么引导? ”

“就是让你朝着正交的方向去努力啊”

“正交?”

“如果你把 Logger,Appender,Formatter 看成坐标系中的 X 轴,Y 轴,Z 轴,你看看,这三者是不是可以独立变化而不互相影响啊?”

img

“哇塞,果然如此,我可以任意扩展 Appender 接口而影响不到 Logger 和 Formatter,无论有多少个 Logger 都影响不了 Appender 和 Formatter,这就是正交了?”

“是啊,当你从系统中提取出正交的概念的时候,那就威力无比了,因为变化被封装在了一个维度上,你可以把这些概念任意组合,而不会变成意大利面条似的代码。 ”

听到村长做了理论的升华,小张兴奋得直搓手。

“好吧,你把这个设计实现了吧,对了,你打算叫什么名字? ”村长问道,

“我打算把他叫做 Log4j,意思是 Log for Java”,

“不错,就这么定了吧”。

5 Log4j

小张又花了两个月的时间把 Log4j 开发了出来,由于 Log4j 有着良好的设计,优异的性能,不仅仅是张家村的人在用,Java 帝国的很多村镇、部落都爱上了它。

后来张家村把 Log4j 在 Apache 部落开源了,这下子吸引了无数的人无偿帮助测试它,扩展它,改进它,很快就成了帝国最流行的日志工具。

张家村建议帝国把 Log4j 纳入到 JDK 中,帝国那效率低下的官僚机构竟然拒绝了。消息传到了 IO 大臣的耳朵里,他不由的扼腕叹息:唉,失去了一次极好的招安机会啊。现在唯一的办法就是赶紧上奏皇上,在官方也提供一套,争取让臣民们使用官方版本。

到了第四代国王(JDK1.4),臣民们终于看到了帝国提供的 java.util.logging 包,也是用来记录日志的,并且其中的核心概念 Logger,Formatter,Handler 和 Log4j 非常相似,只是为时已晚,Log4j 早已深入人心了,不可撼动了。

6 尾声

Log4j 在 Apache 开源以后,小张也逐渐地有点落寞,他闲不住又写了一个工具,叫做 Logback,有了之前的经验,这 Logback 比 Log4j 还要快。

如今的日志世界有了很多的选择,除了 java.util.logging,Log4j 之外,还有 Logback,tinylog 等其他工具。

小张想了想,这么多日志工具,用户如果想切换了怎么办?不想用 Log4j 了,能换到 Logback 吗?

我还是提供一个抽象层吧,用户用这个抽象层的 API 来写日志,底层具体用什么日志工具不用关心,这样就可以移植了。

小张把这抽象层就叫做 Simple Logging Facade for Java,简称 SLF4J

img

对于 Log4j,JDK logging,tinylog 等工具,需要一个适配层,把 SLF4J 的 API 转化成具体工具的调用接口。

由于 Logback 这个工具也是出自小张之手,直接实现了 SLF4J 的 API,所以连适配层都不需要了,用起来速度飞快,效率最高,SLF4J+Logback 成为了很多人的最爱,大有超越 Apache Common Logging + Log4j 之势。

后记:本文主要想讲一下日志工具的历史和现状,尤其是 Log4j 核心的设计理念。

文中的小张其实就是 Ceki Gülcü,他开发了 Log4j,Logback,以及 SLF4J,为 Java 的日志事业做出了卓越的贡献。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/820915.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

实验三蕉 C语言函数应用编程蕉

实验三蕉 C语言函数应用编程蕉可恶,是原始博士!什么时候!?😖😫额啊,我,我是,呃,香蕉?🤔对,我是香蕉🍌😃香🐵香🐵香🐵香🐵香🐵香🐵🍌蕉🍌蕉🍌蕉🍌蕉🍌蕉🍌蕉香🐵香🐵香🐵香🐵香🐵香🐵🍌蕉🍌蕉🍌蕉🍌蕉🍌蕉�…

Cookie、Session、Token三者的区别

在数字世界的茫茫人海中,每一次点击、每一次登录,都伴随着身份认证与数据安全的较量。今天咱要来一场惊心动魄的技术探秘之旅,今天我要带你深入探索Web开发中那三个绕不开的名字——Cookie、Session、Token,它们不仅仅是技术名词,更是构建安全、高效用户交互的基石,看看它…

20222417 2024-2025-1 《网络与系统攻防技术》实验三实验报告

1.实践内容 1.1实验目的 (1)正确使用msf编码器,veil-evasion,自己利用shellcode编程等免杀工具或技巧 正确使用msf编码器,使用msfvenom生成如jar之类的其他文件 veil,加壳工具 使用C + shellcode编程 (2)通过组合应用各种技术实现恶意代码免杀 如果成功实现了免杀的,简单语…

人生路manman,Man游常相伴——软工实践第一次团队作业

作业所属课程 班级的链接作业要求 作业要求的链接作业目标 你理解的作业目标具体内容团队名称 iman团队成员 102202146 - 蓝敏龙, 102201225 - 陈碧煌, 102202105 - 王梓铭, 102202124 - 阿依娜孜, 102202135 - 施宇翔, 102202134 - 承宇豪, 102202117 - 杨邑豪, 102202122 - 张…

java学习10.23

继续写这个javaweb项目,实现增删和之间的页面跳转

2024秋软工实践 福气满满团队展示与选题报告

这个作业属于哪个课程 https://edu.cnblogs.com/campus/fzu/SE2024作业要求 https://edu.cnblogs.com/campus/fzu/SE2024/homework/13281作业的目标 运用LMM大语言模型接口创建一款基于场景的AI对话软件,为传统软件赋予全新功能团队名称 福气满满团队成员学号-名字 052203132童…

《操作系统真象还原》内核内存分布与加载

操作系统内核加载流程图 %%{init:{theme:default, themeVariables:{fontSize:1px}}}%% graph TBA(mbr.s 0xc700开始) --> rd_disk_m_16(mbr.s <br /> rd_disk_m_16) A --> C(loader.s <br> jmp LOADER_BASE_ADDR + 0x300) --> loader_start(loader.s…

20222318 2024-2025-1 《网络与系统攻防技术》实验二实验报告

1.实验内容 (一)本周课程内容 (1)深入理解后门概念及其实际案例,明晰后门对系统安全构成的潜在威胁。 (2)普及后门技术知识,涵盖各类进程隐藏技巧,并熟悉netcat、meterpreter、veil等常见工具的应用。 (3)进一步学习了shellcode注入的逻辑原理及其在不同场景下的应用…

IDEA 类和方法的注释

IDEA 类和方法的注释 一、设置方法的注释 (1) 打开file->setting->Editor->LiveTemplates点击右上面那个+号,选择Template Group双击,然后弹出一个窗口,添加命名为KeyBoard点击OK完成,如下图1所示:图1 (2) file->setting->Editor->LiveTemplates这个…

实验2 类和对象

任务1 t.h1 #pragma once2 3 #include <string>4 5 // 类T: 声明6 class T {7 // 对象属性、方法8 public:9 T(int x = 0, int y = 0); // 普通构造函数 10 T(const T &t); // 复制构造函数 11 T(T &&t); // 移动构造函数 12 ~T();…

提权 | Windows系统

提权篇:Windows系统常见提权姿势。目录cmd提权meterpreter提权getsystemsteal_tokenmigrate令牌窃取(MS16-075)烂土豆提权步骤烂土豆提权原理sc命令提权抓本地密码提权其他工具pr工具内核提权WindowsVulScan cmd提权 前言:我们getshell一个用windows部署的网站后,通过蚁剑或…

for 循环()简单到高阶

for循环的初始意义是遍历一串具有相同特性的值 1、遍历数组,根据索引去求值点击查看代码 public class ForDemo1 {public static void main(String[] args) {int[] arr = {1,2,3,4,5,6,7};for (int i = 0; i < arr.length; i++) {System.out.println("arr["+i+&q…