前言
上篇文章提到了应该如何设计需要输出日志的client jar,大概有三个比较重要的点。
- 缩小jar包依赖范围,如果是基于maven,可以把client工程中依赖的日志相关jar scope设置为provided.
- “感知”应用系统所使用的日志框架,匹配相应的日志框架
- 不使用配置文件,改用编码配置logger
其中第一点非常好实现。本文着重分析第二点与第三点的思路和关键代码。
如何“感知”日志框架
记得很早之前第一次和彭老师喝咖啡的时候,彭老师问我最近在干啥,我说在做公司的权限平台。彭老师问我要设计的时候最开始想到的方案是啥,我说看了看网上的设计好像能满足项目的需求。彭老师说道你一开始其实应该想到像windows操作是怎么考虑权限问题的,看看它的设计说不定能找到灵感有更好地设计方案。
当然彭老师只是提供了一种思路,当面对问题的时候可以想想看有没有人已经很好得解决了这个问题。所以当我要在client jar中“感知”日志框架时,我会先想想在哪些场景下别人和我会有同样的需求。很明显,在一些中间件或者一些框架中会有这种需要,很多中间件会需要输出日志供调试和排错等。
所以,关于如何“感知”日志框架,我在某中间件中找到了答案,该中间件和我有同样的需求场景。对于我们平常使用来说,最重要的两种日志框架是log4j、logback,最重要的日志门面是slf4j、common-logging等,在本文场景中,暂不考虑log4j2。
“感知”日志框架,某中间件中是这么做的。
static {
try {
setLoggerFactory(new Slf4jLoggerFactory());
} catch (Throwable var3) {
try {
setLoggerFactory(new Log4jLoggerFactory());
} catch (Throwable var2) {
setLoggerFactory(new NopLoggerFactory());
}
}
}
public Slf4jLoggerFactory() throws ClassNotFoundException { Class.forName("org.slf4j.impl.StaticLoggerBinder");
}
看见没,就是如此的简单粗暴,要判断系统中是否使用了slf4j,只需要判断“org.slf4j.impl.StaticLoggerBinder”是否存在。这种做法勉强可以算是对的吧,因为如果不使用slf4j,那引入slf4j的相关jar干嘛。这种做法简单粗暴,但是确实可行。
不使用配置文件,改用编码配置logger
前文提到了,如果要适配多种日志框架,那么就需要在client jar的classpath下准备多种不同的日志配置文件。使用配置文件不灵活,那么是否存在不使用配置文件的方式对Logger进行相关设置呢。答案肯定是有,那就是使用代码进行动态配置。
在对logback和log4j的源码进行分析的过程中提到,在logger.info过程中,会调用logger中所有的appender,那么我们要配置日志输出的路径,pattern等,只需要设置相应的appender就行,下文就将分别介绍如何使用代码设置logger的Appender
普通log4j appender设置
public void configureAppender(org.apache.log4j.Logger logger, String name, String pattern,String fileName){
DailyRollingFileAppender appender = new DailyRollingFileAppender();
appender.setName(name);
appender.setLayout(new PatternLayout(pattern)));
appender.setAppend(true);
appender.setFile(fileName);
appender.setEncoding(encoding);
appender.activateOptions();
logger.removeAllAppenders();
logger.addAppender(appender);
}
以上代码为logger设置了一个DailyRollingFileAppender
slf4j+logback appender设置
public void configureAppender(ch.qos.logback.classic.Logger logger, String name, String pattern,String fileName){
RollingFileAppender appender = new RollingFileAppender();
appender.setContext(LogbackLoggerContextUtil.getLoggerContext());
appender.setName(name);
appender.setAppend(true);
appender.setFile(fileName);
TimeBasedRollingPolicy rolling = new TimeBasedRollingPolicy();
rolling.setParent(appender);
rolling.setFileNamePattern(fileName + ".%d{yyyy-MM-dd}");
rolling.setContext(LogbackLoggerContextUtil.getLoggerContext());
rolling.start();
appender.setRollingPolicy(rolling);
PatternLayout layout = new PatternLayout();
layout.setPattern(pattern);
layout.setContext(LogbackLoggerContextUtil.getLoggerContext());
layout.start();
appender.setLayout(layout);
appender.start();
this.logger.detachAndStopAllAppenders();
this.logger.addAppender(appender);
}
以上代码为logback设置了RollingFileAppender
需要注意的是,当使用slf4j的日志门面时,通过LoggerFactory.getLogger得到的logger实例实际类型不尽相同,当使用slf4j+log4j时为org.slf4j.impl.Log4jLoggerAdapter,当使用slf4j+logback时为ch.qos.logback.classic.Logger,前者需要通过反射的手段取得org.apache.log4j.Logger对其设置Appender。
关键代码已经列在上面了,很多代码其实出自官网,官方有提供使用代码进行logger的配置。最后上一张类图来说明整个设计,该类图完整地展示了如何设计一个能自适应多种日志类型的日志工具。
总结
大体的步骤就是识别日志框架,使用相应日志框架得到对应的logger,例如org.apache.log4j.Logger等,根据logger的实际类型将其包装成统一的logger类型并且配置相应的Appender。类图完整地展示了整个设计。