¿Cuál es la forma correcta de manejar la salida de depuración en Java?

32

A medida que mis proyectos Java actuales crecen más y más, siento una necesidad cada vez mayor de insertar resultados de depuración en varios puntos de mi código.

Para habilitar o deshabilitar esta función de manera apropiada, dependiendo de la apertura o el cierre de las sesiones de prueba, generalmente pongo un private static final boolean DEBUG = falseal comienzo de las clases que mis pruebas están inspeccionando, y lo uso trivialmente de esta manera (por ejemplo):

public MyClass {
  private static final boolean DEBUG = false;

  ... some code ...

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

y similares.

Pero eso no me alegra, porque, por supuesto, funciona, pero podría haber demasiadas clases para establecer DEBUG en verdadero, si no está mirando solo un par de ellas.

Por el contrario, a mí (me gusta, creo que muchos otros) no me encantaría poner toda la aplicación en modo de depuración, ya que la cantidad de texto que se genera podría ser abrumadora.

Entonces, ¿hay una manera correcta de manejar arquitectónicamente tal situación o la forma más correcta es usar el miembro de la clase DEBUG?

Federico Zancan
fuente
14
en Java, la forma correcta NO es usar código homebrew para iniciar sesión. Elija un marco establecido, no reinvente la rueda
mosquito
Utilizo un DEPURO booleano en algunas de mis clases más complicadas, por la misma razón que usted dijo. Por lo general, no quiero depurar toda la aplicación, solo la clase que me da problemas. El hábito vino de mis días COBOL, donde las declaraciones DISPLAY eran la única forma de depuración disponible.
Gilbert Le Blanc
1
También recomendaría confiar más en un depurador cuando sea posible y no ensuciar su código con declaraciones de depuración.
Andrew T Finnell
1
¿Practica Test Driven Development (TDD) con pruebas unitarias? Una vez que comencé a hacer eso, noté una reducción masiva en el 'código de depuración'.
JW01

Respuestas:

52

Desea mirar un marco de registro, y tal vez un marco de fachada de registro.

Existen múltiples marcos de registro, a menudo con funcionalidades superpuestas, tanto que con el tiempo muchos evolucionaron para depender de una API común, o se han utilizado a través de un marco de fachada para abstraer su uso y permitir que se intercambien en su lugar si es necesario.

Marcos

Algunos marcos de registro

Algunas fachadas de registro

Uso

Ejemplo básico

La mayoría de estos marcos te permitirían escribir algo del formulario (aquí usando slf4j-apiy 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");
  }
}

Tenga en cuenta el uso de una clase actual para crear un registrador dedicado, que permitiría que SLF4J / LogBack formatee la salida e indique de dónde proviene el mensaje de registro.

Como se señala en el manual SLF4J , un patrón de uso típico en una clase suele ser:

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

Pero, de hecho, es aún más común declarar el registrador con el formulario:

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

Esto permite que el registrador también se use desde métodos estáticos, y se comparte entre todas las instancias de la clase. Es muy probable que esta sea su forma preferida. Sin embargo, como señaló Brendan Long en los comentarios, desea asegurarse de comprender las implicaciones y decidir en consecuencia (esto se aplica a todos los marcos de registro después de estos modismos).

Hay otras formas de crear instancias de registradores, por ejemplo, mediante el uso de un parámetro de cadena para crear un registrador con nombre:

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

Niveles de depuración

Los niveles de depuración varían de un marco a otro, pero los más comunes son (en orden de criticidad, de benignos a malos, y probablemente de muy comunes a, con suerte, muy raros):

  • TRACE Información muy detallada. Debe escribirse solo en los registros. Se usa solo para rastrear el flujo del programa en los puntos de control.

  • DEBUG Información detallada. Debe escribirse solo en los registros.

  • INFO Eventos notables de tiempo de ejecución. Debe ser visible de inmediato en una consola, por lo tanto, use con moderación.

  • WARNING Runtime rarezas y errores recuperables.

  • ERROR Otros errores de tiempo de ejecución o condiciones inesperadas.

  • FATAL Errores graves que causan terminación prematura.

Bloques y guardias

Ahora, supongamos que tiene una sección de código donde está a punto de escribir una serie de declaraciones de depuración. Esto podría afectar rápidamente su rendimiento, tanto por el impacto del registro en sí como por la generación de cualquier parámetro que pueda estar pasando al método de registro.

Para evitar este tipo de problema, a menudo desea escribir algo del formulario:

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

Si no hubiera utilizado esta protección antes de su bloque de sentencias de depuración, aunque los mensajes no se envíen (si, por ejemplo, su registrador está configurado actualmente para imprimir solo cosas por encima del INFOnivel), el heavyComputation()método aún se habría ejecutado .

Configuración

