Se você quiser fazer bem as coisas, primeiro afie suas ferramentas.
Muitos programadores podem esquecer o quão importante é registrar o comportamento das aplicações. Ao encontrar bugs simultâneos causados por alta pressão em ambientes multithread, você pode entender a importância de registrar logs.
Algumas pessoas ficaram muito felizes em adicionar esta frase ao código:
log.info ("Loging feliz e despreocupado");
Ele pode nem perceber a importância dos registros de aplicativos em manutenção, ajuste e identificação de falhas. Eu acho que o SLF4J é a melhor API de registro, principalmente porque suporta uma ótima maneira de injetar esquema:
log.debug ("encontrado {} registra filtro correspondente: '{}'", registros, filtro);
Para Log4J, você só pode fazer isso:
log.debug ("encontrado" + registros + "filtro de amatcaria de registros: '" + filtro + "'");
Esta escrita não é apenas mais prolongada e baixa legibilidade, mas também tem uma emenda de cordas que afeta a eficiência (quando esse nível não requer saída).
O SLF4J apresenta o recurso de injeção {} e, como o splicing de string é evitado toda vez, o método do toque não será chamado e não há necessidade de adicionar isdebugenabled. SLF4J é uma aplicação do modo de aparência, é apenas uma fachada. Para implementação específica, recomendo a estrutura de logback. Eu já o anunciei uma vez antes, em vez do log4j já completo. Tem muitos recursos interessantes. Ao contrário do LOG4J, ele ainda está sob desenvolvimento e melhoria ativos.
Outra ferramenta a recomendar é o perf4j:
Perf4j é para system.currenttimemillis () como log4j é para system.out.println ()
Assim como o Log4J é uma alternativa melhor ao System.out.println, o perf4j é mais como um substituto para o System.currenttimemillis (). Introduzi o Perf4J em um projeto e observei como ele funciona sob cargas altas. Administradores e usuários empresariais ficam surpresos com os belos gráficos fornecidos por este gadget. Podemos ver problemas de desempenho a qualquer momento. Perf4j deve ser um artigo especial para falar. Agora, você pode primeiro olhar para o seu guia do desenvolvedor. Há também um Ceki Gülcü (criador do projeto LOG4J, SLF4J e LOGBACK) que fornece uma maneira fácil de remover dependências para o commons.
Não se esqueça do nível de log
Toda vez que você deseja adicionar uma linha de logs, você pensa, qual nível de log deve ser usado aqui? Cerca de 90% dos programadores não prestam muita atenção a esse problema. Eles usam um nível para registrar logs, geralmente informações ou depuração. Por que?
A estrutura de log tem duas vantagens principais em comparação com o sistema.out: classificação e nível. Ambos permitem filtrar seletivamente logs, permanentemente ou apenas ao solucionar erros de solução de problemas.
Erro: ocorreu um erro grave e deve ser tratado imediatamente. Esse nível de erro é intolerável a qualquer sistema. Por exemplo: exceção do ponteiro nulo, o banco de dados não está disponível e os casos de uso de caminhos críticos não podem continuar sendo executados.
AVISO: O processo subsequente continuará, mas deve ser levado a sério. Na verdade, espero que haja dois níveis aqui: um é o problema óbvio com a solução (como "os dados atuais não estão disponíveis, usando dados em cache"), e o outro é o problema e as sugestões em potencial (como "o programa é executado no modo de desenvolvimento" ou "a senha do console de gerenciamento não é suficientemente segura". Os aplicativos podem tolerar essas informações, mas devem ser verificados e fixos.
Debug: Com o que os desenvolvedores estão preocupados. Mais tarde, falarei sobre o que as coisas devem ser registradas nesse nível.
Rastreio: informações mais detalhadas, usadas apenas no estágio de desenvolvimento. Você ainda pode precisar prestar atenção a essas informações dentro de um curto período de tempo após o lançamento do produto, mas esses registros de log são apenas temporários e devem ser desligados eventualmente. É difícil distinguir a diferença entre depuração e rastreamento, mas se você adicionar uma linha de logs e excluí -lo após o desenvolvimento e o teste, o log deverá estar no nível do rastreamento.
A lista acima é apenas uma sugestão, você pode registrar de acordo com suas próprias regras, mas é melhor ter certas regras. Minha experiência pessoal é: não filtre os logs no nível do código, mas use o nível de log correto para filtrar rapidamente as informações desejadas, o que pode economizar muito tempo.
A última coisa a dizer é que esta declaração condicional habilitada é*habilitada. Algumas pessoas gostam de adicionar isso antes de cada log:
if (log.isdebugenabled ()) log.debug ("local para o seu comercial");Pessoalmente, acho que você deve evitar adicionar essa coisa confusa ao código. O desempenho não parece ser muito melhorado (especialmente após o uso do SLF4J), o que é mais como uma otimização prematura. Além disso, você não acha isso um pouco redundante? Raramente, essa declaração explícita de julgamento é explicitamente necessária, a menos que prove que a construção de mensagens de log é muito cara. Caso contrário, você pode se lembrar do que deveria e deixar a estrutura de log se preocupar com isso.
Você sabe o que está gravando?
Toda vez que você escrever uma linha de logs, reserve um momento para ver o que você está imprimindo no arquivo de log. Leia seu log e descubra onde está a exceção. Primeiro, pelo menos evite exceções de ponteiro nulo:
log.debug ("Solicitação de processamento com ID: {}", request.getId ());
Você confirmou que a solicitação não é nula?
As coleções de gravação também são um grande poço. Se você usar o Hibernate para obter uma coleção de objetos de domínio do banco de dados, acidentalmente o escreverá: log.debug ("Retornando usuários: {}", usuários);
O SLF4J chamará apenas o método da tostragem quando essa declaração for realmente impressa, é claro que isso é legal. No entanto, se a memória transbordar, os problemas de seleção de N+1, fome de fome até a morte, exceção de inicialização atrasada, o espaço de armazenamento de log acaba ... tudo isso pode acontecer. A melhor maneira é gravar apenas o ID do objeto (ou apenas o tamanho da coleção). No entanto, a coleta de IDs requer chamar o método GetID para cada objeto, o que realmente não é uma tarefa fácil em Java. Groovy tem um ótimo operador de expansão (usuários*.id). Em Java, podemos usar a biblioteca do Commons Beanutils para simular:
log.debug ("retornando IDs de usuário: {}", colecione (usuários, "id"));
Provavelmente é assim que o método de coleta é implementado:
Coleção estática pública Collect (coleção, String PropertyName) {return collectionUtils.Collect (coleção, New BeantopropertyValuetransformer (PropertyName));}Finalmente, o método da tostragem não pode ser implementado corretamente ou usado.
Primeiro, para registrar, existem muitas maneiras de criar uma toque para cada classe. É melhor usar o ToStringBuilder para gerar (mas não a versão de sua implementação de reflexão).
Segundo, preste atenção às matrizes e conjuntos atípicos. A implementação da tostragem de matrizes e algumas coleções alternativas podem não chamar o método de tostragem de cada elemento um por um. O método das matrizes#DeepToString fornecido pelo JDK pode ser usado. Verifique os logs que você imprimiu para ver se há alguma informação sobre a exceção do formato.
Evite efeitos colaterais
A impressão de log geralmente não tem muito impacto no desempenho do programa. Recentemente, um amigo meu lançou uma exceção de Hibernate LazyinitializationException em um sistema em algumas plataformas especiais. Como você deve ter adivinhado com isso, algumas impressões de log que resultam em atraso nas coleções de inicialização sendo carregadas quando a sessão está conectada. Nesse caso, se o nível de log for aumentado, a coleção não será mais inicializada. Se você não conhece essas informações de contexto, quanto tempo você levará para descobrir esse bug.
Outro efeito colateral é que ele afeta a velocidade de execução do programa. Uma resposta rápida para esta pergunta: se os logs forem impressos demais ou a tostragem e a emenda de cordas não forem usadas corretamente, a impressão de log terá um impacto negativo no desempenho. Quão grande pode ser? Bem, eu vi um programa reiniciar a cada 15 minutos, porque muitos registros fazem com que os tópicos morriam de fome. Este é o efeito colateral! Pela minha experiência, imprimir cem megabytes em uma hora é quase o limite superior.
Obviamente, se o processo de negócios for abortado devido a exceções de impressão de log, esse efeito colateral será ótimo. Costumo ver pessoas escrevendo isso para evitar isso:
tente {log.trace ("id =" + request.getUser (). getid () + "accesses" + gerente.getpage (). geturl (). tostring ())} catch (nullpointerException e) {}Este é um pedaço de código real, mas para tornar o mundo mais purificado, não escreva assim.
Descrever claramente
Cada registro de log contém dados e descrição. Dê uma olhada neste exemplo:
log.debug ("message processado"); log.debug (message.getjmsMessageId ()); log.debug ("mensagem com id '{}' processado", message.getjmsMessageId ()); Ao solucionar erros em um sistema desconhecido, que tipo de log você prefere ver? Confie em mim, esses exemplos são comuns. Há também um modo negativo:
if (Instância da mensagem do texto textMessage)
// ...
outro
log.warn ("tipo de mensagem desconhecido");
É difícil adicionar tipos de mensagens, IDs de mensagem etc. a este log de aviso? Eu sei que ocorreu um erro, mas o que é? Qual é a informação do contexto?
O terceiro exemplo negativo é "log mágico". Um exemplo real: muitos programadores da equipe sabem que 3 ≥ números a seguir! O número seguido de um número #, seguido de um log de número de número pseudo-aleatório significa "a mensagem com id xyz foi recebida". Ninguém quer alterar este log. Se alguém digitasse o teclado e selecionasse uma string "&&!#" Exclusiva, ele encontraria rapidamente as informações que queria.
O resultado é que todo o arquivo de log parece uma grande série de caracteres aleatórios. Algumas pessoas não podem deixar de se perguntar se este é um programa PERL.
Os arquivos de log devem ser legíveis, claros e auto-descritos. Não use números mágicos, valores de gravação, números, IDs e seu contexto. Registre os dados processados e seu significado. Registre o que o programa está fazendo. Um bom log deve ser um bom documento do código do programa.
Eu mencionei para não imprimir uma senha e ter informações pessoais? Eu acredito que não há um programador tão estúpido.
Ajuste seu formato
O formato de log é uma ferramenta muito útil, que invisivelmente adiciona informações valiosas de contexto ao log. Mas você deve pensar claramente sobre que tipo de informação está incluída em seu formato. Por exemplo, não faz sentido gravar datas em logs escritos a cada ciclo horário, porque seu nome de log já contém essas informações. Pelo contrário, se você não gravar o nome do thread, quando dois threads funcionarem em paralelo, não poderá rastrear os threads através dos logs - os logs se sobrepõem juntos. Em um aplicativo único, não há problema em fazer isso, mas isso já é coisa do passado.
Pela minha experiência, o formato de log ideal deve incluir (exceto as informações do log em si, é claro): hora atual (sem data, precisão de milissegundos), nível de log, nome do thread, nome de log simples (não com nome completo) e mensagens. Em Logback, será assim:
<Appender name = "stdout"> <coder> <dattern>%d {hh: mm: ss.ss}%-5level [%thread] [%logger {0}]%m%n </storking> </coder> </ppender>Nomes de arquivos, nomes de classe, números de linha, não precisam ser listados, embora pareçam úteis. Eu também vi logotging vazio no código:
log.info (""); Como o programador acredita que o número da linha fará parte do formato de log e sabe que, se a mensagem de log vazio aparecer na linha 67 do arquivo, isso significa que o usuário foi autenticado. Além disso, a gravação de nomes de nomes da classe ou os números de linha têm um grande impacto no desempenho.
Um recurso mais avançado da estrutura de registro é o contexto de diagnóstico mapeado. O MDC é apenas um mapa que é local para encadear. Você pode colocar qualquer pares de valor-chave neste mapa, para que todos os registros de log deste thread possam obter informações correspondentes deste mapa como parte do formato de saída.
Registre os parâmetros e os valores de retorno do método
Se você encontrar um bug no estágio de desenvolvimento, geralmente usa um depurador para rastrear o motivo específico. Agora, digamos que você não usará mais o depurador. Por exemplo, como esse bug apareceu no ambiente do usuário há alguns dias, tudo o que você pode obter são alguns logs. O que você pode descobrir com isso?
Se você seguir o princípio simples de imprimir parâmetros de entrada e saída de cada método, não precisará de um depurador. Obviamente, cada método pode acessar sistemas externos, bloquear, esperar etc., e estes devem ser levados em consideração. Basta consulte o seguinte formato:
public String printDocument (documento doc, modo de modo) {log.debug ("Digindo printDocument (doc = {}, mode = {})", doc, modo); String id = ...;; // Log de operação de impressão longa.debug ("Deixando printDocument (): {}", id); Retornar id;}Como você registra os logs no início e no final do método, você pode encontrar códigos ineficientes manualmente e até detectar causas que podem causar impas a deadrafícios e fome - você só precisa ver se não há como "sair" depois de "entrar". Se o significado do nome do seu método for claro, será uma coisa agradável limpar o log. Da mesma forma, analisar exceções é mais fácil porque você sabe o que está fazendo em cada etapa. Se houver muitos métodos para gravar no código, você poderá usar seções AOP para concluí -lo. Isso reduz o código duplicado, mas você precisa ter muito cuidado ao usá -lo e, se não tiver cuidado, pode levar a uma grande quantidade de saída de log.
Os níveis mais adequados para esse tipo de tronco são depuração e rastreamento. Se você achar que um método é chamado com muita frequência e a gravação de seu log pode afetar o desempenho, você só precisa diminuir seu nível de log ou excluir o log diretamente (ou apenas um de todo o método chama?) No entanto, muitos logs são melhores que menos. Pense no registro como testes de unidade, seu código deve ser coberto com toras, assim como seus testes de unidade estão em toda parte. Nenhuma parte do sistema não requer logs. Lembre -se, às vezes você precisa saber se o seu sistema está funcionando corretamente e só pode visualizar os logs que estão constantemente inundando a tela.
Observe sistemas externos
Essa sugestão é um pouco diferente da anterior: se você estiver se comunicando com um sistema externo, lembre -se de registrar os dados de saída e leitura do seu sistema. A integração do sistema é uma tarefa árdua, e o diagnóstico de problemas entre duas aplicações (imagine diferentes empresas, ambientes, equipes técnicas) é especialmente difícil. Recentemente, descobrimos que a gravação de conteúdo completo da mensagem, incluindo os cabeçalhos SOAP e HTTP da Apache CXF, é muito eficaz durante a fase de integração e teste do sistema.
Isso é caro e, se afetar o desempenho, você só poderá desligar o log. Mas, dessa maneira, seu sistema pode funcionar muito rapidamente e pendurar rapidamente, para que você não possa fazer nada a respeito? Ao integrar com sistemas externos, você só pode ter cuidado e estar preparado para sacrificar um pouco de sobrecarga. Se você tiver sorte o suficiente e a integração do sistema é tratada pelo ESB, é melhor gravar a solicitação e a resposta no barramento. Você pode se referir a este componente de log do mule.
Às vezes, a quantidade de dados trocados com sistemas externos determina que é impossível anotar tudo. Por outro lado, é melhor manter tudo no registro durante a fase de teste e os estágios antecipados de liberação e estar preparado para sacrificar o desempenho. Isso pode ser feito ajustando o nível de log. Dê uma olhada nas seguintes dicas:
Coleção <Integer> requestIds = // ... if (log.isdebugenabled ()) log.debug ("Processando IDs: {}", requestIds); else log.info ("Tamanho do processamento de IDs: {}", requestids.size ()); Se esse madeireiro estiver configurado para o nível de depuração, ele imprime a coleção completa de IDs de solicitação. Se estiver configurado para imprimir informações de informações, ele produzirá apenas o tamanho do conjunto. Você pode me perguntar se eu esqueci a condição isinfoenabled, consulte a segunda sugestão. Outra coisa que vale a pena notar aqui é que o conjunto de IDs não pode ser nulo. Embora possa imprimir normalmente em depuração como nulo, é um grande ponteiro nulo quando configurado como informação. Lembra dos efeitos colaterais mencionados na 4ª sugestão?
Exceções corretas de registro
Primeiro, não registre exceções, deixe a estrutura ou o contêiner fazer isso. É claro que há uma exceção: se você lançar exceções (RMI, EJB, etc.) de um serviço remoto, as exceções serão serializadas para garantir que elas possam ser devolvidas ao cliente (parte da API). Caso contrário, o cliente receberá um noclassDeffoundError ou outra exceção estranha em vez de uma mensagem de erro real.
A gravação de exceção é uma das responsabilidades mais importantes do registro, mas muitos programadores tendem a usar o registro como uma maneira de lidar com exceções. Eles geralmente retornam o valor padrão (geralmente nulos, 0 ou esvazia a sequência) e fingem que nada aconteceu. Às vezes, eles primeiro registram a exceção, depois envolvem a exceção e depois o jogam fora:
log.error ("Exceção de IO", e); lançar uma nova customexception (e);Dessa forma, as informações da pilha geralmente serão impressas duas vezes, porque os lugares onde a exceção MyCustomexception são capturados serão impressos novamente. Os registros de log ou enrolam -os e jogam fora, não o usem ao mesmo tempo, caso contrário, seus logs parecerão confusos.
E se realmente queremos registrar? Por algum motivo (presumivelmente não lendo APIs e documentação?), Cerca de metade do registro que eu acho que está errado. Um pequeno teste, qual das seguintes declarações de log pode imprimir corretamente as exceções do ponteiro nulo?
tente {número inteiro x = null; ++ x;} catch (Exceção e) {log.error (e); // a log.error (e, e); // b log.error ("" + e); // c log.error (e.toString ()); // d log.error (e.getMessage ()); // e log.error (null, e); // f log.error ("", e); // g log.error ("{}", e); // h log.error ("{}", e.getMessage ()); // i log.error ("Erro a leitura do arquivo de configuração:" + e); // j log.error ("Erro a leitura do arquivo de configuração:" + e.getMessage ()); // k log.error ("Erro de leitura de arquivo de configuração", e); // L}É estranho que apenas G e L (isso é melhor) estejam certos! A e B não são compilados sob SLF4J. Outros jogarão fora as informações de rastreamento da pilha ou imprimirão informações incorretas. Por exemplo, E não imprime nada porque a exceção do ponteiro nulo em si não fornece nenhuma informação de exceção e as informações da pilha não são impressas. Lembre -se de que o primeiro parâmetro geralmente é informações de texto, sobre o próprio erro. Não escreva informações de exceção. Ele será lançado automaticamente após a impressão do log, em frente às informações da pilha. Mas se você quiser imprimir isso, é claro que precisará passar a exceção ao segundo parâmetro.
Os registros devem ser legíveis e fáceis de analisar
Agora, existem dois grupos de usuários interessados em seus logs: nós, humanos (se você concorda ou não, os codificadores estão aqui) e computadores (geralmente scripts de shell escritos por administradores de sistema). Os logs devem ser adequados para os dois usuários entenderem. Se alguém olhar para o registro do seu programa atrás de você e vê isso:
Então você definitivamente não seguiu meu conselho. Os logs devem ser tão fáceis de ler e entender como código.
Por outro lado, se o seu programa gerar meio GB de toras a cada hora, ninguém ou nenhum editor de texto gráfico poderá finalizá -los. Nesse momento, nossos idosos, Grep, Sed e Awk, vieram quando entraram em campo. Se possível, é melhor para os logs que você registra para deixar claro para o computador. Não formate os números, use alguns formatos que tornam a correspondência regular etc. Se não for possível, imprima os dados em dois formatos:
log.debug ("Solicite ttl definido como: {} ({})", nova data (ttl), ttl); // solicita ttl definido para: wed 28 20:14:12 cestutils (1272478452437) duração final = duração do rew.FormTutilS; {} ms ({}) ", durationmillis, duração); // Importando: 123456789ms (1 dia 10 horas 17 minutos 36 segundos)Os computadores veem "MS After 1970 Epoch", um formato de tempo agradecerá, enquanto as pessoas ficam felizes em ver algo como "1 dia 10 horas 17 minutos 36 segundos".
Em suma, o diário também pode ser escrito como elegante como um poema, se você estiver disposto a pensar sobre isso.
O acima exposto é uma coleção de informações sobre o formato de saída de ajuste do log Java. Amigos interessados podem se referir a ele.