Eclair - Biblioteca de log declarativa do Java Spring



Há muitas perguntas sobre o trabalho dos serviços nos estágios de desenvolvimento, teste e suporte, e todas elas, à primeira vista, são diferentes: "O que aconteceu?" , "Houve um pedido?" , "Qual é o formato da data?" , "Por que o serviço não está respondendo?" etc.

Um log compilado corretamente poderá responder em detalhes a essas e muitas outras perguntas de forma absolutamente autônoma, sem a participação dos desenvolvedores. Na busca de um objetivo tão tentador, nasceu a biblioteca de registros Eclair, projetada para dialogar com todos os participantes do processo sem puxar muitos cobertores.

Sobre o cobertor e os recursos da solução - abaixo.

Qual é o problema do log


Se você não está muito interessado em entender as instalações, pode proceder imediatamente à descrição da nossa solução .

  • O log do aplicativo é seu álibi.
    Na maioria das vezes, apenas ele pode provar o sucesso do aplicativo. Não há estado em um microsserviço; os sistemas adjacentes são móveis e exigentes. “Repita”, “recrie”, “verifique novamente” - tudo isso é difícil e / ou impossível. O log deve ser informativo o suficiente para responder à pergunta: "O que aconteceu?" A qualquer momento . . O log deve ser claro para todos: o desenvolvedor, o testador, às vezes o analista, às vezes o administrador, às vezes a primeira linha de suporte - tudo acontece.
  • Os microsserviços são sobre multithreading.
    Solicitações que chegam ao serviço (ou dados solicitados pelo serviço) geralmente são processadas por vários encadeamentos. O log de todos os threads geralmente é misto. Deseja distinguir entre threads paralelos e entre threads "sequenciais"? O mesmo fluxo é reutilizado para processamento seqüencial de pedidos, executando repetidamente sua própria lógica para diferentes conjuntos de dados. Esses fluxos "sequenciais" de outro plano, mas seus limites devem ser claros para o leitor.
  • O log deve salvar o formato de dados original.
    Se, na realidade, os serviços são trocados por XML, o log correspondente deve armazenar XML. Nem sempre é compacto e nem sempre bonito (mas conveniente). É mais fácil ver o sucesso, mais fácil analisar a falha. Em alguns casos, o log pode ser usado para reproduzir ou reprocessar manualmente a solicitação.
  • Parte dos dados no log requer um relacionamento especial.
    Os dados recebidos (solicitações), dados enviados (respostas), solicitações para sistemas de terceiros e respostas deles geralmente precisam ser armazenados separadamente. Eles estão sujeitos a requisitos especiais: por prazo de validade ou confiabilidade. Além disso, esses dados podem ter uma quantidade impressionante em comparação com uma linha de log típica.
  • Parte dos dados não é para o log.
    Normalmente, o seguinte deve ser excluído do log regular: dados binários (matrizes de bytes, base64, ..), dados pessoais de clientes / parceiros / outros indivíduos e entidades legais. É sempre uma história individual, mas sistemática e não se presta ao controle manual.

Por que não mãos


Pegue o org.slf4j.Logger (faça org.slf4j.Logger com anexos de qualquer processo) e escreva tudo o que é necessário para o log. As entradas para os principais métodos, saídas, se necessário, refletem erros detectados, alguns dados. Isso é necessário? Sim mas:

  • A quantidade de código está crescendo de maneira irracional (incomum). No início, isso não é muito impressionante, se você registrar apenas o mais básico (suporte bem-sucedido, a propósito, com essa abordagem).
  • Ligar para o registrador com as mãos rapidamente se torna preguiça. Declarar um campo static com um logger é muito preguiçoso (bem, o Lombok pode fazer isso por nós). Nós desenvolvedores somos preguiçosos. E ouvimos nossa preguiça, isso é nobre preguiça: está mudando persistentemente o mundo para melhor.
  • Os microsserviços não são bons por todos os lados. Sim, são pequenos e bonitos, mas há um outro lado: são muitos! Um único aplicativo do início ao fim geralmente é escrito por um desenvolvedor. O legado não aparece diante de seus olhos. Feliz, não sobrecarregado com as regras impostas, o desenvolvedor considera um dever inventar seu próprio formato de log, seu princípio e suas próprias regras. Então, implementa brilhantemente a invenção. Cada classe é diferente. Isso é um problema? Colossal.
  • A refatoração quebrará seu log. Mesmo a onipotente Ideia não o salvará. Atualizar o log é tão impossível quanto atualizar o Javadoc. Ao mesmo tempo, pelo menos o Javadoc é lido apenas pelos desenvolvedores (não, ninguém lê), mas o público dos logs é muito maior e a equipe de desenvolvimento não é limitada.
  • O MDC (Mapped Diagnostic Context) é parte integrante de um aplicativo multiencadeado. O enchimento manual do MDC requer limpeza oportuna no final do trabalho no fluxo. Caso contrário, você corre o risco de vincular um ThreadLocal a dados não relacionados. Mãos e olhos para controlar isso, ouso dizer, é impossível.