La configuración depende bastante de su marco de registro, pero ofrecen principalmente las mismas técnicas para esto:

  • configuración programática (en tiempo de ejecución, a través de una API, permite cambios en tiempo de ejecución ),
  • configuración declarativa estática (en el momento del inicio, generalmente a través de un archivo XML o de propiedades, probablemente sea lo que necesita al principio ).

También ofrecen principalmente las mismas capacidades:

  • configuración del formato del mensaje de salida (marcas de tiempo, marcadores, etc.),
  • configuración de los niveles de salida,
  • configuración de filtros de grano fino (por ejemplo, para incluir / excluir paquetes o clases),
  • configuración de apéndices para determinar dónde iniciar sesión (para consola, para archivar, para un servicio web ...) y posiblemente qué hacer con registros más antiguos (por ejemplo, con archivos automáticos).

Aquí hay un ejemplo común de una configuración declarativa, usando un logback.xmlarchivo.

<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 se mencionó, esto depende de su marco y puede haber otras alternativas (por ejemplo, LogBack también permite usar un script Groovy). El formato de configuración XML también puede variar de una implementación a otra.

Para obtener más ejemplos de configuración, consulte (entre otros) a:

Un poco de diversión histórica

Tenga en cuenta que Log4J está viendo una actualización importante en este momento, la transición de la versión 1.x de 2.x . Es posible que desee echar un vistazo a ambos para obtener más diversión histórica o confusión, y si elige Log4J, probablemente prefiera ir con la versión 2.x.

Vale la pena señalar, como Mike Partridge mencionó en los comentarios, que LogBack fue creado por un ex miembro del equipo Log4J. Que fue creado para abordar las deficiencias del marco de registro de Java. Y que la próxima versión principal de Log4J 2.x está integrando algunas características tomadas de LogBack.

Recomendación

En pocas palabras, manténgase desacoplado tanto como pueda, juegue con algunos y vea qué funciona mejor para usted. Al final es solo un marco de registro . Excepto si tiene una razón muy específica, además de la facilidad de uso y la preferencia personal, cualquiera de estos sería bastante correcto, por lo que no tiene sentido estar pendiente de él. La mayoría de ellos también se pueden extender a sus necesidades.

Aún así, si tuviera que elegir una combinación hoy, iría con LogBack + SLF4J. Pero si me hubieras preguntado unos años más tarde, te habría recomendado Log4J con Apache Commons Logging, así que vigila tus dependencias y evoluciona con ellas.

haylem
fuente
1
SLF4J y LogBack fueron escritos por el tipo que originalmente escribió Log4J.
Mike Partridge
44
Para aquellos que puedan estar preocupados por los impactos en el rendimiento del registro: slf4j.org/faq.html#logging_performance
Mike Partridge
2
Vale la pena mencionar que no es tan claro si debe hacer sus registradores static, ya que ahorra una pequeña cantidad de memoria, pero causa problemas en ciertos casos: slf4j.org/faq.html#declared_static
Reinstale Monica
1
@ MikePartridge: conozco el contenido del enlace, pero todavía no impedirá la evaluación de parámetros, por ejemplo. la razón por la cual el registro parametrizado byt es más eficiente es porque el procesamiento del mensaje de registro no ocurrirá (cadena notablemente concatenación). Sin embargo, cualquier llamada al método se ejecutará si se pasa como un parámetro. Entonces, dependiendo de su caso de uso, los bloques pueden ser útiles. Y como se menciona en la publicación, también pueden ser útiles para que usted solo agrupe otras actividades relacionadas con la depuración (no solo el registro) para que ocurran cuando el nivel DEBUG está habilitado.
haylem
1
@haylem: es verdad, mi error.
Mike Partridge
2

usar un marco de registro

la mayoría de las veces hay un método de fábrica estático

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

entonces puede generar su código de registro con diferentes niveles:

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

entonces habrá un único archivo donde puede definir qué mensajes para cada clase se deben escribir en la salida del registro (archivo o stderr)

monstruo de trinquete
fuente
1

Esto es exactamente para lo que están destinados los marcos de registro como log4j o el slf4j más nuevo. Le permiten controlar el registro con gran detalle y configurarlo incluso mientras la aplicación se está ejecutando.

Michael Borgwardt
fuente
0

Un marco de registro es definitivamente el camino a seguir. Sin embargo, también debe tener un buen conjunto de pruebas. Una buena cobertura de prueba a menudo puede eliminar la necesidad de generar resultados de depuración todos juntos.

Dima
fuente
Si utiliza un marco de registro y tiene disponible el registro de depuración, llegará un momento en que esto le evitará tener un mal día.
Fortyrunner
1
No dije que no debieras iniciar sesión. Dije que primero debes hacerte las pruebas.
Dima