Separate Methodenprotokollierung in Java / Logback

Bart schreibt ein paar Protokolle


Herausforderung


Angenommen, wir wollten jede Methode einer bestimmten Java-Klasse anders protokollieren:


  • Jede Methode hat ihre eigene Protokolldatei.
  • ... Ihr Protokollformat,
  • ... sein Mindestmaß an Protokollierung,
  • Wir erweitern das Protokollformat mit unseren eigenen % .
  • Möglichkeit, diese Konfiguration im laufenden Betrieb zu aktualisieren.

Dieser Artikel zeigt, wie diese Anforderungen erfüllt werden. Um die Einfachheit aufrechtzuerhalten, wird die Trennung der Protokollierung nur durch Methoden durchgeführt; In der Realität möchten Sie möglicherweise eine hierarchisch qualifizierende Konfiguration wie .... Ein Link zum vollständigen Quellcode befindet sich unten.


Client-Code


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

Logback


Für die Implementierung wurde eine solide "Logback" -Protokollierungsbibliothek ausgewählt, die interessante Anpassungsmöglichkeiten bietet:


 ch.qos.logback:logback-classic:1.2.3 

Es wird sowohl aus einer XML-Konfiguration als auch direkt aus Java konfiguriert. Ansätze können kombiniert werden:


  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); //  } 

Kurz zur Protokollierung:


  1. Der Programmierer zieht den Logger ,
  2. Der Logger zieht die ihm zugewiesenen Appender ,
  3. Der Appender denkt und ruft den Encoder an ,
  4. Der Encoder formatiert genau eine Zeile des Protokolls.
  5. Dazu zieht er eine Kette von Konvertern , von denen jeder seine % enthüllt.
  6. Erfolg.

Der Einfachheit halber wurde eine reine Java-Konfiguration gewählt. Hier ist alles ziemlich offensichtlich, wenn Sie die XML-Konfiguration berücksichtigen. Die Hauptaufgabe besteht darin, einen eigenen Appender / Encoder zu erstellen und diese zu registrieren - sie werden vom Logback aus ihrem Darm aufgerufen. Fast jedes Objekt, das Sie erstellen, muss gespeichert werden, um die start() -Methode verwenden zu können. Abstraktes Beispiel:


  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); 

Trennen Sie protokollierte Methoden voneinander


Damit der Logback eine Methode von einer anderen unterscheiden kann, speichern Sie vor dem Aufrufen der Methode ihren Namen im ThreadLocal Mapped Diagnostic Context. Während der Analyse erhalten wir diese Werte nicht direkt von der MDC Klasse, da der Protokollierungscode in einem anderen Thread ausgeführt wird und diese Daten nicht vorhanden sind. Wir erhalten sie über ILoggingEvent.getMDCPropertyMap() .


Im allgemeinen Fall müssen Sie, wie vooft richtig bemerkt hat, den Aufrufstapel unterstützen und den MDC-Wert nicht überschreiben, sondern zum vorherigen Frame zurückkehren, was durch die Einführung eines neuen ThreadLocal . Schematisches Beispiel:


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

Eigene Protokolldatei für jede Methode


Lassen Sie uns erstellen und vergessen Sie nicht, Ihren eigenen Appender zu registrieren:


  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); } 

Er selbst tut fast nichts, delegiert nur die Protokollierung an eine Reihe von echten Dateianhängern, einen für jede Methode. Delegiert an einen, der am besten geeignet ist. "Echte" Appender werden bei Bedarf wie folgt erstellt:


  fileAppender = new FileAppender<ILoggingEvent>(); fileAppender.setContext(lc); fileAppender.setAppend(false); fileAppender.setEncoder(getOrCreateEncoderByMethod(lc, method)); fileAppender.setFile(logFileByMethod.get(method)); fileAppender.start(); 

Eigenes Format für jede Methode


Behalten Sie dazu den Cache automatisch erstellter Objekte vom Typ 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; } 

Jede Methode hat ihre eigene Protokollierungsstufe


MultiAppender fügen der MultiAppender Klasse eine Prüfung MultiAppender : Wenn die Ereignisebene MultiAppender für die Methode angegebenen Schwellenwert MultiAppender , protokollieren wir sie nur:


  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); } 

Grundsätzlich kann diese Logik in den Filter eingefügt werden.


Erweitern Sie das Format mit Ihren Variablen


Um Ihren Garten nicht zu umzäunen, sondern die bewährte Logback-Infrastruktur zu nutzen, müssen Sie Ihre eigene Konverterklasse definieren und vollständig öffentlich sein, damit sie von außen instanziiert werden kann. Wenn Sie MDC benötigen, nehmen Sie es aus der Veranstaltung. Der %custom Variablenhandler beginnt hier:


  public class CustomConverter extends ClassicConverter { public String convert(ILoggingEvent event) { // mdc = event.getMDCPropertyMap(); return "variable-expanded"; } } 

Registrieren Sie während des allgemeinen Konfigurationsprozesses den Handler:


  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()); } 

Und wir werden als Encoder zum Beispiel PatternLayoutEncoder , der alles PatternLayoutEncoder . In diesem Fall wird die %custom Variable %custom in die Zeichenfolge "variable-expanded" .


Konfigurationsupdate im laufenden Betrieb


Es gibt eine solche Gelegenheit: Es reicht aus, die Konfiguratorfunktion erneut aufzurufen, ohne zu vergessen, LoggerContext::reset() und den akkumulierten Cache zu löschen.


Multithreading


Wenn die von uns konfigurierte Methode neue Threads zum Leben erweckt, gelten die angegebenen Protokollierungsregeln natürlich nicht für sie - Thread local'ys werden im neuen Thread nicht von selbst angezeigt. Wenn Sie also die Methodeneinstellungen auf einen neuen Stream anwenden möchten, müssen Sie den MDC dort kopieren:


  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"); } 

Ganzes Beispiel


https://github.com/zencd/logback-setup


Literatur


Offizielles Logback-Handbuch

Source: https://habr.com/ru/post/de463601/


All Articles