自定义logback日志组件,自动格式化console日志,并创建 operation_log 与 audit_log文件日志。
源码分析 logger.info(msg) 的执行流程:
Logger#buildLoggingEventAndAppend 入口,创建 LoggingEvent 并执行logger对应的 appender
Logger#callAppenders 执行本logger与父logger对应的appender
AppenderAttachableImpl#appendLoopOnAppenders 执行当前logger的所有appender的doAppend方法
UnsynchronizedAppenderBase#append 执行appender类的append 方法输出日志
Console的appender执行到 OutputStreamAppender#subAppend
LayoutWrappingEncoder#encode开始转成msg
PatternLayoutBase#writeLoopOnConverters 一个个的生成pattern对应的字符串
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 () { if (mdcPropertyMap == null ) { MDCAdapter mdc = MDC.getMDCAdapter(); if (mdc instanceof LogbackMDCAdapter) mdcPropertyMap = ((LogbackMDCAdapter) mdc).getPropertyMap(); else mdcPropertyMap = mdc.getCopyOfContextMap(); } 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 优先级放在第一位。
具体实现
自定义 ConsoleAppender 替换ROOT logger 的ConsoleAppender,并放在执行顺序的第一位
创建 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); 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); appender.start(); return appender; }
然后重写 CustomizedConsoleAppender 的 append 方法
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 () { 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); 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 ); }
其他注意点 需要注意的是,为了避免日志的丢失,应该尽早的执行以上的操作。logback的日志初始化是在 LoggingApplicationListener 中实现的。 所以可以自定义 CustomerLogAppliactionListener 来监听 ApplicationPreparedEvent 事件来执行以上操作,注意 order 设置
1 2 3 4 @Override public int getOrder () { return LoggingApplicationListener.DEFAULT_ORDER + 1 ; }