Qual é a maneira correta de lidar com a saída de depuração em Java?


32

À medida que meus projetos Java atuais crescem cada vez mais, sinto uma necessidade crescente de inserir saída de depuração em vários pontos do meu código.

Para habilitar ou desabilitar esse recurso de maneira apropriada, dependendo da abertura ou do fechamento das sessões de teste, eu geralmente coloco um private static final boolean DEBUG = falseno início das aulas que meus testes estão inspecionando e o uso trivialmente desta maneira (por exemplo):

public MyClass {
  private static final boolean DEBUG = false;

  ... some code ...

  public void myMethod(String s) {
    if (DEBUG) {
      System.out.println(s);
    }
  }
}

e similar.

Mas isso não me deixa muito feliz, porque é claro que funciona, mas pode haver muitas classes para definir DEBUG como verdadeiro, se você não estiver olhando apenas algumas delas.

Por outro lado, eu (como - acho - muitos outros) não gostaria de colocar o aplicativo inteiro no modo de depuração, pois a quantidade de texto que está sendo produzida pode ser esmagadora.

Portanto, existe uma maneira correta de lidar com essa situação arquitetonicamente ou a maneira mais correta é usar o membro da classe DEBUG?


14
em Java, a maneira correta é NÃO usar o código de homebrew para o log. Escolha um quadro estabelecido, não reinventar a roda
mosquito

Eu uso um DEBUG booleano em algumas das minhas classes mais complicadas, pelo mesmo motivo que você declarou. Normalmente, não quero depurar o aplicativo inteiro, apenas a classe que está me causando problemas. O hábito veio dos meus dias COBOL, onde as instruções DISPLAY eram a única forma de depuração disponível.
Gilbert Le Blanc

1
Eu também recomendaria confiar mais em um depurador quando possível e não desarrumar seu código com instruções de depuração.
Andrew T Finnell

1
Você pratica TDD (Test Driven Development) com testes de unidade? Depois que comecei a fazer isso, notei uma redução maciça no 'código de depuração'.
JW01

Respostas:


52

Você deseja examinar uma estrutura de log e, talvez, uma estrutura de fachada de log.

Existem várias estruturas de registro por aí, geralmente com funcionalidades sobrepostas, tanto que, com o tempo, muitas evoluíram para depender de uma API comum ou passaram a ser usadas através de uma estrutura de fachada para abstrair seu uso e permitir que elas fossem trocadas no local se necessário.

Frameworks

Algumas estruturas de log

Algumas fachadas de log

Uso

Exemplo básico

A maioria dessas estruturas permitiria escrever algo do formulário (aqui usando slf4j-apie logback-core):

package chapters.introduction;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

// copied from: http://www.slf4j.org/manual.html
public class HelloWorld {

  public static void main(String[] args) {
    final Logger logger = LoggerFactory.getLogger(HelloWorld.class);

    logger.debug("Hello world, I'm a DEBUG level message");
    logger.info("Hello world, I'm an INFO level message");
    logger.warn("Hello world, I'm a WARNING level message");
    logger.error("Hello world, I'm an ERROR level message");
  }
}

Observe o uso de uma classe atual para criar um criador de logs dedicado, o que permitiria ao SLF4J / LogBack formatar a saída e indicar de onde veio a mensagem de registro.

Conforme observado no manual do SLF4J , um padrão de uso típico em uma classe é geralmente:

import org.slf4j.Logger;  
import org.slf4j.LoggerFactory;  

public class MyClass {

    final Logger logger = LoggerFactory.getLogger(MyCLASS.class);

    public void doSomething() {
        // some code here
        logger.debug("this is useful");

        if (isSomeConditionTrue()) {
            logger.info("I entered by conditional block!");
        }
    }
}

Mas, na verdade, é ainda mais comum declarar o criador de logs com o seguinte formato:

private static final Logger LOGGER = LoggerFactory.getLogger(MyClass.class);

Isso permite que o criador de logs seja usado também dentro de métodos estáticos e é compartilhado entre todas as instâncias da classe. É bem provável que seja sua forma preferida. No entanto, como observado por Brendan Long nos comentários, você deve entender as implicações e decidir adequadamente (isso se aplica a todas as estruturas de registro que seguem essas expressões idiomáticas).

Existem outras maneiras de instanciar os criadores de logs, por exemplo, usando um parâmetro string para criar um criador de logs nomeado:

Logger logger = LoggerFactory.getLogger("MyModuleName");

Níveis de depuração

Os níveis de depuração variam de uma estrutura para outra, mas os comuns são (em ordem de criticidade, de benignos a ruins) e provavelmente muito comuns a esperançosamente muito raros:

  • TRACE Informações muito detalhadas. Deve ser gravado apenas nos logs. Usado apenas para rastrear o fluxo do programa nos pontos de verificação.

  • DEBUG Informação detalhada. Deve ser gravado apenas nos logs.

  • INFO Eventos de tempo de execução notáveis. Deve estar imediatamente visível em um console, portanto, use com moderação.

  • WARNING Esquisitices de tempo de execução e erros recuperáveis.

  • ERROR Outros erros de tempo de execução ou condições inesperadas.

  • FATAL Erros graves que causam rescisão prematura.

Blocos e guardas

Agora, digamos que você tenha uma seção de código na qual está prestes a escrever várias instruções de depuração. Isso pode afetar rapidamente seu desempenho, devido ao impacto do próprio log e da geração de quaisquer parâmetros que você possa estar passando para o método de log.

Para evitar esse tipo de problema, muitas vezes você deseja escrever algo no formato:

