Journalisation des méthodes distincte en Java / logback

Bart écrit quelques journaux


Défi


Disons que nous voulions enregistrer différemment chaque méthode d'une certaine classe Java:


  • Chaque méthode a son propre fichier journal,
  • ... votre format de journal,
  • ... son niveau minimum de journalisation,
  • nous développons le format de journal avec nos propres % ,
  • possibilité de mettre à jour cette configuration à la volée.

Cet article montre comment remplir ces conditions. Afin de maintenir la simplicité, la séparation de la journalisation est effectuée uniquement par des méthodes; en réalité, vous voudrez peut-être avoir une configuration de qualification hiérarchique, comme ... Un lien vers le code source complet sera ci-dessous.


Code client


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

Déconnexion


Pour l'implémentation, une solide bibliothèque de journalisation "logback" a été sélectionnée, ce qui offre des possibilités de personnalisation intéressantes:


 ch.qos.logback:logback-classic:1.2.3 

Il est configuré à la fois à partir d'une configuration XML et directement à partir de Java, les approches peuvent être combinées:


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

En bref sur la journalisation:


  1. Le programmeur tire l' enregistreur ,
  2. L'enregistreur tire les appendices qui lui sont assignés,
  3. L'appendice pense et appelle l' encodeur ,
  4. L'encodeur formate exactement une ligne du journal,
  5. Pour ce faire, il tire une chaîne de convertisseurs dont chacun révèle sa % ,
  6. Succès.

Pour plus de simplicité, une configuration Java pure a été choisie. Tout est assez évident ici si vous gardez à l'esprit la configuration XML. La tâche principale est de créer votre propre appender / encodeur et de les enregistrer - ils seront appelés par la déconnexion de leurs entrailles. Presque chaque objet que vous créez doit être rappelé pour commencer à utiliser la méthode start() . Exemple abstrait:


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

Séparez les méthodes enregistrées les unes des autres


Pour que la déconnexion puisse distinguer une méthode d'une autre, avant d'appeler la méthode, enregistrez son nom dans le contexte de diagnostic ThreadLocal . De plus, pendant l'analyse, nous n'obtenons pas ces valeurs directement de la classe MDC , car le code de journalisation sera exécuté dans un autre thread et ces données ne seront pas là - nous les obtenons via ILoggingEvent.getMDCPropertyMap() .


Dans le cas général, comme vooft l'a correctement noté, vous devez prendre en charge la pile d'appels et ne pas écraser la valeur MDC, mais la ramener à l'image précédente, ce qui se fait par l'introduction d'un nouveau ThreadLocal . Exemple schématique:


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

Propre fichier journal pour chaque méthode


Créons et n'oubliez pas d'enregistrer votre propre appender:


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

Lui-même ne fait presque rien, ne délègue que la connexion à un pack de vrais ajouts de fichiers, un pour chaque méthode. Délégué à l'un, le plus approprié. Les "vrais" appenders sont créés à la demande, comme suit:


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

Propre format pour chaque méthode


Pour ce faire, conservez le cache des objets créés automatiquement de type 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; } 

Chaque méthode a son propre niveau de journalisation


MultiAppender ajoutons une vérification à la classe MultiAppender : si le niveau d'événement MultiAppender seuil spécifié pour la méthode, alors seulement nous l'enregistrons:


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

En principe, cette logique peut être mise dans le filtre.


Extension du format avec vos variables


Afin de ne pas clôturer votre jardin, mais pour profiter de l'infrastructure éprouvée de la déconnexion, vous devez définir votre propre classe de convertisseur, assurez-vous de la rendre entièrement publique afin qu'elle puisse être instanciée de l'extérieur. Si vous avez besoin de MDC , MDC -le de l'événement. Le gestionnaire de variable %custom commence ici:


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

Au cours du processus de configuration générale, enregistrez le gestionnaire:


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

Et nous utiliserons comme encodeur, par exemple, PatternLayoutEncoder , qui récupérera tout. Dans ce cas, la variable %custom se développera dans la chaîne "variable-expanded" .


Mise à jour de la configuration à la volée


Il y a une telle opportunité hors de la boîte: il suffit d'appeler à nouveau la fonction configurateur, sans oublier d'y faire LoggerContext::reset() et de vider le cache accumulé.


Multithreading


Si la méthode que nous avons configurée donne vie à de nouveaux threads, alors, bien sûr, les règles de journalisation spécifiées ne leur seront pas applicables - le thread local'ys n'apparaîtra pas d'eux-mêmes dans le nouveau thread. Donc, si vous souhaitez appliquer les paramètres de méthode à un nouveau flux, vous devez y copier le 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"); } 

Exemple entier


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


Littérature


Manuel officiel de reconnexion

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


All Articles