E é assim que resolvemos esses problemas em nossos aplicativos.

O que é o Eclair e o que ele pode fazer


Eclair é uma ferramenta que simplifica a escrita do código registrado. Ajuda a coletar as meta-informações necessárias sobre o código-fonte, associá-lo aos dados que voam no aplicativo em tempo de execução e enviá-lo para o repositório de log usual, enquanto gera um mínimo de código.

O objetivo principal é tornar o log compreensível para todos os participantes do processo de desenvolvimento. Portanto, a conveniência de escrever código, os benefícios do Eclair não terminam, mas apenas começam.

O Eclair registra métodos e parâmetros anotados:

  • registra a entrada / saída do método a partir do método / exceções / argumentos / valores retornados pelo método
  • filtra exceções para registrá-las especificamente em tipos: somente quando necessário
  • varia os "detalhes" do log, com base nas configurações do aplicativo para o local atual: por exemplo, no caso mais detalhado, ele imprime os valores dos argumentos (todos ou alguns), na versão mais curta - apenas o fato de inserir o método
  • imprime dados como JSON / XML / em qualquer outro formato (pronto para trabalhar com Jackson, JAXB pronto para uso): entende qual formato é o mais preferível para um parâmetro específico
  • entende SpEL (Spring Expression Language) para instalação declarativa e limpeza automática do MDC
  • escreve para N loggers, o “logger” no entendimento do Eclair é um bean no contexto que implementa a interface EclairLogger : você pode especificar o logger que deve processar a anotação por nome, alias ou padrão
  • informa ao programador sobre alguns erros no uso de anotações: por exemplo, a Eclair sabe que funciona em proxies dinâmicos (com todos os recursos subsequentes); portanto, pode sugerir que a anotação no método private nunca funcionará
  • aceita meta-anotações (como o Spring as chama): você pode definir suas anotações para log, usando algumas anotações básicas - para reduzir o código
  • capaz de mascarar dados “sensíveis” ao imprimir: XML de proteção XPath pronto para uso
  • grava um log no modo "manual", define o invocador e "expande" os argumentos que implementam o Supplier : dando a oportunidade de inicializar os argumentos "preguiçosamente"

Como conectar o Eclair


O código fonte é publicado no GitHub sob a licença Apache 2.0.

Para se conectar, você precisa do Java 8, Maven e Spring Boot 1.5+. Artefato hospedado pelo Maven Central Repository:

 <dependency> <groupId>ru.tinkoff</groupId> <artifactId>eclair-spring-boot-starter</artifactId> <version>0.8.3</version> </dependency> 

O starter contém uma implementação padrão do EclairLogger , que usa um sistema de registro inicializado pelo Spring Boot com algum conjunto verificado de configurações.

Exemplos


Aqui estão alguns exemplos de uso típico da biblioteca. Primeiro, é fornecido um fragmento de código e, em seguida, o log correspondente, dependendo da disponibilidade de um determinado nível de log. Um conjunto mais completo de exemplos pode ser encontrado no Wiki do projeto na seção Exemplos .

Exemplo mais simples


O nível padrão é DEBUG.

 @Log void simple() { } 
Se o nível estiver disponível... então o log ficará assim
TRACE
DEBUG
DEBUG [] rteeExample.simple >
DEBUG [] rteeExample.simple <
INFO
WARN
ERROR
-

Os detalhes do log dependem do nível de log disponível.


O nível de registro disponível no local atual afeta os detalhes do registro. Quanto menor o nível disponível (ou seja, quanto mais próximo do TRACE), mais detalhado o registro.

 @Log(INFO) boolean verbose(String s, Integer i, Double d) { return false; } 