if (LOGGER.isDebugEnabled()) {
   // lots of debug logging here, or even code that
   // is only used in a debugging context.
   LOGGER.debug(" result: " + heavyComputation());
}

Se você não tivesse usado esse protetor antes de seu bloco de instruções de depuração, mesmo que as mensagens não sejam exibidas (se, por exemplo, seu criador de logs estiver configurado para imprimir apenas itens acima do INFOnível), o heavyComputation()método ainda teria sido executado .

Configuração

A configuração é bastante dependente da sua estrutura de log, mas eles oferecem basicamente as mesmas técnicas para isso:

  • configuração programática (em tempo de execução, via API - permite alterações no tempo de execução ),
  • configuração declarativa estática (na hora de início, geralmente por meio de um arquivo XML ou de propriedades - provavelmente o que você precisa primeiro ).

Eles também oferecem principalmente os mesmos recursos:

  • configuração do formato da mensagem de saída (timestamps, marcadores, etc ...),
  • configuração dos níveis de saída,
  • configuração de filtros refinados (por exemplo, para incluir / excluir pacotes ou classes),
  • configuração de anexadores para determinar onde fazer logon (para console, arquivar, para um serviço da Web ...) e possivelmente o que fazer com logs mais antigos (por exemplo, com arquivos de rolagem automática).

Aqui está um exemplo comum de uma configuração declarativa, usando um logback.xmlarquivo.

<configuration>

  <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
    <!-- encoders are assigned the type
         ch.qos.logback.classic.encoder.PatternLayoutEncoder by default -->
    <encoder>
      <pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
    </encoder>
  </appender>

  <root level="debug">
    <appender-ref ref="STDOUT" />
  </root>
</configuration>

Como mencionado, isso depende da sua estrutura e pode haver outras alternativas (por exemplo, o LogBack também permite que um script Groovy seja usado). O formato de configuração XML também pode variar de uma implementação para outra.

Para mais exemplos de configuração, consulte (entre outros):

Um pouco de diversão histórica

Por favor note que Log4J está vendo uma grande atualização no momento, a transição da versão 1.x para 2.x . Você pode querer dar uma olhada em ambos para obter mais diversão ou confusão histórica, e se você escolher o Log4J, provavelmente preferirá usar a versão 2.x.

Vale ressaltar, como Mike Partridge mencionou nos comentários, que o LogBack foi criado por um ex-membro da equipe do Log4J. Que foi criado para solucionar as deficiências da estrutura Java Logging. E que a próxima versão principal do Log4J 2.x está agora integrando alguns recursos retirados do LogBack.

Recomendação

Resumindo, mantenha-se o mais dissociado possível, brinque com alguns e veja o que funciona melhor para você. No final , é apenas uma estrutura de registro . Exceto se você tiver um motivo muito específico, além da facilidade de uso e da preferência pessoal, qualquer um deles funcionaria bem, então não faz sentido ficar de olho nele. A maioria deles também pode ser estendida às suas necessidades.

Ainda assim, se eu tivesse que escolher uma combinação hoje, iria com o LogBack + SLF4J. Mas se você me perguntasse alguns anos depois, eu recomendaria o Log4J com o Apache Commons Logging, portanto, fique de olho nas suas dependências e evolua com elas.


1
SLF4J e LogBack foram escritos pelo cara que originalmente escreveu o Log4J.
Mike Partridge #

4
Para aqueles que possam estar preocupado com os impactos da exploração madeireira de desempenho: slf4j.org/faq.html#logging_performance
Mike Partridge

2
Vale ressaltar que não é tão claro se você deve criar seus loggers static, pois economiza uma pequena quantidade de memória, mas causa problemas em certos casos: slf4j.org/faq.html#declared_static
Monica

1
@ MikePartridge: Estou ciente do conteúdo do link, mas ele ainda não impedirá a avaliação de parâmetros, por exemplo. a razão pela qual o log parametrizado do byt é mais eficiente é porque o processamento da mensagem de log não ocorrerá (string notadamente concatenação). No entanto, qualquer chamada de método será executada se for passada como parâmetro. Portanto, dependendo do seu caso de uso, os blocos podem ser úteis. E, como mencionado na postagem, eles também podem ser úteis para você agrupar outras atividades relacionadas à depuração (não apenas o log) para ocorrer quando o nível DEBUG estiver ativado.
haylem

1
@ Hayylem - isso é verdade, meu erro.
Mike Partridge #

2

usar uma estrutura de log

na maioria das vezes há um método estático de fábrica

private static final Logger logger = Logger.create("classname");

então você pode gerar seu código de log com diferentes níveis:

logger.warning("error message");
logger.info("informational message");
logger.trace("detailed message");

haverá um único arquivo onde você poderá definir quais mensagens para cada classe devem ser gravadas na saída do log (arquivo ou stderr)


1

É exatamente para isso que servem as estruturas de log como log4j ou o slf4j mais recente. Eles permitem controlar o registro em detalhes e configurá-lo mesmo enquanto o aplicativo está em execução.


0

Uma estrutura de log é definitivamente o caminho a percorrer. No entanto, você também deve ter um bom conjunto de testes. Uma boa cobertura de teste geralmente pode eliminar a necessidade de saída de depuração todos juntos.


Se você usar uma estrutura de log e tiver o log de depuração disponível - chegará um momento em que isso evitará que você tenha um dia muito ruim.
Fortyrunner 17/11/2012

1
Eu não disse que você não deveria ter logado. Eu disse que você precisa fazer os testes primeiro.
Dima
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.