Hello World

吞风吻雨葬落日 欺山赶海踏雪径

0%

自定义logback日志

自定义logback日志组件,自动格式化console日志,并创建 operation_logaudit_log文件日志。

源码分析

logger.info(msg) 的执行流程:

  1. Logger#buildLoggingEventAndAppend 入口,创建 LoggingEvent 并执行logger对应的 appender
  2. Logger#callAppenders 执行本logger与父logger对应的appender
  3. AppenderAttachableImpl#appendLoopOnAppenders 执行当前logger的所有appender的doAppend方法
  4. UnsynchronizedAppenderBase#append 执行appender类的append 方法输出日志
  5. Console的appender执行到 OutputStreamAppender#subAppend
  6. LayoutWrappingEncoder#encode开始转成msg
  7. PatternLayoutBase#writeLoopOnConverters 一个个的生成pattern对应的字符串
  8. OutputStreamAppender#writeBytes 把对应的内容写入 outputStream

因为自定义的字段比如 traceId 需要使用到 MDC, 由MDC写入LoggingEvent#mdcPropertyMap的,且只能设置一次。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public Map<String, String> getMDCPropertyMap() {
// populate mdcPropertyMap if null
if (mdcPropertyMap == null) {
MDCAdapter mdc = MDC.getMDCAdapter();
if (mdc instanceof LogbackMDCAdapter)
mdcPropertyMap = ((LogbackMDCAdapter) mdc).getPropertyMap();
else
mdcPropertyMap = mdc.getCopyOfContextMap();
}
// mdcPropertyMap still null, use emptyMap()
if (mdcPropertyMap == null)
mdcPropertyMap = Collections.emptyMap();

return mdcPropertyMap;
}

public void setMDCPropertyMap(Map<String, String> map) {
if (mdcPropertyMap != null) {
throw new IllegalStateException("The MDCPropertyMap has been already set for this event.");
}
this.mdcPropertyMap = map;

}

所以如果想通过自定义appender 注入mdc内容一定要保证此 appender 优先级放在第一位。

具体实现

  1. 自定义 ConsoleAppender 替换ROOT logger 的ConsoleAppender,并放在执行顺序的第一位
  2. 创建 RollingFileAppender 并加入到 ROOT logger 中

ConsoleAppender

需要放在执行位置的第一位,并删除掉应用其他的console日志。

  • 通过LoggerFactory.getILoggerFactory()获取到 LoggerContext
  • loggerContext.getLogger 获取到ROOT logger
  • root.iteratorForAppenders()迭代所有的appender
  • root.detachAppender(appender); 删除appender
  • root.addAppender(appender); 增加appender

具体实现如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
private void injectRootAppender() {
LoggerContext loggerContext = (LoggerContext) LoggerFactory.getILoggerFactory();
ch.qos.logback.classic.Logger root = loggerContext.getLogger(ch.qos.logback.classic.Logger.ROOT_LOGGER_NAME);
// 首先移除所有已存在的 ConsoleAppender
List<Appender<ILoggingEvent>> appenders = new ArrayList<>();
// 控制台放第一个
appenders.add(createStdConsoleAppender());
Iterator<Appender<ILoggingEvent>> appenderIterator = root.iteratorForAppenders();
while (appenderIterator.hasNext()) {
Appender<ILoggingEvent> appender = appenderIterator.next();
root.detachAppender(appender);
if (appender instanceof ConsoleAppender) {
continue;
}
appenders.add(appender);
}
for (Appender<ILoggingEvent> appender : appenders) {
root.addAppender(appender);
}
}

private ConsoleAppender<ILoggingEvent> createStdConsoleAppender() {
LoggerContext loggerContext = (LoggerContext) LoggerFactory.getILoggerFactory();
CustomizedConsoleAppender appender = new CustomizedConsoleAppender();
appender.setContext(loggerContext);
appender.setName("CONSOLE");
// 设置输出格式
PatternLayoutEncoder encoder = new PatternLayoutEncoder();
encoder.setContext(loggerContext);
encoder.setPattern("[%d{yyyy-MM-dd HH:mm:ss.SSS}] [%X{traceId}] [%level] [%C#%M] [%thread] %msg%n");
encoder.start();
appender.setEncoder(encoder);
ThresholdFilter filter = new ThresholdFilter();
filter.setLevel("INFO");
filter.start();
appender.addFilter(filter);
// 启动并添加到 root logger
appender.start();
return appender;
}

然后重写 CustomizedConsoleAppenderappend 方法

1
2
3
4
5
6
7
8
public class CustomizedConsoleAppender<E> extends ConsoleAppender<E> {
@Override
protected void append(E eventObject) {
MDC.put("traceId", "X123456789X");
super.append(eventObject);
MDC.remove("traceId");
}
}

RollingFileAppender

loggerContext.getLogger("OperationLogger"); 会自动创建对应的logger
根据这种特性,直接通过以上方式创建logger后增加RollingFileAppender即可。

代码实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
private void createOperationLogger() {
// 创建OperationLogger
LoggerContext loggerContext = (LoggerContext) LoggerFactory.getILoggerFactory();
RollingFileAppender<ILoggingEvent> rollingFileAppender = new RollingFileAppender<>();
rollingFileAppender.setContext(loggerContext);
rollingFileAppender.setName("OperationLoggerAppender");
String filePath = "logs/operation.log";
rollingFileAppender.setFile(filePath);
PatternLayoutEncoder encoder = new PatternLayoutEncoder();
encoder.setContext(loggerContext);
encoder.setPattern("%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n");
encoder.start();
rollingFileAppender.setEncoder(encoder);

SizeAndTimeBasedRollingPolicy<ILoggingEvent> rollingPolicy = new SizeAndTimeBasedRollingPolicy<>();
rollingPolicy.setContext(loggerContext);
rollingPolicy.setFileNamePattern("logs/operation_%d{yyyy-MM-dd}_%i.log.gz");
rollingPolicy.setMaxFileSize(FileSize.valueOf("100mb"));
rollingPolicy.setMaxHistory(1);
rollingPolicy.setTotalSizeCap(FileSize.valueOf("500mb"));
rollingPolicy.setParent(rollingFileAppender);
rollingPolicy.start();
// 设置滚动策略
rollingFileAppender.setRollingPolicy(rollingPolicy);
// 启动 appender
rollingFileAppender.start();
ch.qos.logback.classic.Logger operationLogger = loggerContext.getLogger("OperationLogger");
operationLogger.addAppender(rollingFileAppender);
operationLogger.setLevel(ch.qos.logback.classic.Level.INFO);
operationLogger.setAdditive(false); // 防止日志消息传递到父 logger
}

其他注意点

需要注意的是,为了避免日志的丢失,应该尽早的执行以上的操作。logback的日志初始化是在 LoggingApplicationListener 中实现的。
所以可以自定义 CustomerLogAppliactionListener 来监听 ApplicationPreparedEvent 事件来执行以上操作,注意 order 设置

1
2
3
4
@Override
public int getOrder() {
return LoggingApplicationListener.DEFAULT_ORDER + 1;
}