NívelLog
TRACE
DEBUG
INFO [] rteeExample.verbose > s="s", i=4, d=5.6
INFO [] rteeExample.verbose < false
INFOINFO [] rteeExample.verbose >
INFO [] rteeExample.verbose <
WARN
ERROR
-

Ajuste de log de exceção


Tipos de exceções registradas podem ser filtrados. Exceções selecionadas e seus descendentes serão dados em penhor. Neste exemplo, NullPointerException será registrado no nível WARN, Exception no nível ERROR (por padrão) e Error não será registrado (porque Error não Error incluído no filtro da primeira anotação @Log.error e é explicitamente excluído do filtro da segunda anotação).

 @Log.error(level = WARN, ofType = {NullPointerException.class, IndexOutOfBoundsException.class}) @Log.error(exclude = Error.class) void filterErrors(Throwable throwable) throws Throwable { throw throwable; } //       filterErrors(new NullPointerException()); filterErrors(new Exception()); filterErrors(new Error()); 
NívelLog
TRACE
DEBUG
INFO
WARN
WARN [] rteeExample.filterErrors ! java.lang.NullPointerException
java.lang.NullPointerException: null
at rteeExampleTest.filterErrors(ExampleTest.java:0)
..
ERROR [] rteeExample.filterErrors ! java.lang.Exception
java.lang.Exception: null
at rteeExampleTest.filterErrors(ExampleTest.java:0)
..
ERRORERROR [] rteeExample.filterErrors ! java.lang.Exception
java.lang.Exception: null
at rteeExampleTest.filterErrors(ExampleTest.java:0)
..

Defina cada parâmetro separadamente


 @Log.in(INFO) void parameterLevels(@Log(INFO) Double d, @Log(DEBUG) String s, @Log(TRACE) Integer i) { } 
NívelLog
TRACEINFO [] rteeExample.parameterLevels > d=9.4, s="v", i=7
DEBUGINFO [] rteeExample.parameterLevels > d=9.4, s="v"
INFOINFO [] rteeExample.parameterLevels > 9.4
WARN
ERROR
-

Selecione e personalize o formato de impressão


As “impressoras” responsáveis ​​pelo formato de impressão podem ser configuradas pelos pré e pós-processadores. No exemplo acima, maskJaxb2Printer configurado para que os elementos correspondentes à expressão XPath "//s" sejam mascarados usando "********" . Ao mesmo tempo, jacksonPrinter imprime Dto "como está".

 @Log.out(printer = "maskJaxb2Printer") Dto printers(@Log(printer = "maskJaxb2Printer") Dto xml, @Log(printer = "jacksonPrinter") Dto json, Integer i) { return xml; } 
NívelLog
TRACE
DEBUG
DEBUG [] rteeExample.printers >
xml=<dto><i>5</i><s>********</s></dto>, json={"i":5,"s":"password"}
DEBUG [] rteeExample.printers <
<dto><i>5</i><s>********</s></dto>
INFO
WARN
ERROR
-

Vários registradores no contexto


O método é registrado usando vários registradores ao mesmo tempo: por padrão, registrador (anotado usando @Primary ) e auditLogger. Você pode definir vários registradores se desejar separar os eventos registrados, não apenas por nível (TRACE - ERROR), mas também enviá-los para diferentes armazenamentos. Por exemplo, o criador de logs principal pode gravar um log em um arquivo no disco usando slf4j, e o auditLogger pode gravar uma fatia de dados especial em um excelente armazenamento (por exemplo, em Kafka) em seu próprio formato específico.

 @Log @Log(logger = "auditLogger") void twoLoggers() { } 

Gerenciamento MDC


Os MDCs configurados usando a anotação são excluídos automaticamente após a saída do método anotado. Um valor de registro MDC pode ser calculado dinamicamente usando SpEL. A seguir, exemplos: uma sequência estática percebida por uma constante, avaliando a expressão 1 + 1 , chamando o jacksonPrinter , chamando o método static randomUUID .
Os MDCs com o atributo global = true não são excluídos após a saída do método: como você pode ver, o único registro restante no MDC até o final do log é a sum .

 @Log void outer() { self.mdc(); } @Mdc(key = "static", value = "string") @Mdc(key = "sum", value = "1 + 1", global = true) @Mdc(key = "beanReference", value = "@jacksonPrinter.print(new ru.tinkoff.eclair.example.Dto())") @Mdc(key = "staticMethod", value = "T(java.util.UUID).randomUUID()") @Log void mdc() { self.inner(); } @Log.in void inner() { } 

