Log de método separado em Java / logback

Bart escrevendo alguns logs


Desafio


Suponha que desejássemos registrar cada método de uma determinada classe Java de maneira diferente:


  • Cada método possui seu próprio arquivo de log,
  • ... seu formato de log,
  • ... seu nível mínimo de registro,
  • expandimos o formato do log com nossas próprias % ,
  • capacidade de atualizar essa configuração em tempo real.

Este artigo mostra como atender a esses requisitos. Para manter a simplicidade, a separação do log é realizada apenas por métodos; na realidade, você pode querer ter uma configuração hierárquica qualificada, como ... Um link para o código fonte completo estará abaixo.


Código do cliente


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

Logback


Para a implementação, uma sólida biblioteca de log "logback" foi selecionada, o que fornece possibilidades interessantes para personalização:


 ch.qos.logback:logback-classic:1.2.3 

Ele é configurado a partir de uma configuração XML e diretamente de Java. As abordagens podem ser combinadas:


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

Brevemente sobre o log:


  1. O programador puxa o logger ,
  2. O logger puxa os anexos atribuídos a ele,
  3. O appender pensa e chama o codificador ,
  4. O codificador formata exatamente uma linha do log,
  5. Para fazer isso, ele puxa uma cadeia de conversores , cada um dos quais revela sua % ,
  6. Sucesso.

Para simplificar, foi escolhida uma configuração Java pura. Tudo é bastante óbvio aqui, se você tiver em mente a configuração XML. A principal tarefa é criar seu próprio appender / codificador e registrá-los - eles serão chamados pelo logback de suas entranhas. Quase todos os objetos que você cria devem ser lembrados para começar a usar o método start() . Exemplo abstrato:


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

Separar métodos registrados um do outro


Para que o logback possa distinguir um método do outro, antes de chamar o método, salve seu nome no Contexto de Diagnóstico Mapeado ThreadLocal . Além disso, durante a análise, não obtemos esses valores diretamente da classe MDC , pois o código de log será executado em outro encadeamento e esses dados não estarão lá - os obtemos através de ILoggingEvent.getMDCPropertyMap() .


No caso geral, como a vooft observou corretamente, você precisa dar suporte à pilha de chamadas e não substituir o valor MDC, mas retorná-lo ao quadro anterior, o que é feito através da introdução de um novo ThreadLocal . Exemplo esquemático:


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

Arquivo de log próprio para cada método


Vamos criar e não se esqueça de registrar seu próprio 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); } 

Ele próprio quase não faz nada, apenas delega o log para um pacote de anexadores de arquivos reais, um para cada método. Delegado a um, o mais adequado. Apêndices "reais" são criados sob demanda, da seguinte maneira:


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

Formato próprio para cada método


Para fazer isso, mantenha o cache de objetos criados automaticamente do tipo 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; } 

Cada método possui seu próprio nível de log


Adicionamos uma verificação à classe MultiAppender : se o nível do evento MultiAppender limite especificado para o método, apenas o registraremos:


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

Em princípio, essa lógica pode ser colocada no filtro.


Estendendo o formato com suas variáveis


Para não cercar seu jardim, mas para tirar proveito da infraestrutura comprovada de logback, você precisa definir sua própria classe de conversores, certifique-se de publicamente para que ele possa ser instanciado do lado de fora. Se você precisar do MDC , tire-o do evento. O manipulador de variável %custom começa aqui:


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

Durante o processo de configuração geral, registre o manipulador:


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

E usaremos como codificador, por exemplo, PatternLayoutEncoder , que coletará tudo. Nesse caso, a variável %custom será expandida para a cadeia "variable-expanded" .


Atualização de configuração em tempo real


Existe essa oportunidade LoggerContext::reset() para LoggerContext::reset() basta chamar a função configurador novamente, não esquecendo de fazer LoggerContext::reset() e limpar o cache acumulado.


Multithreading


Se o método configurado por nós reviver novos encadeamentos, é claro que as regras de log especificadas não se aplicarão a eles - os locais de encadeamentos não aparecerão sozinhos no novo encadeamento. Portanto, se você deseja aplicar as configurações do método a um novo fluxo, é necessário copiar o MDC lá:


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

Exemplo inteiro


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


Literatura


Manual Oficial de Logback

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


All Articles