MokaByte 96 - Maggio 2005
  MokaByte 96 - Maggio 2005  

 

 

 

Log di applicazioni J2EE
Il framework Jakarta log4j

Ogni programmatore che abbia scritto una applicazione di media complessità si è prima o poi posto il problema di controllare il funzionamento del proprio codice sia in fase di sviluppo che successivamente durante il test e infine in produzione. Il framework log4j rappresenta lo strumento ideale per rispondere alle tipiche esigenze dello sviluppo di applicazioni J2EE.

Logging nelle applicazioni J2EE
I vari IDE oggi disponibili dispongono di una serie di funzionalità di debugging più o meno avanzate molto comode e potenti. Spesso chi approda a Java da linguaggi come Visual Basic o Delphi è abituato ad utilizzare tali strumenti per verificare il flusso del codice, lo stato delle variabili, inserendo breakpoint, watch di variabili e altri strumenti simili. Usare un debugger integrato è una buona soluzione nel caso in cui si stia sviluppando una applicazione standalone.
Nel mondo J2EE però il livello di complessità introdotto da tecnologie e architetture è in genere tale da rendere questo approccio certamente limitante e limitato. Le motivazioni per cui ogni programmatore Java Enterprise decide di utilizzare soluzioni alternative sono molte. Ecco un breve elenco:

Applicazioni distribuite, architetture stratificate: le applicazioni non sono mai costituite da un unico blocco applicativo ma in genere diversi moduli e diversi strati applicativi collaborano in modo distribuito e remoto gli uni dagli altri. Spesso diviene impossibile utilizzare l'ambiente di sviluppo se si devono tenere sotto controllo diversi moduli in esecuzioni in modo remoto o distribuito

Eterogeneità ed interoperabilità di moduli diversi: strettamente legato al contesto precedente, i diversi componenti che formano l'applicazione nel suo contesto devono essere eseguiti in modo diverso, spesso asincrono e devono essere monitorati in modo differente (debug dei moduli nuovi, controllo dei moduli in produzione, verifica di quelli in upgrade o manutenzione),

Sviluppo, test, produzione: il normale ciclo di sviluppo del software prevede differenti modalità di esecuzione. Si richiede spesso la necessità di controllare il diverso comportamento di un componente in funzione del contesto di esecuzione.
Spesso le sessioni di test basate su strumenti come JUnit, devono poter essere eseguite in automatico al variare del sistema grazie a semplici script di sistema.

Verifica iterativa: proprio perché i vari moduli possono cambiare nel tempo si rende spesso necessario tornare ad eseguire controlli basati sull'osservazione dei messaggi di log con diversa prospettiva. Mentre si esegue il modulo A che usa il modulo B si potrebbe riattivare una sessione di debug sul modulo B anche se questo ormai è stato esaustivamente testato.

Per questi motivi fondamentali, unitamente ad altre di carattere pratico, è ormai prassi comune considerare il log applicativo come un elemento dello sviluppo di una applicazione, tanto che si sono nel tempo proposti diversi framework e strumenti applicativi. Il più famoso che ha rimpiazzato ogni altro alternativo è certamente log4j, sviluppato dal gruppo Jakarta.

In questo articolo verrà presentato nelle sue funzionalità principali affrontando l'uso di questo sistema di log nei casi che si presentano più frequentemente: logging all'interno di applicazioni web in Tomcat, logging in applicazioni web o EJB in JBoss, logging di test in applicazioni stand alone.

 

