Java / logback中的单独方法记录

巴特写一些日志


挑战赛


假设我们要以不同的方式记录某个Java类的每个方法:


  • 每个方法都有自己的日志文件,
  • ...您的日志格式,
  • ...其最低记录级别
  • 我们使用自己的%扩展日志格式,
  • 能够动态更新此配置。

本文介绍了如何满足这些要求。 为了保持简单性,仅通过方法执行日志记录的分离。 实际上,您可能需要一个分层的限定配置,例如...。下面是完整源代码的链接。


客户代码


  class ThingService { log = LoggerFactory.getLogger(); getThing() { log.debug("getThing..."); // => one.log } listThings() { log.debug("listThings..."); // => another.log } } 

退回


对于实现,选择了一个可靠的“ logback”日志记录库,该库提供了有趣的自定义可能性:


 ch.qos.logback:logback-classic:1.2.3 

它既可以通过XML配置进行配置,也可以直接通过Java进行配置,可以将方法组合在一起:


  public void configureLogback() throws JoranException { LoggerContext lc = LoggerFactory.getILoggerFactory(); lc.reset(); // reset prev config JoranConfigurator configurator = new JoranConfigurator(); configurator.setContext(lc); configurator.doConfigure("config.xml"); // any data source StatusPrinter.printInCaseOfErrorsOrWarnings(lc); //   : Logger root = LoggerFactory.getLogger(Logger.ROOT_LOGGER_NAME); root.setLevel(Level.INFO); //  } 

关于日志的简要介绍:


  1. 程序员拉记录器
  2. 记录器将分配给他的附加程序拉出,
  3. 附加器考虑并调用编码器
  4. 编码器会精确格式化日志的一行,
  5. 为此,他拉出了一系列转换器 ,每个转换器都揭示了其%
  6. 成功。

为简单起见,选择了纯Java配置。 如果牢记XML配置,这里的一切都很明显。 主要任务是创建自己的附加程序/编码器并注册它们-登录后将从它们的肠道中调用它们。 必须记住,几乎所有创建的对象都必须使用start()方法start() 。 抽象示例:


  Logger rootLogger = LoggerFactory.getLogger(Logger.ROOT_LOGGER_NAME); LoggerContext lc = rootLogger.getLoggerContext(); lc.reset(); // reset prev config var encoder = new PatternLayoutEncoder(); encoder.setContext(lc); encoder.setPattern("%-5level %message%n"); encoder.start(); var appender = new ConsoleAppender<ILoggingEvent>(); appender.setContext(lc); appender.setEncoder(encoder); appender.start(); rootLogger.setLevel(Level.DEBUG); rootLogger.addAppender(appender); 

彼此分开的记录方法


这样,在调用该方法之前,登录可以将一种方法与另一种方法区分开来,将其名称保存在ThreadLocal Mapped Diagnostic Context中。 此外,在分析过程中,我们不会直接从MDC类获取这些值,因为日志记录代码将在另一个线程中执行,并且该数据也将不会存在-我们通过ILoggingEvent.getMDCPropertyMap()获得它们。


在一般情况下,正如vooft正确指出的那样,您需要支持调用堆栈,而不是覆盖MDC值,而是将其返回到前一帧,这是通过引入新的ThreadLocal 。 原理图示例:


  try { MDC.put(MDC_KEY_METHOD, currentMethod); // 1.  currentMethod    // 2.    // 3.       AOP, . } finally { String previousMethod = //     MDC.put(previousMethod); } 

每种方法都有自己的日志文件


让我们创建一个,不要忘记注册自己的附加程序:


  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<>(); //  ;  Encoder getOrCreateEncoderByMethod(LoggerContext lc, String method) { String pattern = patternByMethod.get(method); encoder = new PatternLayoutEncoder(); encoder.setContext(lc); encoder.setPattern(pattern); encoder.start(); return encoder; } 

每种方法都有自己的日志记录级别


MultiAppenderMultiAppender类添加一个检查:如果事件级别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) { // mdc = event.getMDCPropertyMap(); return "variable-expanded"; } } 

在常规配置过程中,注册处理程序:


  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


文学作品


官方登录手册

Source: https://habr.com/ru/post/zh-CN463601/


All Articles