Apache log4j: una libreria di logging per Java

In questo articolo vedremo come utilizzare all’interno del nostro codice Java una delle più conosciute librerie per il logging: l’API log4j.

Il termine log deriva da logbook, il quale era il registro di navigazione, presente in ogni nave, su cui veniva segnata, ad intervalli regolari la velocità, il tempo, la forza del vento, oltre a eventi significativi che accadevano durante la navigazione. Con il significato di giornale di bordo, o semplicemente giornale, su cui vengono registrati gli eventi in ordine cronologico il termine è stato importato nell’informatica (nel 1963) per indicare:

  • la registrazione cronologica delle operazioni man mano che vengono eseguite;
  • il file su cui tali registrazioni sono memorizzate.

Log4j è un progetto Open Source dell’Apache Software Foundation (ASF) che permette di monitorare un’applicazione Java durante il suo utilizzo qualora non fosse possibile utilizzare strumenti di debugging, si pensi ad esempio quando una applicazione è distribuita sul web o è di tipo multithreading. Il logging ha però una controindicazione: porta ad un decremento delle performance. Per alleviare questo difetto, log4j è stato sviluppato per essere veloce ed estendibile, permettendo vari livelli di granularità nel controllo del logging.

Log4j utilizza tre componenti principali:

  • i Logger;
  • gli Appenders;
  • i Layouts.

Il vantaggio più importante che ha un sistema di logging ha rispetto a riempire il nostro codice di System.out.println, è che è possibile controllare quando visualizzare gli statement di log. In altre parole è possibile dividere lo spazio dei log, che è lo spazio dove risiedono tutti gli statement di log, in categorie definite da criteri decisi dallo sviluppatore. La classe Logger rappresenta appunto uno spazio dei log che rispetta dei determinati criteri (nelle versioni precedenti questa classe si chiama proprio Category). I Logger sono classi che hanno un nome che identifica una gerarchia secondo una precisa regola: un logger è un antenato di un altro logger se il suo nome seguito da un punto è il prefisso del nome del discendete. Un logger è definito come il genitore di un logger (definito come figlio) se non vi sono altri antenati tra se stesso ed il discendente.

Ad esempio “java” è un genitore di “java.util” ed è un antenato di “java.util.String”.

Esiste poi un logger radice (RootLogger) il quale esiste sempre (non va definito come gli altri) ed ha un metodo proprio per essere ricavato (getRootLogger). Gli altri tipi di Logger vanno invece instanziati tramite il metodo statico Logger.getLogger(), questo metodo prende come input il nome del logger che vogliamo definire. La classe Logger è così definita:

package org.apache.log4j;
public class Logger {
// metodi di creazione e di retrieve
public static Logger getRootLogger();
public static Logger getLogger(String name);
// metidi per stampare messaggio di log
public void trace(Object message);
public void debug(Object message);
public void info(Object message);
public void warn(Object message);
public void error(Object message);
public void fatal(Object message);
// metodo generico di printing
public void log(Level l, Object message);
}

Ai Logger possono essere assegnati dei livelli che sono :

  • OFF: ha il livello di priorità più alto possibile ed è utilizzato per disabilitare il logging;
  • FATAL: utilizzato per indicare errori gravi occorsi nell’applicazione che non le permettono di continuare;
  • ERROR: utilizzato per indicare errori occorsi nell’applicazione che le permettono di continuare;
  • WARN: utilizzato per indicare potenziali errori occorsi nell’applicazione;
  • INFO: utilizzato per indicare messaggi di informazioni sullo stato di avanzamento dell’applicazione (coarse-grained level);
  • DEBUG: utilizzato per indicare messaggi di informazioni di debugging sullo stato di avanzamento dell’applicazione (fine-grained level);
  • TRACE: utilizzato per indicare messaggi di informazioni di debugging sullo stato di avanzamento dell’applicazione ad un livello molto dettagliato (finer-graimed rispetto a DEBUG);
  • ALL: ha il livello di priorità più basso possibile ed è utilizzato per abilitare tutte le istruzioni di logging;

definiti nella classe Level, se ad un Logger non è assegnato esplicitamente un livello, questi lo eredita dal suo più vicino antenato che abbia il livello definito. Se il logger per “java.util.String” non ha un livello settato e neanche il suo genitore “java.util” allora eredita il livello dell’antenato “java”.

Una richiesta di logging (la scrittura vera e propria di uno statement di logging) avviene invocando i metodi di printing (trace(), debug(), ecc) su una istanza di Logger. Utilizzando uno di questi metodi si associa un livello di richiesta del logging al Logger; ad esempio utilizzando il metodo error() si richiede la scrittura di uno statement di log con livello ERROR. Una richiesta di logging è abilitata se il suo livello è maggiore o uguale del livello settato nel suo Logger. In altre parole, una richiesta di log di livello p in un Logger con livello q è abilitata se p >= q. Questa regola è al centro di log4j. Si presuppone che i livelli sono ordinati così: ALL < TRACE < DEBUG < INFO < WARN < ERROR < FATAL < OFF. Infatti se andiamo ad analizzare la Priority nel codice sorgente della classe Level abbiamo la seguente situazione:

public final static int OFF_INT = Integer.MAX_VALUE;
public final static int FATAL_INT = 50000;
public final static int ERROR_INT = 40000;
public final static int WARN_INT = 30000;
public final static int INFO_INT = 20000;
public final static int DEBUG_INT = 10000;
public static final int TRACE_INT = 5000; 
public final static int ALL_INT = Integer.MIN_VALUE; 

Vediamo degli esempi:

package it.appuntisoftware;
import org.apache.log4j.Level;
import org.apache.log4j.Logger;
public class TestLog4j {
public static void main (String arg[]){
// otteniamo una instanza di un Logger chiamata "it.appuntisoftware"
Logger  logger = Logger.getLogger("it.appuntisoftware");
// Settiamo il suo livello, in genere è preferibile usare un file di configurazione
logger.setLevel(Level.INFO);
Logger wwwlogger = Logger.getLogger("it.appuntisoftware.www");
// Queste richieste sono abilitate, perchè WARN >= INFO e ERROR >=INFO.
logger.warn("Warn level.");
logger.error("Error level.");
// Questa richiesta è disabilitata, perchè DEBUG < INFO.
logger.debug("Debug level.");
// Il logger "it.appuntisoftware.www" ,eredita il suo livello da "it.appuntisoftware",
// Di conseguenza la richiesta è abilitata perchè INFO >= INFO.
wwwlogger.info("WWW Info level.");
// Questa richiesta è disabilitata, perchè DEBUG < INFO.
wwwlogger.debug("WWW Debug level.");
}
}

Log4j permette di “stampare” le richieste di log su più destinazioni, una destinazione di output viene chiamata Appender. Esisto appender per la console, file, socket, JMS e Syslog remoti. Tramite il metodo addAppender() è possibile aggiungere un appender ad un Logger.

Infine vi sono i layout (la classe PatternLayout) che permette all’utente di specificare il formato dell’output con una convenzione simile a quella del C per la funzione printf.  Ad esempio il PatternLayout definito con il pattern “%r [%t] %-5p %c – %m%n” genera un output simile a questo:

176 [main] INFO org.foo.Bar – Located nearest gas station.

il pattern (denominato conversion pattern) è formato da testo e da espressioni di controllo chiamate conversion specifiers , ognuna di queste inizia con il simbolo % ed è seguita da un format modifier (opzionale) e da un conversion character . Il conversion character specifica il tipo di dato da visualizzare ad esempio la categoria, la priorità, la data, il nome del thread ecc… Il format modifier invece controlla dei parametri quali la larghezza di un campo, il padding la giustificazione a destra o a sinistra ecc…

Alcuni dei conversion character sono (si distinguono le minuscole dalle maiuscole) :

  • c: stampa la categoria dell’evento di log, cioè il nome del Logger, ad esempio it.appuntisoftware;
  • d: stampa la data dell’evento di log;
  • m: stampa il messaggio associato al log;
  • n: stampa un carattere di a capo;
  • p: stampa il livello dell’evento di log;
  • t: stampa il nome del thread che ha generato l’evento di log;

Il format modifier utilizza il simbolo “-” per indicare di giustificare il testo a sinistra ed utilizza un numero per indicare il numero minimo di caratteri da riempire eventualmente con degli spazi, ad esempio con %-5p indichiamo che quando stampiamo un livello, esso deve essere allineato a sinistra con un eventuale padding fino a 5 caratteri così il testo risulta allineato qualsiasi sia il livello stampato.

L’output senza padding utilizzando il pattern %p %t%c %x – %m%n

L’output con il padding %-5p %t%c %x – %m%n

Infine se inseriamo un numero preceduto da un “.” allora fissiamo la dimensione massima del testo in output, ad esempio con %.30 limitiamo a 30 caratteri l’output conversion character.

Possiamo configurare log4j, cioè indicare quali Appender utilizzare, quali conversion pattern usare, ecc…, utilizzando un file di properties o un file XML, ad esempio il seguente file log4j.propertiese viene automaticamente caricato all’avvio dell’applicazione (se cambiassimo nome dobbiamo caricarlo manualmente con il metodo della classe PropertyConfigurator.configure(“myLog.properties”)):

# Settiamo il  root logger level a DEBUG con un solo appender CONSOLE_APPENDER.
log4j.rootLogger=DEBUG, CONSOLE_APPENDER
# CONSOLE_APPENDER è settato su ConsoleAppender.
log4j.appender.CONSOLE_APPENDER=org.apache.log4j.ConsoleAppender
# CONSOLE_APPENDER usa il seguente PatternLayout.
log4j.appender.CONSOLE_APPENDER.layout=org.apache.log4j.PatternLayout
log4j.appender.CONSOLE_APPENDER.layout.ConversionPattern=%-5p [%t] %c %x - %m%n&lt;/pre&gt;

Ovviamente possiamo utilizzare più di un Appender contemporaneamente:

# Settiamo il  root logger level a DEBUG con due appender CONSOLE_APPENDER e FILE_APPENDER
log4j.rootLogger=DEBUG, CONSOLE_APPENDER, FILE_APPENDER

# CONSOLE_APPENDER è settato su ConsoleAppender.
log4j.appender.CONSOLE_APPENDER=org.apache.log4j.ConsoleAppender
log4j.appender.CONSOLE_APPENDER.layout=org.apache.log4j.PatternLayout
log4j.appender.CONSOLE_APPENDER.layout.ConversionPattern=%-5p [%t] %c %x - %m%n

# FILE_APPENDER
log4j.appender.FILE_APPENDER=org.apache.log4j.RollingFileAppender
log4j.appender.FILE_APPENDER.File=MyLogFile.log
log4j.appender.FILE_APPENDER.MaxFileSize=1000KB
log4j.appender.FILE_APPENDER.MaxBackupIndex=2
log4j.appender.FILE_APPENDER.layout=org.apache.log4j.PatternLayout
log4j.appender.FILE_APPENDER.layout.ConversionPattern=%-5p [%t] %c - %m%n
1 Stella2 Stelle3 Stelle4 Stelle5 Stelle (1 voti, media: 5,00 di 5)
Loading...
You can leave a response, or trackback from your own site.

Leave a Reply

*