Il framework log4j
Questo framework permette di inserire messaggi di log all'interno di una applicazione (sia J2EE che J2SE) in modo molto potente ed elegante.
Il concetto alla base di tutto il sistema è che durante il ciclo di sviluppo test e utilizzo di un componente software nasce l'esigenza di controllare a vario livello il funzionamento dello stesso.
Se quindi durante lo sviluppo e il test è utile essere informati di ogni più piccola informazione circa il flusso applicativo, quando il sistema sarà stato completamente debuggato e messo in produzione, è opportuno monitorare solamente gli eventi più gravi ed imprevisti (le eccezioni o i fatal error).
Per questo motivo log4j offre in modo molto semplice ed efficace la possibilità di "accendere" o "spegnere" il log di messaggi di livello basso (livello dei messaggi ALL e DEBUG) medio (livello dei messaggi INFO) o grave (ERROR e FATAL).
Combinando questa funzionalità con la possibilità di redirigere in modo dinamico e composito messaggi verso una o più destinazione (console, file, syslog, email ed altro ancora) appare chiaro quanto sia potente il sistema in questione.
Il fattore probabilmente più importante è la capacità di configurare e modificare dinamicamente il comportamento del log (livello, destinazioni) semplicemente modificando un file di configurazione esterno alla applicazione, senza la necessità di riscrivere o ricompilare il codice, tramite la definizione di livelli di messaggio è possibile in ogni momento.
Gli elementi principali del framework sono tre: loggers (e loro gerarchie), appenders o destinazioni e layout di stampa del messaggio.

 

I loggers e le gerarchie di loggers
Un logger è uno strumento che permette di inviare messaggi verso una determinata destinazione (vedi più avanti). Istanza della classe Logger (che dalla versione 1.2 della API sostituisce la Category) sono strutturati gerarchicamente in modo analogo ai package in Java dai quali prendono spunto per la organizzazione dei nomi. Ad esempio un logger di nome com.mokabyte assume il ruolo di padre del logger con nome com.mokabyte.mylogger, e antenato di com.mokabyte.mylogger.speciallogger.
L'organizzazione gerarchica dei logger è la base su cui è stato progettato il framework permettendone flessibilità e maneggevolezza.
Al vertice della gerarchia dei logger si trova il RootLooger che ha la caratteristica di esistere sempre e non può essere ricavato per nome ma tramite il metodo statico

Logger.getRootLogger();

Ogni altro logger invece deve essere ricavato tramite il nome assegnatogli per mezzo del metodo statico

Logger.getLogger(String name);

Si tratta di un factory method che restituisce un logger già pronto all'uso senza doverne invocare il costruttore: il nome passato come parametro comporta la creazione di un logger con quel nome.
Leggendo nella documentazione si scopre inoltre che internamente questo factory utilizza un singleton, per cui se in vari punti della applicazione si invoca lo stesso logger (stesso nome) si otterrà sempre la stessa istanza di logger. Quindi scrivendo

Logger loggerA = Logger.getLogger("myLogger");
Logger loggerB = Logger.getLogger("myLogger ");

si ottiene come risultato che le due variabili loggerA e loggerB punteranno alla stessa istanza di logger.
La presenza di un singleton internamente al framework ha anche un altro importante riflesso: l'inizializzazione tramite la lettura dei parametri di configurazione può essere fatta una sola volta per tutta l'applicazione, normalmente all'interno della classe principale (per una applicazione standalone) o nel punto di start per una applicazione web o EJB (vedi oltre).
Di seguito é riportata la definizione completa della classe Logger

package org.apache.log4j;

public class Logger {

  // metodi di creazione e factory
  public static Logger getRootLogger();
  public static Logger getLogger(String name);

  // metodi di stampa:
  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);

  // generico metodo di stampa
  public void log(Level l, Object message);
}

L'invocazione di uno dei metodi di stampa dà luogo a quella che in gergo si dice una request. Si possono quindi avere 5 livelli di request che corrispondono anche ai livelli che si possono assegnare ad un determinato logger. Dal più basso al più alto questi livelli sono

DEBUG, INFO, WARN, ERROR e FATAL

Quindi scrivendo

Logger.info("ecco un messaggio di avvertimento");

si genera un request INFO, ovvero si invia il messaggio contenuto tra virgolette verso la destinazione o le destinazioni (appender) associati al logger.
Ad ogni logger viene assegnato un livello di importanza e l'intersezione fra livello del logger con quello della request da luogo al comportamento del logger stesso: una request risulta essere attiva (ovvero alla sua invocazione il messaggio viene effettivamente inviato) se il livello della request é maggiore o uguale al livello assegnato al logger.
Quindi la request appena vista verrà eseguita solo se il logger ha livello DEBUG o INFO.

