
挑战赛
假设我们要以不同的方式记录某个Java类的每个方法:
- 每个方法都有自己的日志文件,
- ...您的日志格式,
- ...其最低记录级别
- 我们使用自己的
%
扩展日志格式, - 能够动态更新此配置。
本文介绍了如何满足这些要求。 为了保持简单性,仅通过方法执行日志记录的分离。 实际上,您可能需要一个分层的限定配置,例如
→
→
→
...。下面是完整源代码的链接。
客户代码
class ThingService { log = LoggerFactory.getLogger(); getThing() { log.debug("getThing...");
退回
对于实现,选择了一个可靠的“ logback”日志记录库,该库提供了有趣的自定义可能性:
ch.qos.logback:logback-classic:1.2.3
它既可以通过XML配置进行配置,也可以直接通过Java进行配置,可以将方法组合在一起:
public void configureLogback() throws JoranException { LoggerContext lc = LoggerFactory.getILoggerFactory(); lc.reset();
关于日志的简要介绍:
- 程序员拉记录器 ,
- 记录器将分配给他的附加程序拉出,
- 附加器考虑并调用编码器 ,
- 编码器会精确格式化日志的一行,
- 为此,他拉出了一系列转换器 ,每个转换器都揭示了其
%
, - 成功。
为简单起见,选择了纯Java配置。 如果牢记XML配置,这里的一切都很明显。 主要任务是创建自己的附加程序/编码器并注册它们-登录后将从它们的肠道中调用它们。 必须记住,几乎所有创建的对象都必须使用start()
方法start()
。 抽象示例:
Logger rootLogger = LoggerFactory.getLogger(Logger.ROOT_LOGGER_NAME); LoggerContext lc = rootLogger.getLoggerContext(); lc.reset();
彼此分开的记录方法
这样,在调用该方法之前,登录可以将一种方法与另一种方法区分开来,将其名称保存在ThreadLocal
Mapped Diagnostic Context中。 此外,在分析过程中,我们不会直接从MDC
类获取这些值,因为日志记录代码将在另一个线程中执行,并且该数据也将不会存在-我们通过ILoggingEvent.getMDCPropertyMap()
获得它们。
在一般情况下,正如vooft正确指出的那样,您需要支持调用堆栈,而不是覆盖MDC值,而是将其返回到前一帧,这是通过引入新的ThreadLocal
。 原理图示例:
try { MDC.put(MDC_KEY_METHOD, currentMethod);
每种方法都有自己的日志文件
让我们创建一个,不要忘记注册自己的附加程序:
class MultiAppender extends AppenderBase<ILoggingEvent> { @Override protected void append(ILoggingEvent event) { method = event.getMDCPropertyMap().get(MDC_KEY_METHOD); Appender appender = getOrCreateAppender(method); appender.doAppend(event); }
他本人几乎不执行任何操作,只将日志记录到一组实际文件附加程序中,每种方法均一个。 委派一位,最合适。 按需创建“真实”附加器,如下所示:
fileAppender = new FileAppender<ILoggingEvent>(); fileAppender.setContext(lc); fileAppender.setAppend(false); fileAppender.setEncoder(getOrCreateEncoderByMethod(lc, method)); fileAppender.setFile(logFileByMethod.get(method)); fileAppender.start();
为此,请保留Encoder
类型的自动创建对象的缓存:
Map<String, String> patternByMethod = new HashMap<>();
每种方法都有自己的日志记录级别
MultiAppender
向MultiAppender
类添加一个检查:如果事件级别MultiAppender
为该方法指定MultiAppender
阈值,则仅将其记录:
Map<String, Level> levelByMethod = new HashMap<>(); protected void append(ILoggingEvent event) { Level minLevel = levelByMethod.get(methodName); if (event.getLevel().levelInt >= minLevel.levelInt) { appender.doAppend(event); }
原则上,可以将此逻辑放入过滤器中。
为了不围墙花园,而要利用经过验证的logback基础结构,您需要定义自己的转换器类,确保完全公开,以便可以从外部实例化它。 如果您需要MDC
,请从活动中获取它。 %custom
变量处理程序从此处开始:
public class CustomConverter extends ClassicConverter { public String convert(ILoggingEvent event) {
在常规配置过程中,注册处理程序:
void configurePatterns(LoggerContext lc) { Map<String, String> rules = lc.getObject(CoreConstants.PATTERN_RULE_REGISTRY); if (rules == null) { rules = new HashMap<String, String>(); lc.putObject(CoreConstants.PATTERN_RULE_REGISTRY, rules); } rules.put("custom", CustomConverter.class.getName()); }
我们将使用例如PatternLayoutEncoder
作为编码器,它将提取所有内容。 在这种情况下, %custom
变量将扩展为字符串"variable-expanded"
。
即时更新配置
开箱即用有这样的机会:再次调用配置器函数就足够了,不要忘记在那里执行LoggerContext::reset()
并清除累积的缓存。
多线程
如果我们配置的方法使新线程栩栩如生,那么,当然,指定的日志记录规则将不适用于它们-线程local'y不会单独出现在新线程中。 因此,如果要将方法设置应用于新的流,则必须在此处复制MDC
:
Map<String, String> mdcOrig = MDC.getCopyOfContextMap(); ExecutorService es = Executors.newFixedThreadPool(1); es.submit(() -> threadWorker(mdcOrig)); void threadWorker(Map<String, String> parentMdc) { MDC.setContextMap(parentMdc); log.error("expected to appear in method2*.log"); }
整个例子
https://github.com/zencd/logback-setup
文学作品
官方登录手册