1. JAVA logging a confronto
Paolo Predonzani
paolo.predonzani@manydesigns.com
JUG Genova – 9 Marzo 2011
2. Logging... alla fine delle fini
Si tratta di sostituire questo:
System.out.println(“La variabile x vale: ” + x);
System.err.println(“Accesso negato: ” + filename);
...con questo:
Logger logger = LoggerFactory.getLogger(“mylogger”);
logger.debug(“La variabile x vale: ” + x);
logger.error(“Accesso negato: ” + filename);
3. Perché System.out e System.err
non sono sufficienti?
● Solo due stream non bastano per:
● Messaggi UI a carattere
● Messaggi tecnici sul funzionamento interno
● Traccia di sicurezza
● Altri...
Spesso si vogliono attivare/disattivare i messaggi
tecnici in maniera selettiva a seconda della
situazione. Esempio: i messaggi richiesti in
sviluppo possono essere molto diversi da quelli
richiesti in esercizio.
4. Il messaggio comunque è...
Fate logging (e non fate debugging)
“As personal choice, we tend not to use debuggers beyond getting a
stack trace or the value of a variable or two. One reason is that it
is easy to get lost in details of complicated data structures and
control flow; we find stepping through a program less productive
than thinking harder and adding output statements and self-checking
code at critical places. Clicking over statements takes longer than
scanning the output of judiciously-placed displays. It takes less
time to decide where to put print statements than to single-step to
the critical section of code, even assuming we know where that
is. More important, debugging statements stay with the program;
debugging sessions are transient.”
Brian W. Kernighan and Rob Pike
5. Output o così:
Nov 11, 2009 11:40:06 AM com.manydesigns.portofino.methods.PortofinoServletContextListener
createDatabaseAbstraction
INFO: Database product name: PostgreSQL
Nov 11, 2009 11:40:06 AM com.manydesigns.portofino.methods.PortofinoServletContextListener
createDatabaseAbstraction
INFO: Database product version: 8.3.5
Nov 11, 2009 11:40:06 AM com.manydesigns.portofino.methods.PortofinoServletContextListener
createDatabaseAbstraction
INFO: Database major/minor version: 8.3
Nov 11, 2009 11:40:06 AM com.manydesigns.portofino.methods.PortofinoServletContextListener
createDatabaseAbstraction
INFO: Driver name: PostgreSQL Native Driver
O così:
6. Logger (categorie di logging)
Un “logger” rappresenta una categoria di messaggi
collegati, da controllare in modo unificato.
Logger logger = LoggerFactory.getLogger(“security”);
Più spesso è semplicemente la categoria dei messaggi
prodotti da una certa classe:
Logger logger =
LoggerFactory.getLogger(“com.example.MyClass”);
O più semplicemente:
Logger logger =
LoggerFactory.getLogger(com.example.MyClass.class);
I logger formano una gerarchia: il carattere “.”
separa i padri/figli.
7. Livelli di logging
I livelli di logging indicano la gravità o importanza del
messaggio, es: trace, debug, info, warn, error.
logger.setLevel(Level.INFO);
logger.info(“Avvio programma”); // sarà stampato
int x = 2;
logger.debug(“x vale ” + x); // NON sarà stampato
I livelli di logging per ogni logger sono configurabili
programmaticamente (come in esempio) o più
frequentemente tramite file di configurazione .properties
o .xml
8. Esempio di configurazione xml
<configuration>
...
<logger name="com.example" level="info"/>
<logger name="com.example.MyClass" level="debug"/>
<logger name="org.hibernate" level="warn"/>
<root level="info">
<appender-ref ref="STDOUT"/>
</root>
</configuration>
9. Handler e formatter
● Uno Handler è un oggetto che si occupa della
scrittura fisica dei log su un file, su db, su
server di eventi, ecc.
● P.e.: un handler per i messaggi normali e uno per i
messaggi di sicurezza
● Più logger insististono su un handler
● Un Formatter è un oggetto che formatta in testo
una chiamata di logging e i suoi parametri
%d{HH:mm:ss.SSS} [userId=%X{userId},userName=%X{userName}]
%logger{40} [%F:%L]%n%level: %msg%n
13:09:49.026 [userId=1001,userName=predo]
c.m.p.database.JdbcConnectionProvider [ConnectionProvider.java:222]
WARN: Could not create database abstraction
10. Mapped Diagnostic Context
● Una mappa chiave-valore utile a identificare il
contesto di esecuzione, p.e.: l'utente, il client,
ecc.
11. Nested Diagnostic Context
● Uno stack per identificare il con contesto di esecuzione
● Permette di dire “siamo dentro all'applicativo X, modulo Y,
sottomodulo Z. Oppure di esprime contesti di algoritmi
ricorsivi.
12. Logging - il rovescio della medaglia:
proliferazione delle librerie
Perché tante librerie di logging?
● Java logging rilasciato tardi (JDK 1.4)
● Funzionalità distintive delle singole librerie
Problematiche:
● Più API da imparare
● Versioni diverse della stessa libreria
● Conflitti di classloading in ambienti server
13. Bisogna distinguere
● Implementazioni di base:
● Java logging (java.util.logging)
● Log4J
● Logback
● API di astrazione:
● Apache commons logging
● SLF4J
Perché astrarre?
● Per cambiare l'implementazione sottostante
● Per configurare in un solo punto
● Per formattare in modo omogeneo
14. Astrazione, implementazione
e bridging
Altra API o
framework
bridge
API
di astrazione
Adapter per Adapter per Adapter per
Java logging log4j logback
Java logging log4j logback
16. Java logging – configurazione
handlers = java.util.logging.ConsoleHandler, java.util.logging.FileHandler
.level = ALL
java.util.logging.ConsoleHandler.level = INFO
java.util.logging.FileHandler.level = ALL
java.util.logging.ConsoleHandler.formatter = java.util.logging.SimpleFormatter
17. Log4J esempio di configurazione
tramite propeties
log4j.rootLogger=debug, stdout, R
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
# Pattern to output the caller's file name and line number.
log4j.appender.stdout.layout.ConversionPattern=%5p [%t] (%F:%L) - %m%n
log4j.appender.R=org.apache.log4j.RollingFileAppender
log4j.appender.R.File=example.log
log4j.appender.R.MaxFileSize=100KB
# Keep one backup file
log4j.appender.R.MaxBackupIndex=1
log4j.appender.R.layout=org.apache.log4j.PatternLayout
log4j.appender.R.layout.ConversionPattern=%p %t %c - %m%n
18. Logback – configurazione xml
<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>
20. Logback – esempio di output
13:09:49.026 [userId=,userName=] c.m.p.database.JdbcConnectionProvider [ConnectionProvider.java:222]
WARN: Could not create database abstraction for portofino
org.postgresql.util.PSQLException: Connection refused. Check that the hostname and port are correct and that the
postmaster is accepting TCP/IP connections.
at org.postgresql.core.v3.ConnectionFactoryImpl.openConnectionImpl(ConnectionFactoryImpl.java:123) ~[postgresql-
8.3-606.jdbc3.jar:na]
at org.postgresql.core.ConnectionFactory.openConnection(ConnectionFactory.java:66) ~[postgresql-8.3-
606.jdbc3.jar:na]
at org.postgresql.jdbc2.AbstractJdbc2Connection.<init>(AbstractJdbc2Connection.java:124) ~[postgresql-8.3-
606.jdbc3.jar:na]
at org.postgresql.jdbc3.AbstractJdbc3Connection.<init>(AbstractJdbc3Connection.java:30) ~[postgresql-8.3-
606.jdbc3.jar:na]
at org.postgresql.jdbc3.Jdbc3Connection.<init>(Jdbc3Connection.java:24) ~[postgresql-8.3-606.jdbc3.jar:na]
at org.postgresql.Driver.makeConnection(Driver.java:386) ~[postgresql-8.3-606.jdbc3.jar:na]
at org.postgresql.Driver.connect(Driver.java:260) ~[postgresql-8.3-606.jdbc3.jar:na]
at java.sql.DriverManager.getConnection(DriverManager.java:525) ~[na:1.5.0]
at java.sql.DriverManager.getConnection(DriverManager.java:171) ~[na:1.5.0]
at com.manydesigns.portofino.database.JdbcConnectionProvider.acquireConnection(JdbcConnectionProvider.java:80)
~[JdbcConnectionProvider.class:na]
at com.manydesigns.portofino.database.ConnectionProvider.init(ConnectionProvider.java:165)
~[ConnectionProvider.class:na]
at
com.manydesigns.portofino.context.hibernate.HibernateContextImpl.loadConnections(HibernateContextImpl.java:137)
[HibernateContextImpl.class:na]
at com.manydesigns.portofino.servlets.PortofinoListener.createContext(PortofinoListener.java:328)
[PortofinoListener.class:na]
at com.manydesigns.portofino.servlets.PortofinoListener.contextInitialized(PortofinoListener.java:153)
[PortofinoListener.class:na]
at org.apache.catalina.core.StandardContext.listenerStart(StandardContext.java:4135) [catalina.jar:6.0.29]
at org.apache.catalina.core.StandardContext.start(StandardContext.java:4630) [catalina.jar:6.0.29]
at org.apache.catalina.core.ContainerBase.addChildInternal(ContainerBase.java:791) [catalina.jar:6.0.29]
at org.apache.catalina.core.ContainerBase.addChild(ContainerBase.java:771) [catalina.jar:6.0.29]
21. Confronto implementazioni
Java logging Log4j Logback
Licenza JDK Apache EPL/LGPL
Layout personalizzabili estensione sì sì
Numero di livelli 7 6 5
Configurazione properties properties, xml, xml, API,
API groovy
Configurazione per test o no no sì
condizionata
Reload configurazione no Sì tramite thread sì senza thread
Diagnostic context no Nested/Mapped Mapped
Jar/versione negli stacktrace no no sì
Diffusione scarsa ampia in crescita
Stabilità di API/configurazione ferma stabile config cambiata
Documentazione buona ottima ottima
API minimale buona molto buona
Messaggi parametrizzati no tramite log4j- sì
extras
22. Confronto API di astrazione
Commons logging SLF4J
Licenza Apache MIT
Numero di livelli 6 6
Configurazione properties -
Configurazione programmatica no no
Diffusione ampia ampia
Bridging no sì
Binding dell'implementazione discovery statica
23. Problematiche di logging su
application server
● Presenza/assenza delle librerie di logging
nell'application server:
● Presenza → conflitti con librerie in WEB-INF/lib
● Assenza → assenza della funzionalità
● Problemi di class loader
● Memory-leak
26. Presenza delle librerie su vari
appserver
Java log4j logback JCL SLF4J
logging
Tomcat 6 JULI
Jboss 5.1 Sì Sì Sì Sì
Glassfish 3 Sì
27. Soluzioni
● Usare una libreria di logging inusuale
● Essendo improbabile che l'appserver la contenga,
includerla nella webapp è sicuro
● Usare Java logging standard
● Includere la libreria nella webapp sempre
● sperare che non vada in conflitto con l'appserver
● va bene con API stabili
● Includere o non includere la libreria nella
webapp a seconda dell'appserver