Anche se non si possono avere le stesse prestazione del C++ che utilizza il meccanismo di preprocessamento del codice (che però richiede la ricompilazione), la documentazione ufficiale di log4j specifica che una request disattivata non impatta sulle performance della applicazione complessiva: il messaggio non solo non verrà inviato, ma in base ad una efficiente ottimizzazione del codice il metodo non verrà nemmeno eseguito.
All'atto pratico i logger si usano cospargendo il codice di moltissime debug request, di alcune strategiche info request e di poche ma essenziali error request (in genere all'interno di una catch o al verificarsi di determinate condizioni).
La scelta sul numero, il tipo e la collocazione delle varie request all'interno del codice, é probabilmente una delle cose più difficili da stabilire. Si tratta come sempre di seguire il buon senso, di avvalersi dell'esperienza e di trovare il giusto compromesso.
Tutte queste request potranno essere disabilitate in fase di test o produzione, quando gli errori più grossolani si presume siano stati individuati ed eliminati.
L'organizzazione gerarchica dei logger consente di impostare i livelli dei vari logger in modo gerarchico: ad esempio associando il livello INFO ad un logger MyLogger, tutti i logger figli di MyLogger adotteranno tale livello, a meno che nella discesa della gerarchia non vi sia una ridefinizione.
Un esempio pratico chiarisce questo passaggio meglio di ogni altra spiegazione teorica; definendo

log4j.category.com.mokabyte=ERROR, MyConsole
log4j.category.com.mokabyte.community=INFO
log4j.category.com.mokabyte.community.web.MyClass=DEBUG

si impone il nodo base della gerarchia a livello ERROR (che riflette tale livello su tutti i figli); quindi, non essendo definito diversamente il logger com.mokabyte.util assume livello ERROR. Successivamente si assegna il livello INFO al logger com.mokabyte.community (quindi tutti i logger del tipo com.mokabyte.community.xxx.yyy sono assegnati a livello INFO) e solamente il logger log4j.category.com.mokabyte.community.web.MyClass verrà impostato a livello DEBUG.
Il livello del logger radice può essere specificato tramite la seguente istruzione

log4j.rootLogger=ERROR

