Configurare path relativi per Log4j utilizzando Spring
Uno dei problemi più fastidiosi quando si esegue il deploy di una web application Java è la corretta configurazione del logging. In questo articolo vedremo come Spring ci può venire in aiuto.
Path assoluti vs path relativi
Il problema principale dei path assoluti è che vanno modificati per ogni installazione dell'applicazione, richiedendo tempo e attenzione. Proviamo allora a configurare la web application per utilizzare path relativi.
Qui sotto vediamo un primo tentativo di configurazione che scrive su file (salvare il file come /WEB-INF/classes/log4j.properties):
log4j.rootLogger=ERROR, stdout, rollingFile log4j.appender.stdout=org.apache.log4j.ConsoleAppender log4j.appender.stdout.layout=org.apache.log4j.PatternLayout log4j.appender.stdout.layout.ConversionPattern=%d %p [%c] - <%m>%n log4j.appender.rollingFile=org.apache.log4j.RollingFileAppender log4j.appender.rollingFile.File=WEB-INF/logs/application.log log4j.appender.rollingFile.MaxFileSize=512KB log4j.appender.rollingFile.MaxBackupIndex=10 log4j.appender.rollingFile.layout=org.apache.log4j.PatternLayout log4j.appender.rollingFile.layout.ConversionPattern=%d %p [%c] - %m%n log4j.appender.rollingFile.Encoding=UTF-8
Il file scelto per i log è WEB-INF/logs/application.log, ma se proviamo questa soluzione (basta installare l'applicazione, scrivere del codice che invoca i metodi di Log4j e far partire Tomcat) ci rendiamo conto che i path relativi non hanno funzionano come vorremmo! I file vengono scritti infatti in TOMCAT_HOME/bin/WEB-INF/logs; questo in molti casi non è accettabile, per motivi di sicurezza e stabilità.
Non solo, se scriviamo questo (nel file log4j.properties):
log4j.appender.rollingFile.File=/WEB-INF/logs/application.log
(notare lo slash "/" prima di "WEB-INF"), addirittura tenterà di scrivere in C:\WEB-INF\logs (Windows) o in /WEB-INF/logs (Linux/Unix). A quanto pare quindi non c'è un modo facile e soprattuto standard (cioè senza dover ricorrere a codice o librerie) per indicare un path relativo per Log4j. Bisogna usare un path assoluto.
Utilizzare i path assoluti però è una strada in salita: dovremmo personalizzare i nostri pacchetti per ogni installazione, rubando tempo prezioso allo sviluppo. Come fare allora?
Spring ci viene in aiuto
Come al solito gli ingegneri di Spring hanno pensato a tutto, anche a questa eventualità, inventando un modo veramente poco invasivo e allo stesso tempo flessibile per superare questa difficoltà: il suo nome è Log4jWebConfigurer.
Questa classe si occupa della configurazione appunto di Log4j ed è stata pensata proprio per superare le difficoltà che abbiamo visto sopra. Il suo funzionamento è semplice: ai file di configurazione viene messa a disposizione una nuova variabile ${webapp.root}, che contiene il percorso assoluto della web application nel filesystem. La nostra configurazione quindi diventera:
log4j.appender.rollingFile.File=${webapp.root}/WEB-INF/logs/application.log
Configurare Spring
Per prima cosa, se non l'abbiamo già fatto, aggiungiamo spring.jar al classpath, copiandolo in /WEB-INF/lib.
Apriamo ora il file /WEB-INF/web.xml, e aggiungiamo queste righe prima delle definizioni di <servlet>:
<listener> <listener-class>org.springframework.web.util.Log4jConfigListener</listener-class> </listener>
Purtroppo questa soluzione non basta, perché rimane un problema: nella root del filesystem viene creata una directory "${webapp.root}" (esattamente come è scritta)!! Ma allora siamo al punto di partenza, direte voi...
Il problema è che, molto discutibilmente, Log4j prende l'iniziativa: si accorge che nel classpath (in /WEB-INF/classes appunto) c'è un file che si chiama log4j.properties e lo utilizza per auto-configurarsi. Tutto ciò avviene in maniera autonoma, quindi senza aspettare che venga definita la famosa variabile ${webapp.root}, da qui il problema del percorso. Se non ci avete capito nulla, non vi preoccupate: io stesso facevo fatica a crederlo una volta scoperto!
Per fortuna però gli autori di Spring hanno pensato anche a questo, introducendo il context-param log4jConfigLocation, vediamo come usarlo.
Innanzi tutto bisogna assolutamente cambiare nome al file log4j.properties, altrimenti Log4j tenterà di leggerlo da sè, combinando quindi i guai che abbiamo visto sopra. Potremmo chiamarlo ad esempio log4j-myapp.properties.
Poi bisogna aggiungere queste righe a web.xml:
<context-param> <param-name>log4jConfigLocation</param-name> <param-value>/WEB-INF/classes/log4j-myapp.properties</param-value> </context-param>
Et voilà! Log4j piegato ai nostri voleri ;)
Non finisce qui...
Cosa succederebbe se ci fossero più web application che usano questo meccanismo? Molto semplice: una gran confusione! Il fatto è che la variabile ${webapp.root} viene definita come system property; la specifica delle Servlet non è molto chiara a tal punto, in linea teorica dovrebbe essere possibile isolare le system properties per web application, ma purtroppo in Tomcat non è così.
Non vorrei sembrare ripetitivo, ma mi tocca: anche in questo caso, ancora una volta, Spring ha pensato ad una soluzione...
Possiamo definire un nuovo context-param:
<context-param> <param-name>webAppRootKey</param-name> <param-value>root-mia-istanza</param-value> </context-param>
Questo parametro specifica il nome della variabile di sistema che indica la root. Dovremmo quindi mettere mano (l'ultima volta!) al nostro file di configurazione:
log4j.appender.rollingFile.File=${root-mia-istanza}/WEB-INF/logs/application.log
L'unico accorgimento a questo punto è di utilizzare un nome differente per ogni istanza di web application, per evitare conflitti.
Conclusioni
Come vedete i problemi ci sono, ma ci sono anche le soluzioni. Sia Log4j che Spring hanno dei difetti, ma i pregi li superano di gran lunga, per cui bisogna fare buon viso a cattivo gioco e non darsi per vinti accontentandosi di soluzioni parziali o poco pratiche.
Linkografia
- dimitri's blog
- Aggiungi un commento
- 4678 letture
- English


Ottimo, mi hai risolto n-esimo problema
multi-istanza
Fantastico
Guerra atomica di framework per scrivere un log!
> Non vorrei essere troppo
> Non vorrei essere troppo lapidario, ma forse dovevi indagare prima come
> avviene la scrittura dei log del tuo servlet container o application server.
> Quando viene avviato il server, nella riga di comando ci sono sempre un po' di
> variabili di ambiente che servono a qualcosa :D Nel caso di tomcat:
> log4j.appender.rollingFile.File=${catalina.home}/logs/tuaapp.log
Uno degli scopi dell'articolo è mostrare come ottenere una web application che utilizzi *in automatico* dei path relativi per salvare i file di log. Nel primo paragrafo parlo appunto di come *durante lo sviluppo* sia scomodo dover configurare ogni volta dei path assoluti. E non pensare che sia un'attività rara: pensa a quando si fa un branch, quando si deve fare un bugfix su una vecchia applicazione, eccetera.
E non scendo nel merito di quanto sia deprecabile configurare una web application da... riga di comando!?!
> E ancora: Che
> intendi quando dici occorre rinominare il file di log4j? Mai sentito parlare
> del Log4jConfigListener? Stai facendo un po' di confusione!
Intendo quello che c'è scritto: se non rinomini log4j.properties in qualcos'altro, Log4J se lo legge *prima* di Log4jConfigListener, ignorando l'eventuale variabile ${webapp.root}
> Ps: uso Spring da
> anni ma ti assicuro che non serve fare tutto questo casino per impostare un
> appender per Log4J!
Per impostare un appender che scriva in un path *relativo alla webapp* però sì. Se conosci qualche altro metodo ti prego di scrivermi in privato così se vuoi posso pubblicare il tuo articolo a fianco di questo, che ne dici?
Ciao e grazie ;)
Ecco lo sapevo .. te la sei
Ma no :)
Valà, prendersela per delle critiche costruttive? Non sia mai...
Guarda, ti do ragione sul fatto che l'ultimo punto ("Non finisce qui...") effettivamente è una forzatura; il mio scopo era illustrare come ottenere i log su path relativi una volta per tutte all'inizio dello sviluppo (al checkout, all'installazione, come ti pare) e l'ultima soluzione (rinominare la variabile) va un po' contro questo principio.
Riguardo alla linea di comando... beh scusa l'hai scritto tu, che ci posso fare???
Piuttosto, mandami una mail in privato invece di postare anonimamente, mi interessa conoscerti per scambiare quattro chiacchiere. Inoltre, dopo questi tuoi interventi, mi sono convinto ad aggiornare l'articolo con queste nuove "scoperte" e ho bisogno di qualcuno che mi faccia una revisione senza paura di fare osservazioni puntigliose ;)
Dimitri
Riga di comando!