
Mercredi dernier, lors de la première
journée de conférences du Devoxx, Ceki Gülcü, nous a présenté deux framework de
logs complémentaires SLF4J et logback.
SLF4J
Ceki Gülcü a d'abord
commencé sa présentation par la présentation de SFL4J (The Simple
Logging Facade for Java), qui consiste en une API (slf4j-api.jar) permettant
d'harmoniser les appels de log dans le code Java quelle que soit
l'implémentation choisie (log4j ou JUL par exemple). Il explique au passage la
possibilité de brancher sur son application utilisant SLF4J une implémentation
noop (no operation performed).
Le speaker (et créateur de SLF4J) présente ce dernier comme un potentiel doublon de commons-logging à quelques différences prêt comme le choix de l'implémentation choisie par le framework qui se fait automatiquement (dynamiquement) dans commons-logging et de manière manuelle (statique) dans SLF4J en important le jar adéquat dans le classpath : slf4j-logj12.jar pour log4j, slf4j-jdk14.jar pour JUL (java.util.logging) ou encore logback-classic.jar pour utiliser logback.
Le speaker (et créateur de SLF4J) présente ce dernier comme un potentiel doublon de commons-logging à quelques différences prêt comme le choix de l'implémentation choisie par le framework qui se fait automatiquement (dynamiquement) dans commons-logging et de manière manuelle (statique) dans SLF4J en important le jar adéquat dans le classpath : slf4j-logj12.jar pour log4j, slf4j-jdk14.jar pour JUL (java.util.logging) ou encore logback-classic.jar pour utiliser logback.
Le speaker est ensuite passé aux choses plus
concrètes en montrant quelques exemple d'appel SLF4J, il a notamment mis
l'accent sur la possibilité d'utiliser des messages paramétrés qui ne seront
construits (i.e. qui ne consommeront de ressources) que s'il doivent être
affichés. Pour démontrer son propos il a donné l'exemple suivant
:
| La vieille méthode | La méthode moins naïve mais verbeuse | La méthode SLF4J, intelligente et concise |
| logger.debug("Nom="+nom); |
if(logger.isDebugEnabled()){
logger.debug("Nom="+nom);
}
|
logger.debug("Nom={}",nom); |
Il a ensuite abordé les autres fonctionnalités offertes par SLF4J, parmis lesquelles :
- L'accès au MDC (Mapped Diagnostic
Context) qui permet d'attacher des variables de log à un Thread comme par
exemple le login de l'utilisateur ayant fait la requête permettant ainsi de
suivre les logs de sa session de manière triviale même au sein de logs
complètement entrelacés. Les informations stoquées dans le MDC sont ensuite
utilisables directement dans les patterns de log utilisés dans la configuration
de la couche de logging.
- Les Markers hiérarchisés qui permettent en quelque sorte d'affecter des tags aux différents logs. Les applications sont évidemment nombreuses mais Ceki Gülcü a donné un exemple assez simple où les Marker sont utilisés pour marquer les logs "confidentiels" et ou les différents Appender encryptent ces logs "confidentiels". Grâce aux Markers il est donc possible de créer des comportements spécifiques quelle que soit l'origine du log.
SLF4J a ensuite été abordé toujours sous l'angle d'un framework d'uniformisation des logs applicatifs mais cette fois-ci en regardant l'intégration de ce dernier sur des projets utilisant déjà un pou plusieurs systèmes de logs. Il est aisin possible de créer une véritable passerelle entre commons-logging, log4j, JUL avec SLF4J afin de centraliser la gestion et la configuration des logs d'une application. Pour cela il suffit de remplacer les jar d'implémentation du (des) système(s) utilisés (comme commons-logging.jar ou log4j.jar) par des jar d'interception de logs (respectivement jcl-over-slf4j.jar et log4j-over-slf4j.jar). Dans le cas de JUL il faut cependant faire un petit effort de paramètrage (déclaration d'un logger dans le rootLogger).
Pour ceux qui auraient simplement envie de remplacer (et non pas aggreger) le système de logs de leur application, Ceki Gülcü a créé un utilitaire simple de migration vers SLF4J : le SLF4J Migrator
Logback
La créateur du
framework nous a d'abord mis dans le bain en définissant LOGBack comme le
successeur de Log4J, expliquant brièvement que LOGBack est simplement une
évolution de ce dernier (par de rupture conceptuelle).
LogBack est divisé en 3 modules :
LogBack est divisé en 3 modules :
- Logback Core : Module de base étendu par les deux autres modules
- Logback Classic : Module de log des applications Java (implémente les API SLF4J décrites précédemment)
- Logback Access : Module d'accès aux logs du conteneur de Servlet (Tomcat, Jetty)
Logback utilise Joran en ce qui concerne la configuration des logs, ce qui signifie qu'il est possible de décrire énormément de choses dans la configuration (contrairement aux configurations sous forme de XML répondant à une DTD statique ou de fichiers properties). il est par ailleurs possible de gérer les inclusions de morceaux de configurations et ainsi rendre le paramétrage des logs extrêmement modulaires au sein d'un projet de grande taille.
Durant sa démonstration des possibilité de logback, le speaker a aussi montrer qu'il est possible de publier des MBeans spécifiques à logback dans JMX et ainsi contrôler les logs dynamiquement via une console comme JConsole (c'est d'ailleurs comme ça qu'il rafraichissait son instance de logback lors des démos).
Filters
Logback offre, à l'instar de
log4j, la possibilité d'utiliser des Filtres au sein de la configuration des
logs, ce qui permet de prendre (ou non) des logs en fonction de leur
correspondance à un filtre. Il est possible de créer des filtres sur des
expressions Java complexes (grâce à Juanino qui compile les expression java
contenues dans la configuration). On peut par exemple écrire une configuration
comme celle-ci avec logback :
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<filter class="ch.qos.logback.core.filter.EvaluatorFilter">
<evaluator name="myEval">
<expression>message.contains("contenu
recherché")</expression>
</evaluator>
<OnMismatch>NEUTRAL</OnMismatch>
<OnMatch>DENY</OnMatch>
</filter>
<layout>
<pattern>
%-4relative [%thread] %-5level %logger -
%msg%n
</pattern>
</layout></appender>
Les expressions n'ont cependant à
disposition que les informations contenues dans le LoggingEvent (dont par
exemple le MDC, le timestamp de log, le logger, le marker racine ...). Pour
plus d'informations sur l'utilisation des filtres, la doc se
trouvent ici
Il existe aussi un tpe un peu
particulier de Filtre dans logback qui sont les TurboFilter. ces filtres ne
sont pas attachés à un appender particulier et sont donc appelés à chaque fois
que le système doit créer une évènement de log (donc très tôt dans le processus
de log). Il sont utilisés, comme leur nom l'indique, pour accélérer la prise de
décision dans la gestion des filtres puisque si un appel au logger ne sera pas
loggué à cause d'un TurboFilter (DENIED) alors aucun évènement ne sera diffusé
dans le système. Les TurboFilter peuvent par exemple ressembler à ceci :
<turboFilter class="ch.qos.logback.classic.turbo.MDCFilter">
<MDCKey>login</MDCKey>
<Value>bobby</Value>
<OnMatch>ACCEPT</OnMatch>
</turboFilter>
<turboFilter class="ch.qos.logback.classic.turbo.MarkerFilter">
<Marker>inutile</Marker>
<OnMatch>DENY</OnMatch></turboFilter>Ceki Gülcü a poursuivi ses démonstrations
avec le très intéressant SiftingAppender qui est capable d'encapsuler
un appender paramétré (donc plusieurs appender différents au runtime) en
fonction d'attributs qui peuvent provenir du MDC (ou d'une valeur par
défaut).
Grâce à ce dernier, il est par
exemple possible de créer un appender par login ou par rôle dans une
application de manière très simple et sans toucher ou presque au code Java.
L'appender suivant définit par exemple un appender par rôle utilisateur en
fonction de la valeur role (rôle de l'utilisateur exécutant la requête)
contenue dans le MDC :
<configuration>
<appender name="SIFT" class="ch.qos.logback.classic.sift.SiftingAppender">
<discriminator>
<Key>role</Key>
<DefaultValue>ROLE_USER</DefaultValue>
</discriminator>
<sift>
<appender name="FILE-${role}" class="ch.qos.logback.core.FileAppender">
<File>${role}.log</File>
<Append>true</Append>
<layout class="ch.qos.logback.classic.PatternLayout">
<Pattern>%d [%thread] %level %mdc %logger{35} -
%msg%n</Pattern>
</layout>
</appender>
</sift>
</appender>
<root level="DEBUG">
<appender-ref ref="SIFT" />
</root>
</configuration>Cette
configuration stocke dans le fichier ROLE_USER.log les logs des
utilisateurs n'ayant pas de rôle défini dans le MDC. On voit bien ici que
plusieurs Appenders vont être instanciés avec chacun un nom différent
(FILE-ROLE_USER pour l'Appender par défaut).
Ceki Gülcü a enfin terminé en soulignant une
petite fonctionnalités tout à fait intéressante de logback qui permet de
suffixer les lignes de logs d'une stacktrace d'erreur par le nom et la version
du jar utilisé, ce qui permet lors du debug d'erreur de récupérer
instantanément cette information cruciale.
Exemple de log généré :
java.lang.NullPointerException at
com.xyz.Wombat(Wombat.java:57) ~[wombat-1.3.jar:1.3] at
com.xyz.Wombat(Wombat.java:76) ~[wombat-1.3.jar:1.3] at
sun.reflect.NativeMethodAccessorImpl.invoke0(Native
Method) ~[na:1.5.0_06]...Synthèse
Pour terminer, nous pouvons dire
que, étant nous mêmes utilisateurs de log4j sur nos projets quotidiens,
Ceki Gülcü nous a
convaincu que logback est bel et bien une évolution de Log4j (et non pas une
révolution) et qu'il semble très attrayant de tenter de passer à ce nouveau
framework (qui propose qui plus est nativement les API SLF4J)
A essayer donc !
Ressources
SLF4J
: http://www.slf4j.org/
LOGBack
: http://logback.qos.ch/
Blog de Ceki
Gulcu : http://ceki.blogspot.com/
Une
présentation du Jazzon09 quasi-similaire à celle du Devoxx par le même speaker
: http://beta.parleys.com/#id=357&st=5