Il fatto che vi sia una stretta corrispondenza fra nomi di logger e package.classi non è casuale:
essendo la gerarchia di nomi di classi e package formalmente corretta per specifica (il compilatore genera un errore se vi sono due classi con lo stesso nome all'interno dello stesso package), assegnano il Fully Qualified Name (FQN, nome della classe completo di package) ad un logger, si ottiene in modo semplice e sicuro un set di logger dai nomi univoci e gerarchici.
Ad esempio nella classe MyClass si potrebbe scrivere

package log4j.category.com.mokabyte.community.web;

public class MyClass {
  Logger logger = Logger.getLogger(MyClass.getClass.getName());
  ...
}

ed in questo caso il logger qui definito ad uso interno avrà lo stesso nome della classe, ovvero com.mokabyte.log4j.MyClass.
E' bene notare che da un punto di vista formale niente vieta di utilizzare nomi di fantasia sempre che si rispetti una organizzazione gerarchica di tali nomi; ovviamente tale scelta è sconsigliabile per ragioni di praticità.

 

I layout
Sui layout non vi molto da dire, dato che il loro funzionamento è piuttosto intuitivo: si tratta di oggetti che servono per definire la formattazione e la forma del messaggio.
Essendo una request costituita da messaggio testuale, un layout è generalmente una stringa utilizzata da un parser per formattare tale messaggio.
Normalmente per configurare il layout del messaggio si utilizzando pattern molto simili a quelli della funzione printf() del linguaggio C. Ecco un esempio

%d{ABSOLUTE} %-5p %c.%M() - %m%n

Ognuno parametro indica una precisa trasformazione grafica: %M ad esempio permette di stampare il nome del metodo all'interno del quale si è creato il messaggio, mentre %c indica il nome della classe. Invece %m stampa il messaggio della request.
Quindi la sequenza %c.%M() - %m relativamente al codice

public class MyClass{
  
Logger logger = Logger.getLogger(MyClass.getClass.getName());
  
  ...
  
  public void myMethod(){
    logger.info("ecco un messaggio di avvertimento");
  }

  ...

}

inserito all'interno del metodo myMethod() della classe MyClass, stampa un messaggio del tipo

[...] MyClass.myMethod () - ecco un messaggio di avvertimento

Per maggiori dettagli sulla sintassi di configurazione dei layout di log4j si consulti la documentazione presente sul sito web (vedi [log4j]).

 

Gli appenders
La relazione che lega un logger con un appender ricalca il cosiddetto pattern sorgente ascoltatore. Ogni request eseguita su un particolare logger, verrà inviata a tutti gli appender che si sono registrati presso quel logger.
Il framework mette a disposizione alcuni appender predefiniti come la console, file di testo, code di messaggi JMS, oppure i demoni syslog di Unix così come il logger di eventi di NT.
E' possibile definire un proprio appender componendo gli appender predefiniti, ad esempio reindirizzando i messaggi verso più appender contemporaneamente.
Anche per gli appender l'organizzazione gerarchica dei logger è utile: un messaggio prodotto da una determinata request verrà propagato, oltre all'appender registrato su quel determinato logger, anche verso tutti gli appender registrati sui logger più in alto nella gerarchia. Questo comportamento, detto appender additivity, può essere evitato settando a false l'additivity flag. Anche in questo caso in [log4j] si possono trovare tutti gli approfondimenti sia sul ruolo degli appender che sulle regole di additività.

 

La configurazione
Questa è probabilmente la parte più importante di tutta la gestione del sistema log4j: questa procedura vuol dire essenzialmente definire per ogni logger il suo livello e oltre agli appender e layout ad esso associati. La configurazione può essere fatta all'interno del codice o tramite file di configurazione (nel formato .properties o XML). Per ovvi motivi è da preferirsi la seconda soluzione che è anche quella che si analizzerà in questa sede.
Di seguito è riportato un breve esempio di file di configurazione tramite il quale si cercherà di dare un'idea della struttura e della sintassi che tale file deve avere. Ovviamente non si ha la pretesa di analizzare ogni singolo caso ed elemento, ma piuttosto di fornire una panoramica un'idea di massima su come utilizzare tale file per i casi più frequenti.
Verrà prima affrontato il caso del file di configurazione in formato .properties: esso è organizzato in sezioni, a seconda della parte che si vuole configurare. Per prima cosa si possono impostare alcuni parametri di configurazione generali. Ad esempio

log4j.appender.MyConsole=org.apache.log4j.ConsoleAppender

definisce un appender di nome MyConsole associato alla console (classe ConsoleAppender) dove verranno stampati tutti i messaggi.
Il formato dell'output potrà essere gestito in vari modi: in questo caso si sceglie di utilizzare un pattern layout manager

log4j.appender.MyConsole.layout=org.apache.log4j.PatternLayout

specificandone il formato tramite

log4j.appender.MyConsole.layout.ConversionPattern=%-4r %-5p [%t] %37c %3x - %m%n

Se si desiderasse configurare un appender su file, si potrebbe utilizzare il seguente codice per il quale si è preso spunto direttamente dalla documentazione ufficiale

# definisce un appender di nome FA
# FA è RollingFileAppender con dimensione massima del file di 10 MB
# Utilizza come layout il TTCCLayout, con le date in formato ISO8061
log4j.appender.FA=org.apache.log4j.RollingFileAppender
log4j.appender.FA.MaxFileSize=10MB
log4j.appender.FA.MaxBackupIndex=1
log4j.appender.FA.layout=org.apache.log4j.TTCCLayout
log4j.appender.FA.layout.ContextPrinting=enabled
log4j.appender.FA.layout.DateFormat=ISO8601

impostare il valore di soglia (livello di log) per tutta l'applicazione tramite la riga

log4j.threshold=[level]

dove al solito i valori di level al solito possono essere

OFF, FATAL, ERROR, WARN, INFO, DEBUG, ALL.

Si configurano uno o più appender tramite la seguente sintassi

log4j.appender.MyAppender=fully.qualified.name.of.appender.class

che specifica il nome della classe per un appender di nome MyAppender: il nome dell'appender può contenere punti separatori.
Successivamente si possono passare alcuni parametri di configurazione per l'appender.

log4j.appender.appenderName.option1=value1
...
log4j.appender.appenderName.optionN=valueN

A questo punto si può procedere nella definizione delle proprietà di configurazione dei logger, sia specificando il comportamento del logger root, sia specificando uno per uno ogni logger definito all'interno della applicazione. Nel primo caso si potrebbe scrivere ad esempio

log4j.rootLogger=[level], appenderName, appenderName, ...

dove level può assumere uno dei valori OFF, FATAL, ERROR, WARN, INFO, DEBUG, ALL, mentre i vari appenderName sono i nomi degli appender da utilizzare.
Questa soluzione, anche se non è molto flessibile, evita di dover configurare ogni singolo logger della applicazione (che essendo figli di rootLogger ne ereditano le caratteristiche). Il programmatore dovrà scegliere il giusto compromesso fra semplicità e potenza.
Infine se si volesse configurare ogni singolo logger si dovrà per ognuno inserire una riga simile a quella vista per il rootLogger: ad esempio

log4j.logger.com.mokabyte.community.util.io.PersistenceManager=DEBUG, MyConsole
log4j.logger.com.mokabyte.community.users.User=ERROR, MyConsole
log4j.logger.com.mokabyte.community.users.GuestUser=ERROR, MyConsole
log4j.logger.com.mokabyte.community.users.PowerUser=ERROR, MyConsole
log4j.logger.com.mokabyte. community.users.UserManager=ERROR, MyConsole


In questo caso, pur essendo l'applicazione molto semplice, si è costretti a dover specificare molte informazioni; se ci si dimentica di definire il comportamento di un logger verrà generata un errore bloccante.
Per chi fosse interessato ad una completa analisi dei parametri di configurazione si consiglia di leggere la pagina di commento inserita direttamente nel JavaDoc del log4j [log4jconf].
Nel caso in cui si volesse eseguire una configurazione veloce e semplice per alcune prove, si può utilizzare la classe BasicConfigurator che utilizza le impostazione di default proprie del framework e che non sono modificabili (appender console, livello DEBUG).
In alternativa al formato .properties del file di configurazione è possibile utilizzare il formato XML, che risulta essere leggermente più complesso ma certamente più potente (grazie alla potenza espressiva dell'XML ad esempio si possono creare aggregazioni in modo più chiaro).
Il formato XML è adottato da JBoss per cui è utile provare a dare uno sguardo al file log4j.xml, contenuto nella directory conf della configurazione utilizzata (default|all|minimal) per far partire il server.
Di seguito è riportato un estratto di alcuni passaggi interessanti: si noti la parte tramite la quale si definiscono alcuni appender:

Appender file
Definisce un appender associato ad un file di tipo rolling semplice (un file basato su una struttura di tipo coda di messaggi, con una dimensione massima in byte) oppure daily rolling (ogni giorno ne viene creato uno nuovo)

<appender name="FILE" class="org.jboss.logging.appender.DailyRollingFileAppender">
  <errorHandler class="org.jboss.logging.util.OnlyOnceErrorHandler"/>
  <param name="File" value="${jboss.server.home.dir}/log/server.log"/>
  <param name="Append" value="false"/>

  <!-- Rollover at midnight each day -->
  <param name="DatePattern" value="'.'yyyy-MM-dd"/>

  <layout class="org.apache.log4j.PatternLayout">
    <!-- The default pattern: Date Priority [Category] Message\n -->
    <param name="ConversionPattern" value="%d %-5p [%c].%M() %m%n"/>

   </layout>
</appender>

Appender console
Definisce un appender associato alla console di sistema. Si noti l'attributo Target associato alla System.out di Java; importantissimo l'attributo di soglia Threshold che permette di tagliare tutti i messaggi sotto un certo livello limitando in questo modo l'affollarsi di messaggi indesiderati per quell'appender. Si potrebbe voler inviare migliaia di messaggi verso appender FILE ma limitare la console (notoriamente più lenta) a stampare solo i messaggi a livello ERROR

<appender name="CONSOLE" class="org.apache.log4j.ConsoleAppender">
  <errorHandler class="org.jboss.logging.util.OnlyOnceErrorHandler"/>
  <param name="Target" value="System.out"/>
  <param name="Threshold" value="DEBUG"/>

  <layout class="org.apache.log4j.PatternLayout">
    <!-- The default pattern: Date Priority [Category] Message\n -->
    <param name="ConversionPattern" value="%d{ABSOLUTE} %-5p %c.%M() - %m%n"/>
  </layout>
</appender>

Appender mail
Permette di inviare mail con i messaggi di log. Dato l'alto overhead che può causare tale appender si faccia attenzione ad associarlo solo a messaggi di livello molto alto (soglia impostata a ERROR)

<appender name="SMTP" class="org.apache.log4j.net.SMTPAppender">
  <errorHandler class="org.jboss.logging.util.OnlyOnceErrorHandler"/>
  <param name="Threshold" value="ERROR"/>
  <param name="To" value="admin@myhost.domain.com"/>
  <param name="From" value="nobody@myhost.domain.com"/>
  <param name="Subject" value="JBoss Sever Errors"/>
  <param name="SMTPHost" value="localhost"/>
  <param name="BufferSize" value="10"/>
  <layout class="org.apache.log4j.PatternLayout">
    <param name="ConversionPattern" value="[%d{ABSOLUTE},%c{1}] %m%n"/>
  </layout>
</appender>

Appender async
E' un appender particolarmente utile per creare aggregazioni di appender con invio di messaggi asincroni rispetto alla esecuzione del codice applicativo

<appender name="ASYNC" class="org.apache.log4j.AsyncAppender">
  <errorHandler class="org.jboss.logging.util.OnlyOnceErrorHandler"/>
  <appender-ref ref="FILE"/>
  <appender-ref ref="CONSOLE"/>
  <appender-ref ref="SMTP"/>
</appender>

Infine alcune categorie con associati i livelli

<category name="org.apache.commons">
  <priority value="INFO"/>
</category>

<category name="org.jboss">
  <priority value="INFO"/>
</category>


<category name="com.mokabyte">
  <priority value="ERROR"/>
</category>

<category name="com.mokabyte.mvc.servlets">
  <priority value="ERROR"/>
</category>

<category name="com.mokabyte.mvc.actions">
  <priority value="ERROR"/>
</category>

<category name="com.mokabyte.community">
  <priority value="INFO"/>
</category>

Ecco infine il codice XML per la definizione del root level dei messaggi

<root>
  <appender-ref ref="CONSOLE"/>
  <appender-ref ref="FILE"/>
</root>

Logging in J2EE
Una applicazione J2EE è caratterizzata da una serie di requisiti architetturali e tecnologici non banali; spesso il buon funzionamento (ma anche il cattivo) dipendono da molti aspetti non facilmente individuabili o isolabili.
Un tipico applicativo è in genere costituito da componenti, assemblati in strati, comunicanti tramite protocolli di vario tipo (CORBA, RMI/IIOP, JNP, AJP, HTTP, etc.) per cui è spesso difficile capire dove si sia verificato il problema quando qualcosa non va come dovrebbe.
Per questo motivo è prassi comune spargere nel codice nei vari punti cruciali messaggi di log di vario tipo per capire in ogni istante cosa stia succedendo.
Questo genere di approccio porta in alcuni casi ad una eccessiva ridondanza dei messaggi, cosa che rende difficile nello stesso modo l'interpretazione dell'accaduto. Si pensi ad esempio al caso in cui all'interno di una applicazione web basata su MVC, una Action esegue una chiamata remota ad un session bean di façade. In questo caso si può sintetizzare l'applicazione come strutturata su due livelli (strato web e strato EJB).
Se si verifica un errore all'interno del session bean, è presumibile che verrà generato un messaggio di errore nello strato EJB, mentre la propagazione/wrapping delle eccezioni verso lo strato web, darà luogo ad un messaggio anche in questo livello.
Per lo stesso errore si hanno quindi due messaggi corrispondenti allo stesso evento: il maintainer della applicazione si ritrova quindi due file di log da controllare e la ridondanza di informazioni che ne deriva potrebbe rendere meno semplice l'individuazione del problema.
Per questo motivo la propagazione delle eccezioni, unitamente alla scelta del livello di log, sono aspetti molto importanti. Data la vastità dell'argomento si parlerà di questi temi in un articolo che verrà pubblicato su MokaByte nei prossimi mesi.
Inizializzazione di log4j: il problema del punto di inizio
Il problema principale relativo all'utilizzo di log4j è capire dove e quando debba essere eseguita la inizializzazione del framework: essendo questa una operazione che deve essere eseguita una volta sola (successive inizializzazioni possono portare a problemi se non al blocco del sistema di log) e prima di ogni altra (per poter monitorare tutte le altre operazioni del sistema) è importante identificare il punto di inizio della applicazione.
Questo rappresenta un po' il problema dell'uovo e della gallina: fino a quando non ho inizializzato il logger non posso monitorare l'insorgere di problemi, ma uno dei problemi che si potrebbero verificare è proprio l'impossibilità di eseguire la inizializzazione del logger (ad esempio il file lo4j.properties o log4j.xml non viene trovato).
Per questo motivo una soluzione che è divenuta ormai uso comune (si veda i messaggi di log allo start di JBoss) è la seguente:

  1. eseguire sempre allo start della applicazione una inizializzazione di default senza parametri tramite il metodo senza parametri configure() della classe BasicConfigurator; questa operazione dovrebbe poter essere sempre eseguita tranne in casi di problemi molto gravi. A questo punto l'applicazione dispone di un logger di default con il quale effettuare il controllo delle operazioni successive.
  2. Al momento opportuno (poco dopo l'inizializzazione di default) eseguire il caricamento, controllato dal logger default, del file di properties e procedere alla re-inizializzazione parametrica di log4j. Per forzare il ri-caricamento delle proprietà di configurazione si può utilizzare il metodo resetConfiguration() come nel seguente pezzo di codice

    try{
      Properties LogProperties = new Properties();
      // ricava il nome del file di properties dal sistema
      logPropertiesFilename= this.getProperty("logPropertiesFilename");
      logger.debug("nome del file di proprietà log4j="+logPropertiesFilename);
      if (logPropertiesFilename == null || logPropertiesFilename.equals("")){
        logger.info("Il logPropertiesFilename è nullo si usa quello di default");
        logPropertiesFilename = "WEB-INF/log4j.properties";
      }
      else{
        logger.info("Caricamento delle proprietà di log dal file
                    "+logPropertiesFilename);
      }

      LogProperties.load(servletContext.getResourceAsStream(logPropertiesFilename));
      logger.debug("LogProperties caricato "+LogProperties);
      org.apache.log4j.LogManager.resetConfiguration();
      PropertyConfigurator.configure(LogProperties);
      logger = Logger.getLogger(Log4jInitBean.class.getName());

      logger.info("----------------------------------------------------------");
      logger.info(" Inizializzazione Log4J effettuata ");
      logger.info("----------------------------------------------------------");
    }
    catch(Exception e){
      String msg ="Exception durante la procedura di riconfigurazione di Log4j:"+e;
      logger.error(msg);
      throw new MokaByteInitException(msg);
    }

  3. Nel caso in cui qualcosa non vada per il verso giusto durante il caricamento del file di proprietà il sistema potrà comunque usare il logger di default precedentemente inizializzato.

Il punto fondamentale a questo punto è quindi capire dove e quando l'applicazione esegue lo start, in modo da inserire tale procedura.
Il punto di inizio varia profondamente a seconda che si tratti di una applicazione standalone, una applicazione web o una applicazione EJB.
Per il primo caso la soluzione è piuttosto semplice: lo start point è nel metodo main() della classe principale, per cui qui possono essere eseguite senza particolari problemi le operazioni di configurazione default e parametrica.

 

Start point in una web application e inizializzazione in Tomcat
Nel caso di una applicazione web le cose sono un po' più complesse, dato che non si può avere la certezza di quale classe venga eseguita per prima: la prima servlet della servlet chain? Il primo listener? Un primo POJO? Quasi mai si ha la certezza, specie se l'applicazione viene poi modificata nel tempo o se viene eseguita in web container differenti.
Dopo diverse soluzioni che ho personalmente provato ed implementato, ho adottato una soluzione standard per tutte le applicazioni sviluppate dal nostro gruppo di lavoro: il nostro framework web è basato su una serie di librerie web all'interno delle quali vi è un listener di contesto.
Tale listener attiva fra le altre cose un bean per la inizializzazione di default del log4j.
Quando un utente si collega a una applicazione il listener di contesto è la prima cosa che viene eseguita dal container, per cui si può essere sufficientemente sicuri che sia effettuata la inizializzazione del logger di default.
Successivamente il sistema prevede una serie di inizializzatori configurabili tramite file XML (init-beans.xml): il concetto è simile alle Action di Struts, tranne che qui i vari bean di inizializzazione sono definiti ed eseguiti alla creazione del context o della sessione utente. Per questo è stato definito un bean apposito per la riconfigurazione di log4j tramite file di proprietà.
Ecco la definizione del bean log4jInitBean inserito all'interno

<init-bean name="log4jInitBean"
           classname="com.mokabyte.mvc.beans.commons.Log4jInitBean"
           scope="context">
  <init-params>
    <init-param>
      <param-name>logPropertiesFilename</param-name>
      <param-value>WEB-INF/log4j.properties</param-value>
    </init-param>
  </init-params>
</init-bean>

Commentando o scommentando tale porzione di codice si può in ogni momento attivare il configuratore di log allo start della applicazione web. Dato che Tomcat non possiede un sistema di inizializzazione automatica del logger, in questo caso si terrà attivo tale inizializzatore.
In questo modo ogni applicazione web avrà un suo file di configurazione autocontenuto all'interno del file JAR di deploy: questa organizzazione è molto comoda se si desidera tenere l'organizzazione il più pulita possibile.

 

Inizializzazione in JBoss
JBoss si comporta in modo diverso rispetto al container web Tomcat. Sia che si tratti di una applicazione web che di una EJB, l'application server contiene al suo interno un unico logger configurabile in modo unico tramite il file log4j.xml.
Per questo motivo se si tentasse di effettuare una inizializzazione personalizzata tramite il BasicConfigurator si otterrebbe un errore non bloccante per il server ma bloccante per la produzione dei messaggi (in pratica più nessun messaggio di log apparirebbe in console o su file).
All'interno del file log4j.xml si dovranno quindi inserire tutte le istruzioni di configurazione di tutte le applicazioni cosa che potrebbe rendere le cose un po' meno pulite ed isolate.
JBoss però riesce a sopperire a questa mancanza di pulizia con una potente funzionalità: il log4j.xml viene infatti monitorato dal container, per cui ogni modifica effettuata al file viene ricaricata in tempo reale (o a intervalli regolari a seconda della configurazione), permettendo di apportare modifiche ai log senza la necessità di ri-deployare o restartare l'applicazione (come invece si rende necessario con il sistema offerto da Tomcat).
Da notare infine che l'utilizzo del sistema componibile di bean di inizializzazione basati sul file init-bens.xml visto poco sopra, permette di deployare una applicazione web indifferentemente su Tomcat o JBoss semplicemente commentando o commentando una piccola porzione di XML senza dover riscrivere o ricompilare l'applicazione.

 

Conclusione
Il framework log4j è certamente uno dei prodotti di casa Jakarta di minor rilievo se paragonato ad oggetti come Struts, Tomcat, e MyFaces.
Questo prodotto però, per la sua semplicità d'uso e per la potenza delle funzionalità offerte, è diventato di fatto il sistema di log più utilizzato in ambito Java (e non solo grazie a porting per linguaggi come C e C#) soppiantando quello proposto da Sun ed introdotto con il JDK 1.4.
Un uso corretto di questo strumento permette in ogni momento di tenere sotto controllo il comportamento di una applicazione, anche realizzando retro controlli di sistemi già in produzione nel caso si verifichino problemi imprevisti.

 

Bibliografia
[log4j] - Home page del progetto log4j, http://jakarta.apache.org/log4j
[log4jdocs] - Documentazione ufficiale di log4j
http://jakarta.apache.org/log4j/docs/documentation.html