
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...");
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();
Kurz zur Protokollierung:
- Der Programmierer zieht den Logger ,
- Der Logger zieht die ihm zugewiesenen Appender ,
- Der Appender denkt und ruft den Encoder an ,
- Der Encoder formatiert genau eine Zeile des Protokolls.
- Dazu zieht er eine Kette von Konvertern , von denen jeder seine
%
enthüllt. - 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();
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);
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();
Behalten Sie dazu den Cache automatisch erstellter Objekte vom Typ Encoder
:
Map<String, String> patternByMethod = new HashMap<>();
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.
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) {
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