segunda-feira, 11 de novembro de 2013

Boas práticas para o uso de logs em Java

O JDK vem com o pacote java.util.logging que tem a biblioteca para tratamento de log nativa e o Log4J do projeto Apache é a opção log mais utilizada. O Log4J tem mais features que o java.util.logging. O Common Logging também da Apache é apenas um wrapper (uma casca) para encobrir o uso de qualquer mecanismo de log, inclusive o Log4J e java.util.logging.

Uma mensagem em qualquer mecanismo de log pode ter níveis de severidade diferentes. Estes níveis se dividem basicamente em:

  • fatal - este nível deve ser utilizado em casos extremos, em geral mensagens que impacta negativamente todo o ambiente (e não apenas uma sessão de um usuário) e exige uma ação não de um operador, mas sim do administrador do ambiente e do arquiteto do sistema
  • error - este nível representa uma exceção Java que ocorreu durante a execução de uma operação de um usuário ou a execução de uma tarefa. Pode-se enviar mensagens neste nível para alguma inconsistência identificada pelo próprio sistema, mesmo que não gere exceção. 
  • warn - este nível é utilizado para alguma informação que seja interessante ser registrada, que pode representar um problema, mas o sistema se recuperou e não deixou de executar a tarefa.
  • info - este nível de mensagem é utilizado para indicar o que é que a aplicação está fazendo em alto nível. 
  • debug - este nível de mensagem, são de baixo nível, onde detalha resultados intermediários de uma tarefa, e são utilizados, normalmente para depuração de um trecho da aplicação
Ata da Reunião - cada anotação tem uma severidade diferente
Não se deve usar a saída padrão (System.out) ou a saída de erro (System.err). Deve sempre usar o log. O log é configurável, você habilita e desabilita seus níveis, permite escrever em diversos destinos diferentes (console, arquivo, banco de dados, rede, etc), seleciona o nível por pacote Java, permite rotacionar, informar data e hora para toda mensagem, entre outras vantagens. Escrever na saída padrão ou de erro não permite nada disso, o que pode tornar a informação volumosa e confusa. 

Quando na aplicação se escreve em níveis mais baixo, principalmente o nível debug, é importantíssimo utilizar isDebugEnabled() por questão de otimização. É normal que, quanto menor o nível, mais mensagens existam, mas é normal que você não queira ver as mensagens de log de todos os módulos da aplicação, mas apenas de alguns. Então deve-se habilitar o log apenas destes módulos.

log.debug("Retorno cliente " + cliente + ", Status=" + status);

No exemplo acima, sempre irá concatenar a string, para chamar o método log.debug, para verificar se está habilitado ou não o log para este pacote Java. Caso esteja, escreve no log normalmente, mas caso não esteja, o Java realizou a concatenação da string desnecessariamente. Agora imagine este tipo de código em um loop e em diversos pacotes diferentes, sendo que o modo debug não está ligado. É uma concatenação desnecessária que ocorre com todos os log.debug. Por questão de otimização, deve-se verificar o nível de log antes de concatenar a string do debug.


if (log.isDebugEnabled()) {
    log.debug("Retorno cliente " + cliente + ", Status=" + status);
}

Logando exceção

Quando o código encontra uma exceção, ele deve:

  • tratar a exceção - se seu código pode tratar a exceção (ex.: falha de rede), então deve fazê-lo; ou
  • relançar a mesma exceção - em caso de exceção unchecked; ou
  • reencapsular a exceção - no caso de exceções checked; ou
  • logar e exibir ao usuário - se em nenhum outro ponto foi possível tratar a exceção, em geral no nível mais alto da stack trace (na Action, ou onMessage, ou no método main), então a mensagem deve ser logada e informada ao usuário.
Sempre que reencapsular a exceção, a nova exceção precisa fazer bom uso dos dois parâmetros que existem nos construtores das exceções:
  • message - informar a atividade que estava tentando ser realizada quando a exceção ocorreu, preferencialmente informando algum parâmetro de entrada para ajudar a identificar o erro; e
  • cause - é a exceção original
Um exemplo de como relançar a exceção pode ser conferido a seguir:


void salvar(Cliente c) {
    try {
        DAOCliente.salvar(c);
    } catch (SQLException ex) {
        throw new AppException(
            "Erro de banco ao salvar cliente " // ação com erro
            + c != null ? c.getNome() : null, // um parâm. de entrada
            ex); // exceção original
    }
}

Da mesma maneira, estes dois parâmetros são de extrema importância durante o log no nível mais alto de erro:

void doGet(HttpServletRequest req, HttpServletResponse resp) {
    try {
        // . . .
        businessClient.salvar(c);
        // . . .
    } catch (AppException ex) {
        log.error(
            "Erro ao tentar cadastrar cliente " // ação com erro
            + req.getParameter("nome"), // um parâmetro de entrada
            ex); // exceção original
    }
}


Nenhum comentário:

Postar um comentário