Journalisation avec SLF4J

Baptiste Wicht

Cet article va vous présenter SLF4J qui est une couche d'abstraction de journalisation. Cette API a plusieurs avantages qui seront présentés dans cet article ainsi que les différentes fonctionnalités de cette librairie.

Version anglophone - English Version : Logging with SLF4J.

Commentez ♪

- Titre : Journalisation avec SLF4J

- Auteur : Baptiste Wicht

- Parution : 20 janvier 2010

- Mise à jour : 29 janvier 2010

- Licence : Les sources présentées sur cette page sont libres de droits et vous pouvez les utiliser à votre convenance. Par contre, la page de présentation constitue une œuvre intellectuelle protégée par les droits d'auteur. Copyright ® 2009 Baptiste Wicht. Aucune reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.

Sommaire

- I. Introduction

- II. Sélection de l'implémentation de journalisation

- III. Redirection des appels depuis d'autres implémentations

- IV. Utilisation de l'API

- V. Les Marker

- VI. Accès au MDC

- VII. Conclusion

- VII-1. Remerciements

I - Introduction

SLF4J est une couche d'abstraction pour les API de journalisation Java. Le principe est à peu près similaire à celui de Jakarta Commons Logging. Les avantages de l'utilisation d'une telle couche d'abstraction permettent de s'abstraire de l'implémentation utilisée. Ainsi, il est possible de changer facilement d'implémentation de journalisation sans avoir à toucher la base de code. Au plus, la configuration de l'implémentation de journalisation doit être modifiée. Et enfin, dans le cas de la conception d'une librairie, cela permet de laisser à l'utilisateur de cette librairie le choix du système de journalisation.

Vous me direz : si Commons Logging fait déjà cela, pourquoi un autre framework d'abstraction de logging ? Tout simplement parce que Commons Logging a ses défauts. Le premier gros défaut de Commons Logging concerne le chargement de l'implémentation de journalisation. En effet, la recherche de l'implémentation se fait dynamiquement à l'implémentation via un système de classloader. Or cette méthode peut poser problème dans certaines situations, par exemple lorsque l'application utilise des classloaders personnalisés ou alors au sein d'une architecture OSGi. De plus, l'implémentation utilisée par Commons Logging peut causer des fuites mémoires.

En outre, Commons Logging nous contraint à tester si un niveau de log est activé ou non avant de faire des logs contenant des concaténations pouvant se révéler lourdes au long de l'application.

Nous allons voir que SLF4J permet de résoudre ces problèmes de manière efficace.

Vous pouvez télécharger la distribution officielle sur le site de SLFJ4J.

II - Sélection de l'implémentation de journalisation

Contrairement à Commons Logging, SLF4J ne résout pas l'implémentation de journalisation à l'exécution, mais à la compilation à l'aide d'une API de bridging. En plus du JAR de l'API de SLF4J, il faut inclure les JAR suivants : celui qui fait office de pont entre SLF4J et l'implémentation et celui de l'implémentation utilisée. Voici ce que cela pourrait donner avec Log4J :

Sélection d'une implémentation

Tout cela effectué uniquement par l'ajout de JAR au classpath. Aucun besoin de configurer quoi que ce soit mise à part l'implémentation. Il faut cependant veiller à n'utiliser que l'interface de SLF4J sans quoi l'utilisation d'une couche d'abstraction n'a plus aucun intérêt.

III - Redirection des appels depuis d'autres implémentations

En plus de permettre une abstraction de la couche de journalisation, SLF4J offre également la possibilité de rediriger des appels à une implémentation vers SLF4j qui lui-même les redirigera vers l'implémentation choisie.

Pour cela, il suffit d'utiliser le JAR xxx-to-slf4j.jar qui va intercepter les appels de xxx pour les rediriger vers l'API SLF4J.

Le diagramme suivant montre exactement comment sont redirigés les appels :

Redirection des appels

Il faut bien entendu ne pas mettre dans le classpath le JAR de l'implémentation que vous voulez rediriger. Et plus important encore, il ne faut surtout pas utiliser la redirection d'une API tout en utilisant cette API comme implémentation de journalisation, car cela créerait un cycle infini qui ferait planter votre programme à coup sûr.

IV - Utilisation de l'API

L'API de SLF4J n'est pas très compliquée et ressemble beaucoup à celles des autres implémentations ou abstractions telles que Log4J ou Jakarta Commons Logging. Voici comment récupérer un logger :

org.slf4j.Logger logger = org.slf4j.LoggerFactory.getLogger(HelloWorld.class);

Ensuite, vous pouvez l'utiliser comme les autres implémentations à l'aide des méthodes debug(), info(), warn(), trace() et error(). Par exemple :

logger.info("Hello World");

Mais ce n'est pas là que réside la force de SLF4J. Prenons un exemple simple de logging :

logger.debug("Info : x = " + info.getX() + ", y = " + info.getY() + ", str = " + infos.getStr());

Cela veut dire qu'à chaque fois que cette ligne de code sera exécutée, une concaténation sera faite. Cela peut vite s'avérer être lourd si la méthode est exécutée de manière régulière. C'est pourquoi il faut préalablement tester si le niveau est activé :

if(logger.isDebugEnabled()){
logger.debug("Info : x = " + info.getX() + ", y = " + info.getY() + ", str = " + infos.getStr());
}

Cette fois, les performances sont garanties, mais ce genre de code n'est pas du tout esthétique et devient rapidement lourd. D'autant plus que le test est déjà fait dans la méthode debug qui n'affichera rien si le niveau n'est pas activé. Alors pourquoi faire le travail deux fois ? SLF4J propose donc une nouvelle alternative assez équivalente à la méthode printf de Java :

