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èmes 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.

2. 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
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.

3. 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
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.

4. 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 :

 
Sélectionnez
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 :

 
Sélectionnez
logger.info("Hello World");

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

 
Sélectionnez
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é :

 
Sélectionnez
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 :

 
Sélectionnez
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é.

5. 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 marké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 :

 
Sélectionnez
<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 :

 
Sélectionnez
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.

 
Sélectionnez
Marker parentMarker = MarkerFactory.getMarker(“parent”);
Marker childMarker = MarkerFactory.getMarker(“child”);
 
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.

6. 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 :

 
Sélectionnez
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 :

 
Sélectionnez
%X{prenom} %X{nom} - %m%n

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

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

Ce qui pourrait donner :

 
Sélectionnez
Baptiste Wicht - Age 22
Baptiste Wicht - Localisation Suisse

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

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

affichera :

 
Sélectionnez
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.

7. 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 : 10 commentaires Donner une note à l'article (4.5)
Ou alors directement sur le site avec le formulaire de commentaire au bas de cette page.

7.1. Remerciements

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