Laboratorio de Programación  >  Material de estudio  >  Apuntes  >  Trazas de ejecución

Log: trazas de ejecución

José A. Mañas <jmanas@dit.upm.es>
Dept. de Ingeniería de Sistemas Telemáticos
Universidad Politécnica de Madrid
5 de febrero, 2006

1. Introducción

Los programas no triviales hacen muchas cosas entre que toman los datos de entrada y generan la salida. A veces interesa tener un registro de lo que va sucediendo, con más o menos detalle.

El nivel de detalle de las trazas de ejecución interesa que sea ajustable según las circunstancias: dinámicamente.

1.1. Depuración

Un caso típico en el que nos interesa que el programa "se explique" paso por paso es cuando estamos depurando: el programa no funciona y necesitamos descubrir dónde falla para repararlo.

Las trazas serán de poco detalle cuando empezamos a buscar; pero iremos aumentando el detalle según vayamos acorralando el error.

No es ninguna novedad que los programadores "trufen" el código de sentencias System.err.println para dejar traza de por dónde vamos pasando y en valor que van tomando las variables. Lo malo de esta aproximación es que las sentencias de escritura hay que anularlas cuando el sistema se pone en producción, bien comentándolas, bien eliminándolas, bien armando algún mecanismo de control dinámico del nivel de detalle deseado (así, nivel cero significa que no hay traza alguna, lo que es típico de producción).

Java incluye un paquete que permite automatizar la tarea de activar y desactivar trazas con diferente nivel de detalle.

2. package log.Logger

Se trata de un paquete preparado para realizar las prácticas de la asignatura.

Está inspirado en el paquete java.util.logging de la librería de Java (que se describe al final); pero adaptado a la asignatura.

Es necesario incluir el paquete:
    import log.Logger;

Para trazar necesitamos un logger:
    Logger LOGGER= Logger.getLogger("NOMBRE")

2.1. El nombre del logger

El programador puede bautizar su logger como le apetezca, si bien lo usual es usar el nombre del paquete y la clase para que la estructura de las trazas sea paralela a la estructura de paquetes, clases y métodos:
    private static final Logger LOGGER= Logger.getLogger("paquete.clase")

Decidir exactamente el nivel de granularidad depende de cada caso. Podemos empezar con un logger por paquete:
    private static final Logger LOGGER= Logger.getLogger("paquete")

Si el paquete es grande (muchas clases) puede ser interesante tener un logger para cada clase o para algunas clases:
    private static final Logger LOGGER= Logger.getLogger("paquete.clase")

Si una clase es muy grande (muchos métodos) puede ser interesante tener in logger para cada método o para algunos métodos:
    private static final Logger LOGGER= Logger.getLogger("paquete.clase.metodo")

En los ejemplos de estos apuntes usaremos un logger por clase.

Los nombres forman una jerarquía o "árbol genealógico" que nos permitirá fijar el nivel de detalle de traza de familias enteras, pues el nivel que fijemos para un logger será el que usen todos sus "descendientes", salvo indicación expresa.

Así podremos dar reglas para todos los paquetes, reglas que refinaremos en algunos paquetes, podremos refinar más por clases e incluso por métodos. Según haga falta.

2.2. Nivel de detalle

java.util.logging define algunos niveles típicos que, sin ser obligatorios, permiten clasificar las trazas en familias:

OFF No se genera traza alguna.
SEVERE Se usa para trazar errores catastróficos, que son aquellos de los que no hay recuperación posible, provocando la terminación del programa. La traza recoge el fallo causante de la detención.
WARNING Se usa para trazar errores peligrosos, para los que hay previsto un mecanismo de supervivencia.
INFO Trazas normales: para ir viendo lo que pasa.
CONFIG Se usan típicamente al arrancar un programa para trazar la configuración inicial, que frecuentemente se lee de alguna parte.
FINE Información de detalle, típicamente útil para localizar errores (depuración).
FINER Información de más detalle, típicamente útil para localizar errores (depuración).
FINEST Información de máximo detalle, típicamente útil para localizar errores (depuración).
ALL Se traza todo, a cualquier nivel.