logger.debug("Info : x = {}, y = {}, str = {}", new Object[]{info.getX(), info.getY(), infos.getStr()});

Les {} vont être remplacés par les valeurs passées en paramètres. Ainsi, la concaténation va se faire uniquement si le niveau de log est activé. De plus, esthétiquement, ce code est beaucoup plus propre que les deux précédents.

Dans le cas où la récupération des informations à journaliser serait lourde, il faudrait néanmoins utiliser la première technique consistant à tester si le niveau de log est activé avant de faire la journalisation. Par exemple, nous pourrions imaginer une méthode getDebugInfos() sur un objet qui fait beaucoup d'opérations pour récupérer des informations. Cette méthode ne doit pas être exécutée si le niveau de log n'est pas activé.

V - Les Marker

Les Marker permettent essentiellement d'associer des tags à des logs. Ces tags permettent ensuite aux différents appenders de prendre en charge différemment certains logs. Imaginons par exemple un appender qui écrirait de manière cryptée les données et qui ne devrait être utilisé que sur les données marquées comme confidentielles. Les Marker vont nous permettre d'implémenter une telle chose.

Cette fonctionnalité n'est accessible qu'avec l'implémentation LogBack : il s'agit en effet de la seule qui implémente les Marker. Néanmoins, vous pourrez utiliser les Marker avec les autres implémentations, bien que cela n'aura aucun effet.

Imaginons par exemple un appender nommé CryptAppender qui encode le log selon un algorithme quelconque. Nous pourrions le configurer ainsi :

<appender name="CRYPTED" class="CryptAppender">
<layout class="ch.qos.logback.classic.html.HTMLLayout">
<pattern>?te%-5level%logger%msg</pattern>
<throwableRenderer class="ch.qos.logback.classic.html.DefaultThrowableRenderer" />
</layout>
<evaluator class="ch.qos.logback.classic.boolex.OnMarkerEvaluator">
<marker>CONFIDENTIAL</marker>
</evaluator>
</appender>

Le OnMarkerEvaluator permet de ne sélectionner que les logs ayant été markés avec un certain tag. Dans notre cas, il faut marker les logs confidentiels avec CONFIDENTIAL. Voici comment procéder :

Marker confidentialMarker = MarkerFactory.getMarker("CONFIDENTIAL");
logger.error(confidentialMarker, "C'est confidentiel !");

Cela n'affichera donc pas le log directement, mais le log de manière cryptée.

Une autre utilisation de cette fonctionnalité pourrait, par exemple, concerner l'envoi par email de certains logs.
Vous pouvez bien entendu configurer plusieurs appenders pour le même tag. Vous pouvez également créer une hiérarchie de Marker.

Marker parentMarker = MarkerFactory.getMarker(&#8220;parent&#8221;);
Marker childMarker = MarkerFactory.getMarker(&#8220;child&#8221;);

parentMarker.add(childMarker);

Ce faisant, tout ce qui est journalisé avec parentMarker ou childMarker sera traité dans les appenders configurés pour parent tandis que les appenders configurés pour child ne verront que ce qui est journalisé avec child.

VI - Accès au MDC

Le « Mapped Diagnostic Context » (MDC) est, en résumé, une simple map (ensemble clés-valeurs) maintenue par le framework de journalisation dans laquelle l'application peut insérer des couples clés-valeurs qui pourront ensuite être utilisés pour ajouter des informations dans le système de log.

Imaginons que nous traitons des informations sur des personnes et que nous les affichons en logging. Le nom et le prénom de la personne devront être affichés sur chacune des lignes de logging. Nous pourrions automatiser l'inclusion du nom et du prénom dans les logs avec MDC.

Il est possible d'ajouter des valeurs dans le MDC avec la méthode put :

MDC.put("prenom", "Baptiste");
MDC.put("nom", "Wicht");

Ensuite, il faut les prendre en compte dans le layout de journalisation. Ce layout se configure au niveau de l'implémentation qui doit supporter MDC. Les implémentations qui supportent MDC sont Log4J, JUL et Logback. Voici un layout prenant en compte MDC :

%X{prenom} %X{nom} - %m%n

Désormais pour afficher des informations, il suffit de faire :

logger.info("Age {}", age);
logger.info("Localisation {}", localisation);

Ce qui pourrait donner :

Baptiste Wicht - Age 22
Baptiste Wicht - Localisation Suisse

Enfin, si le MDC est modifié entre deux instructions de logging, cela va directement changer la journalisation :

logger.info("Age {}", age);
MDC.put("prenom", "Jacques");
logger.info("Localisation {}", localisation);

affichera :

Baptiste Wicht - Age 22
Jacques Wicht - Localisation Suisse

Cela peut donc se révéler très pratique pour stocker et afficher des informations globales à l'application. Par exemple un login, un id de session ou n'importe quelle autre donnée.

VII - Conclusion

En conclusion, SLF4J se révèle être une très bonne couche d'abstraction de journalisation. Elle se révèle très puissante, tout en étant très simple à utiliser et restant dans le même style que les autres systèmes de journalisation existants.

L'auteur de SLF4J conseille d'utiliser l'implémentation de journalisation LogBack qui est l'implémentation de référence de SLF4J.

Si vous avez des commentaires sur cet article, n'hésitez pas à vous exprimer sur le forum : Commentez
Ou alors directement sur le site avec le formulaire de commentaire au bas de cette page.

VII-1 - Remerciements

Un grand merci à eusebe19 et à Wachter pour leur correction orthographique.