Faça logon ao executar o código acima:
DEBUG [] rteeExample.outer >
DEBUG [beanReference={"i":0,"s":null}, sum=2, static=string, staticMethod=01234567-89ab-cdef-ghij-klmnopqrstuv] rteeExample.mdc >
DEBUG [beanReference={"i":0,"s":null}, sum=2, static=string, staticMethod=01234567-89ab-cdef-ghij-klmnopqrstuv] rteeExample.inner >
DEBUG [beanReference={"i":0,"s":null}, sum=2, static=string, staticMethod=01234567-89ab-cdef-ghij-klmnopqrstuv] rteeExample.mdc <
DEBUG [sum=2] rteeExample.outer <


Instalação do MDC com base em parâmetros


Se você especificar o MDC usando a anotação no parâmetro, o parâmetro anotado estará disponível como o objeto raiz do contexto de avaliação. Aqui "s" é um campo da classe Dto com o tipo String .

 @Log.in void mdcByArgument(@Mdc(key = "dto", value = "#this") @Mdc(key = "length", value = "s.length()") Dto dto) { } 

Faça logon ao executar o código acima:
DEBUG [length=8, dto=Dto{i=12, s='password'}] rteeExample.mdcByArgument > dto=Dto{i=12, s='password'}

Registro manual


Para o registro "manual", basta implementar a implementação do ManualLogger . Os argumentos passados ​​que implementam o Supplier da interface serão "expandidos" somente se necessário.

 @Autowired private ManualLogger logger; @Log void manual() { logger.info("Eager logging: {}", Math.PI); logger.debug("Lazy logging: {}", (Supplier) () -> Math.PI); } 
NívelLog
TRACE
DEBUG
DEBUG [] rteeExample.manual >
INFO [] rteeExample.manual - Eager logging: 3.141592653589793
DEBUG [] rteeExample.manual - Lazy logging: 3.141592653589793
DEBUG [] rteeExample.manual <
INFOINFO [] rteeExample.manual - Eager logging: 3.141592653589793
WARN
ERROR
-

O que a Eclair não faz


A Eclair não sabe onde você armazenará seus logs, por quanto tempo e em detalhes. O Eclair não sabe como você planeja usar seu log. A Eclair extrai cuidadosamente do seu aplicativo todas as informações necessárias e as redireciona para o armazenamento que você configurou.

Um exemplo de configuração do EclairLogger direcionando um log para um logback com um Appender específico:

 @Bean public EclairLogger eclairLogger() { LoggerFacadeFactory factory = loggerName -> { ch.qos.logback.classic.LoggerContext context = (LoggerContext) LoggerFactory.getILoggerFactory(); ch.qos.logback.classic.Logger logger = context.getLogger(loggerName); // Appender<ILoggingEvent> appender = ? // logger.addAppender(appender); return new Slf4JLoggerFacade(logger); }; return new SimpleLogger(factory, LoggingSystem.get(SimpleLogger.class.getClassLoader())); } 

Esta solução não é para todos.


Antes de começar a usar o Eclair como a principal ferramenta de registro, você deve se familiarizar com vários recursos desta solução. Esses "recursos" devem-se ao fato de o Eclair ser baseado no mecanismo de proxy padrão do Spring.

- A velocidade de execução do código agrupado no próximo proxy é insignificante, mas diminui. Para nós, essas perdas raramente são significativas. Se surgir a questão de reduzir o lead time, existem muitas medidas eficazes de otimização. Recusar um registro informativo conveniente pode ser considerado uma das medidas, mas não em primeiro lugar.

- StackTrace "incha" um pouco mais. Se você não está acostumado com os proxies stackTrace of Spring longos, isso pode ser um incômodo para você. Por um motivo igualmente óbvio, a depuração de classes com proxy será difícil.

- Nem todas as classes e todos os métodos podem ser proxy : métodos private não podem ser proxy, você precisará registrar a cadeia de métodos em um bean, você não pode proxy qualquer coisa que não seja um bean, etc.

No final


É completamente claro que essa ferramenta, como qualquer outra, deve poder ser usada para se beneficiar dela. E esse material ilumina superficialmente apenas o lado em que decidimos avançar em busca da solução perfeita.

Críticas, pensamentos, sugestões, links - congratulo-me com qualquer participação de vocês na vida do projeto! Ficaria feliz se você achar a Eclair útil para seus projetos.

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


All Articles