Los siguientes ejemplos de uso deberían ser obvios:

  LOGGER.log(Logger.WARNING, "mensaje");
  LOGGER.severe("mensaje");
  LOGGER.warning("mensaje");
  LOGGER.info("mensaje");
  LOGGER.config("mensaje");
  LOGGER.fine("mensaje");
  LOGGER.finer("mensaje");
  LOGGER.finest("mensaje");

2.3. Excepciones

El lanzamiento de excepciones se puede trazar de diferentes maneras.

La más sencilla consiste en asociar el texto de la excepción a la traza:
    LOGGER.warning("mensaje " + e);

También se puede pasar como tercer argumento a log:
    LOGGER.log(Logger.WARNING, "mensaje ", e);

3. Configuración

El programador describirá en un fichero de propiedades los detalles de qué traza desea.

Se usa siempre el fichero
    $HOME/logger.java

El directorio $HOME depende del sistema operativo en el que trabajemos; pero si el paquete no encuentra el fichero, indica por medio de un mensaje en consola qué es lo que estaba buscando, de forma que podamos llevar el fichero a su sitio.

    $ java Diccionario
    no se encuentra: C:\Documents and Settings\pepe\java.logger

Y cuando lee el fichero, indica qué es lo que está leyendo:

    $ java Diccionario
    java.logger <- C:\Documents and Settings\pepe\java.logger

El fichero java.logger indica varias cosas.

.level= ALL
mínimo nivel que aparecerá por consola o fichero
.console.level= ALL
mínimo nivel que aparecerá por consola (System.err)
.file.level= SEVERE
mínimo nivel que aparecerá en fichero
.file.name= J$.log
nombre del fichero en el que se escribe la traza
(se llamará "J" algo ".log" en el mismo directorio en el que está java.logger)
Diccionario= INFO
el logger de nombre "Diccionario" trazará a nivel INFO y superiores
paquete= nivel
los logger de nombre "paquete..." trazarán al nivel indicado y superiores
paquete.clase= nivel
los logger de nombre "paquete.clase..." trazarán al nivel indicado y superiores
paquete.clase.metodo= nivel
los logger de nombre "paquete.clase.metodo..." trazarán al nivel indicado y superiores

Si un Logger tiene por nombre una serie de palabras separadas por puntos
    a.b.c.d

se buscará sucesivamente alguna entrada en java.logger que diga

  1. a.b.c.d= NIVEL
  2. a.b.c= NIVEL
  3. a.b= NIVEL
  4. a= NIVEL
  5. .level= NIVEL
aplicándose el NIVEL de la primera que encuentre.
Recuerde que los nombres forman un "árbol genealógico".

4. Ejemplo

Diccionario.java es un programa Java que busca una palabra en un diccionario, estando el programa preparado para trazar su comportamiento en diferentes niveles de detalle.

Para probar, debe organizar la jerarquía de directorios paralela a la jerarquía de paquetes. Algo así

    C:\java\lprg\2006-01-31\Diccionario.java
    C:\java\lprg\2006-01-31\log\Logger.java

Para ejecutar el programa ejemplo puede interesarle usar este diccionario inicial de palabras.

Se adjunta el resultado de ejecutar con diferente nivel de traza:

2. java.util.logging

java.util.logging es un paquete Java para automatizar el trazado de programas. Es parte de la distribución estándar de Java y no hay que hacer ninguna instalación especial para usarlo.

El paquete java.util.logging es similar a log.Logger pero permite hacer muchas más cosas que las que se describen en esta nota. Para una referencia completa, se remite al lector a

Java Logging Overview

Thinking in Java, el libro de Bruce Eckel, incluye un capítulo dedicado a las trazas con extenso detalle.