Log日志框架简介(log4j、logback、MDC链路追踪)
日志概要简介
不管是使用何种编程语言,日志输出几乎无处不在。总结起来,日志大致有以下几种用途:
问题跟踪:通过日志不仅仅包括我们程序的一些bug,也可以在安装配置时,通过日志可以发现题 状态监控:通过实时分析日志,可以监控系统的运行状态,做到早发现问题,早处理问题。
安全审计:审计主要体现在安全方面上,通过日志进行分析,可以发现是否存在非授权的操作。
日志体系本身的架构实现相对比较简单,因为比较简单,目前我们使用的很多框架都默认集成了log日志框架,所以很少有人会对其进行较详细的了解和查看,目前大部分都是停留在基础的日志打印输出层面,对于为什么?怎么做?并不是特别的清楚,我这里对日志系统的体系做一个简要的介绍,并结合此次对openfire的日志调整内容,实现进行一个说明,希望对大家后续日志的处理有一定的帮助。
日志框架
我们在使用日志框架时,通常会涉及到如下内容 : log4j、logback、JDK Logging 、slf4j、commons-logging…
基础知识
日志框架分为三大部分,包括日志门面、日志适配器、日志库。
-
日志门面
:门面设计模式是面向对象设计模式中的一种。日志门面采用的就是这种模式。它只提供一套接口规范,自身不负责日志功能实现,目的是让使用者不需要关注底层具体是哪个日志库来负责日志打印以及具体的使用细节。目前最为广泛的日志门面有两种: slf4j 和 common-logging。 在阿里的日志规约中也说明在进行日志输出时,需要在代码中使用日志门面类,而不要使用具体的日志库,方便后续如果要替换日志库时,不会有太大的影响。这两个日志门面的差别对我们来说不是很大,最直观的差别是:
common-logging日志输出:
int score = 99;
p.setScore(score);
log.info("Set score " + score + " for Person " + p.getName() + " ok.");
SLF4J的日志输出:
int score = 99;
p.setScore(score);
logger.info("Set score {} for Person {} ok.", score, p.getName());
-
日志库
:它具体实现了日志的相关功能,主流的日志库有三个,分别是log4j、log-jdk、logback。
jdk1.4版本引入了java.util.logging.Logger,简称log-jdk
。Logging系统在JVM启动时读取配置文件并完成初始化,一旦开始运行
main()
方法,就无法修改配置;配置不太方便,需要在JVM启动时传递参数-Djava.util.logging.config.file=<config-file-name>
。因此,Java标准库内置的Logging使用并不是非常广泛 log4j是最早诞生的日志库 ,它诞生之前都是使用system.out或者system.err。之前log4j1的速度比较慢,log4j2升级后速度较之前有和很大的提升。所以目前用的最多的日志库为log4j2和logback logback是最晚出现的 ,它与log4j出自同一个作者,是log4j升级版,而且本身就实现了slf4j的接口。 所以通常日志搭配会是 slf4j+logback (在springboot中通常会选用此组合)
所有在实际使用的程序中,注意日志库的选择,如果同一个工程中存在多个日志库共存,则可能会导致日志输出的异常现象。
- 日志门面适配器 :因为slf4j规范是后来提出来的,在此之前的日志库是没有实现slf4j的接口的,例如:log4j,所以,在工程里要想使用slf4j+log4j的模式,就额外需要一个适配器(slf4j-log4j12)来解决接口不兼容的问题。 目前openfire 就是使用 log4j-slf4j-impl 适配器来实现日志的输出。
log-jdk、log4j和logback的日志库在进行配置时,存在一定的差别,我们可以将日志输出到控制台和文件,同时不同的日志库也支持将日志输出到数据库、邮件发送、socket、mq等。这里不在详述,后续根据需要在自行查看。
Log日志追踪技术之MDC
MDC(Mapped Diagnostic Context,映射调试上下文)是Slf4j(提供了接口定义和核心实现,日志库负责适配器的实现)提供的一种方便在多线程条件下记录日志的功能。MDC 可以看成是一个与当前线程绑定的Map,可以往其中添加键值对。MDC 中包含的内容可以被同一线程中执行的代码所访问。当前线程的子线程会继承其父线程中的 MDC 的内容。当需要记录日志时,只需要从 MDC 中获取所需的信息即可。MDC 的内容则由程序在适当的时候保存进去。对于一个 Web 应用来说,通常是在请求被处理的最开始保存这些数据。
简而言之,MDC就是日志框架提供的一个InheritableThreadLocal,项目代码中可以将键值对放入其中,然后使用指定方式取出打印即可。
MDC的核心代码实现类,对代码进行了删减,省略非核心代码
public class MDC {
......
static MDCAdapter mdcAdapter;
public static void put(String key, String val) throws IllegalArgumentException {
if (key == null) {
throw new IllegalArgumentException("key parameter cannot be null");
} else if (mdcAdapter == null) {
throw new IllegalStateException("MDCAdapter cannot be null.");
} else {
mdcAdapter.put(key, val);
public static String get(String key) throws IllegalArgumentException {
if (key == null) {
throw new IllegalArgumentException("key parameter cannot be null");
} else if (mdcAdapter == null) {
throw new IllegalStateException("MDCAdapter cannot be null.");
} else {
return mdcAdapter.get(key);
...........
}
BasicMDCAdapter 的核心实现类的处理逻辑
public class BasicMDCAdapter implements MDCAdapter {
private InheritableThreadLocal<Map<String, String>> inheritableThreadLocal = new InheritableThreadLocal<Map<String, String>>() {
protected Map<String, String> childValue(Map<String, String> parentValue) {
return parentValue == null ? null : new HashMap(parentValue);
public BasicMDCAdapter() {
public void put(String key, String val) {
if (key == null) {
throw new IllegalArgumentException("key cannot be null");
} else {
Map<String, String> map = (Map)this.inheritableThreadLocal.get();
if (map == null) {
map = new HashMap();
this.inheritableThreadLocal.set(map);
((Map)map).put(key, val);
public String get(String key) {
Map<String, String> map = (Map)this.inheritableThreadLocal.get();
return map != null && key != null ? (String)map.get(key) : null;
.........
}
MDC实现链路追踪DEMO,设置参数后只需要在log日志输出的文件中配置
%x
就可以了。
try{
String traceId = TraceUtils.begin();
MDC.put("muuid", traceId);
Object obj = pjp.proceed(args);
return obj;
} catch(Throwable e) {