Tutoriel sur la migration des bibliothèques de log depuis commons-logging/Log4J vers Slf4J/Logback

Gauthier Perrineau

- Titre : Tutoriel sur la migration des bibliothèques de log depuis commons-logging/Log4J vers Slf4J/Logback

- Auteur : Gauthier Perrineau

- Parution : 3 janvier 2018

- Licence : Copyright ® 2018 Gauthier Perrineau. 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. Etat des lieux

- III. Pourquoi migrer?

- III-1. commons-logging vs Slf4J

- III-2. Logback (et Log4j2) vs Log4j

- III-3. A propos de java.util.logging (JUL)

- IV. Les étapes de la migration

- IV-1. Unification des solutions de journalisation: Mise en place des ponts vers SLF4J

- IV-1-a. Cas des logs via commons-logging + log4j

- IV-1-b. via slf4j

- IV-1-c. via un appel direct de log4J sans passer par une API de logging

- IV-1-d. via JUL

- IV-2. Migration du code source vers SLF4J (via un outil disponible sur leur site officiel)

- IV-2-a. Faire un backup

- IV-2-b. Utiliser l'outil de migration officiel

- IV-2-c. Finaliser la migration à la main.

- IV-3. Remplacement de log4j par logback comme bibliothèque de logging

- V. Création et paramétrage du fichier logback.xml

- V-1. Si votre projet était configuré avec des fichiers log4j.properties

- V-2. Si votre projet était configuré avec des fichiers log4j.xml

- V-3. Paramêtrage manuel des appenders les plus complexes

- V-3-a. un appender façon log4j

- V-3-b. l'appender logback correspondant

- V-4. Configuration du LevelChangePropagator pour limiter l'impact du SLF4JBridgeHandler sur les performances

- VI. Fonctionalités avancées

- VI-1. Modification de la configuration à chaud

- VI-1-a. auto reloading

- VI-1-b. via JMX

- VI-2. Configuration du LogbackConfigListener à la place du Log4jConfigListener

- VI-3. MDC

- VI-4. Astuces logback

- VI-4-a. Structures de controle

- VI-4-b. Mode debug

- VI-4-c. Indiquer quel fichier de configuration utiliser au moyen d'un paramètre système

- VI-4-d. Afficher les informations de package dans les stacktraces

- VII. Conclusion

I - Introduction

La problématique de la journalisation des traces logicielles en java est compliquée par les nombreuses librairies sur lesquelles s'appuie un projet et il n'est pas rare qu'au final il y ait trois ou quatre systèmes qui fonctionnent en parallèle, avec inévitablement des logs qui sont perdus car ils n'aboutissent pas dans le bon fichier, et également des fichiers tels que le catalina.out de tomcat (ou même la sortie standard) qui sont inutilement polués par des logs mal redirigés. Il est donc important d'aborder la problématique de la journalisation en recherchant une solution qui en couvre tous les aspects.

Si on s'intéresse plus spécifiquement au code source d'un projet en faisant abstraction des bibiliothèques sur lesquelles il s'appuie, on trouve également de projet en projet des variations significatives dans les choix et qui ne sont généralement justifiées que par des vieilles habitudes ou la facilité du moment.

L'objectif de ce tutoriel est de détricoter tout cela et d'expliquer comment s'en sortir simplement.

Pour se remettre dans le contexte du fonctionnement habituel des logs, voir ici

II - Etat des lieux

Lorsqu'il s'agit de journalisation, l'usage veut qu'on décorrèle d'une part l'API de Logging (déclarée et appelée dans le code java), et le framework de logging sur lequel elle s'appuie et qui fait réellement le travail. Par analogie on peut considérer l'API de Logging comme l'interface d'un Service chargé de la journalisation, tandis que le framework de logging correspondrait à l'implémentation de l'interface en question.

Des exemples courants de l'API de logging: commons-logging, SLF4J, java.util.logging.
Des exemples courants du framework de logging: log4j, logback, log4j2, l'implémentation par défaut de la jvm.

A noter qu'un mauvais usage qui perdure encore dans certains projets consiste à ne pas utiliser d'API de logging et à appeler directement le framework de logging. Voire à mixer les deux.

III - Pourquoi migrer?

Dans l'introduction de ce tutoriel, nous avons déjà évoqué les diverses problématiques liées à l'utilisation mixée de plusieurs technologies de logging et l'importance qu'il y a à unifier tout cela. Nous allons maintenant nous intéresser aux diverses technologies répandues sur le marché afin d'expliquer notre choix de mettre en lumière SLF4J/Logback.

III-1 - commons-logging vs Slf4J

Pendant longtemps commons-logging a été l'api référence pour le log dans un projet java. Les raisons pour lesquelles elle a été peu à peu dépréciée sont multiples. En voici quelques-unes:

- commons-logging est réputé pour être sujet à des fuites de mémoire dans certains cas de figure (NB: ce n'est pas l'avis de son auteur(en) qui explique qu'une fuite de mémoire n'est en gros possible que si les développeurs configurent leur projet n'importe comment)

- Ensuite la manière dont commons-logging scann le classloader via divers hacks pour trouver le framework de logging est très impopulaire car elle ouvre la porte à de nombreux problèmes dont des comportements inatendus, et des problèmes difficiles à débugger. Voir à ce sujet cette discussion(en), ainsi que cet article(en). Pour faire simple on peut considérer qu'il rercherche dans les propriétés du projet le nom du framework de logging et qu'il fait ensuite un "Class.forName(..)" pour l'instancier, avec tous les problèmes que cela implique.

- Par ailleurs l'utilisation de commons-logging s'avère assez vite verbeuse dés lors que l'on souhaite journaliser des messages un peu construits: il faut utiliser la fameuse méthode que tous les développeurs détestent:

if (logger.isDebugEnabled()) { // pour éviter que la JVM perde du temps à construire le message dans le cas où le DEBUG n'est pas actif
logger.debug("problème avec le user "+username+" (commande n°"+numCommande+", sessionid="+sessionid+")");
}

En comparaison, Slf4J est plus simple d'utilisation, n'est pas sujet à la fuite de mémoire dont souffre commons-logging, s'appuie sur un mécanisme très simple pour trouver le framework de logging (il charge(en) la classe org.slf4j.impl.StaticLoggerBinder, à charge pour le développeur de n'inclure qu'une seule librairie comportant cette signature) et offre des fonctionnalités avancéees.

III-2 - Logback (et Log4j2) vs Log4j

Un peu d'histoire: Logback et SLF4J ont tous deux été pensés par l'auteur de Log4J. Ce dernier a un jour décidé que plutôt que de continuer à maintenir Log4J avec tous ses défauts, il péférait repartir de zéro en perdant la compatibilité ascendante. Par la suite des développeurs de la communauté Apache ont repris le projet Log4J et sorti une version Log4J2 sensiblement équivalente à Logback en terme de fonctionnalités. Dans le cadre du présent tutoriel il a été décidé de se concentrer sur Logback qui est l'implémentation native d'SLF4J et qui est davantage populaire.

Les raisons de passer de Log4J à Logback sont multiples, citons par exemple des améliorations en terme de performance, un rechargement à chaud directement supporté par la librairie, un monitoring et ajustement de niveau de journalisation possible via JMX, et des possibilités beaucoup plus étendues de configuration des logs. Voir aussi cet article(en).

III-3 - A propos de java.util.logging (JUL)

Proposé par défaut par le JDK depuis Java 1.4 il s'agit d'une implémentation de log "par défaut" prévue avant tout pour les développeurs qui:

- font juste quelques tests et ne souhaitent pas s'encombrer d'une ou deux bibiliothèques externes juste pour la journalisation,

- ou comptent livrer un programme dans lequel les logs ne sont pas importants, typiquement un client java exécuté sur l'ordinateur d'un tiers.

Certaines bibiliothèques ont fait le choix de s'appuyer dessus et il est donc important de le prendre en compte. De fait, c'est une autre bonne raison de migrer vers SLF4J comme nous le verrons par la suite.
Quelques liens sur le sujet:

- Un article(en) qui analyse les performances de JUL

- Une discussion(en) sur les limitations de JUL

- Un tutoriel sur l'utilisation de JUL en java

IV - Les étapes de la migration

La page qui a servit de référence pour cette partie est SL4JF Legacy (en).

IV-1 - Unification des solutions de journalisation: Mise en place des ponts vers SLF4J

Dans le cadre d'un projet qui est parti un peu dans tous les sens, il y a 4 sources possibles pour des logs, que nous allons détailler ci-après. L'idée est que dans un premier temps on va s'assurer de les gérer de manière unifiée via l'API SLF4J en utilisant pour cela des adaptateurs, mais pour le moment sans toucher au code source et en conservant LogJ4J comme bibiliothèque de logging.

IV-1-a - Cas des logs via commons-logging + log4j

C'est le schéma classique, probablement celui qui est utilisé dans le code et par le plus grand nombre de librairies qui datent un peu. L'idée ici est d'exclure la librairie commons-logging partout où elle est référencée dans le projet, afin de la remplacer par jcl-over-slf4j qui basiquement fournit les mêmes classes et packages, mais redirige les logs vers SLF4J. Pour ce faire on pourra suivre par exemple la démarche suivante:

- s'il s'agit d'un projet maven multi-module, éditer en priorité le pom parent. Sinon éditer le fichier pom.xml du projet et aller à la section correspondant au dependencyManagement, qui permet, rappelons le, de déclarer un comportement commun pour toutes les librairies qui y sont référencées, indépendamment d'où elles sont par la suite utilisées dans le projet.

- recenser ensuite toutes les librairies du projet qui dépendent de commons-logging directement ou par transitivité, et s'assurer qu'elles soient bien déclarées dans la section dependencyManagement (attention cela ne remplace pas une déclaration dans la section "dependencies" du pom, mais ça permet de n'y déclarer pour chaque artefact que son id et son group-id, le reste étant configuré une fois pour toutes dans le dependencyManagement)

- Pour toutes les librairies recensées à l'étape précédente, exclure du dependencyManagement les dépendances vers commons-logging (et commons-logging-api s'il y a lieu)

IV-1-b - via slf4j

Ca peut déjà être le cas pour certaines dépendances. En théorie il est possible que la librairie slf4j-log4j12 (ou logback) soit présente dans le projet, permettant de rediriger ces logs vers log4j. Sinon ils sont perdus. Pour ce cas de figure il n'y a pas grand chose à faire, simplement s'assurer que la librairie slf4j-log4j12 soit bien présente dans les dépendances par défaut du projet.

IV-1-c - via un appel direct de log4J sans passer par une API de logging

Comme le but à ce stade est d'utiliser SLF4J comme API de logging, mais de continuer de s'appuyer sur log4J pour le framework de logging, on va laisser les choses comme ça pour le moment. Lorsqu'on basculera sur lobgback, on remplacera log4j par l'adaptateur log4j-over-slf4j, mais comme ce dernier surcharge les classes de log4j, si on le fait dés maintenant il va y avoir un conflit entre les deux librairies.

IV-1-d - via JUL

Ce cas est le plus épineux. En effet jul-to-slf4j ne peut pas faire comme les autres adaptateurs (jcl-over-slf4j et log4j-over-slf4j), qui réimplémentent les classes des librairies qu'ils remplacent (respectivement commons-logging et log4j), car les packages correspondant du JDK sont protégés et ne peuvent être remplacés. Du coup on procède différemment, via un traducteur d'évènements le SLF4JBridgeHandler qui redirige tous les appels devant passer par JUL vers l'API SLF4J. Mais attention cela induit un très gros problème de performance puisque tous les évènements de logs vont être traduits et transmis à SLF4J indépendamment de si la configuration de l'API permet de les afficher ou pas. Ce problème ne sera résolu que lors du passage à Logback.

Pour mettre en place le SLF4JBridgeHandler voici la marche à suivre:

1. s'assurer que la librairie jul-to-slf4j soit bien dans les dépendances par défaut du projet

2. faire en sorte que les méthodes statiques removeHandlersForRootLogger() puis install() de la classe SLF4JBridgeHandler soient exécutées au démarrage de l'application.

Cette dernière étape peut se faire soit programmatiquement, dans le main d'une application standalone ou dans un bean s'exécutant au démarrage d'une application plus complexe, soit via de la configuration et par exemple dans le cadre d'un projet spring, en ajoutant ce qui suit dans la configuration xml:

<!-- Déclaration Bridge JUL -->
<bean id="slf4JBridgeHandler" class="org.slf4j.bridge.SLF4JBridgeHandler" init-method="removeHandlersForRootLogger" />
<bean class="org.slf4j.bridge.SLF4JBridgeHandler" init-method="install" depends-on="slf4JBridgeHandler" />

IV-2 - Migration du code source vers SLF4J (via un outil disponible sur leur site officiel)

Dans le cadre des dépendances de notre projet on n'a pas la maitrise de l'outil de journalisation choisi par les développeurs et on est donc obligés de passer par des adaptateurs. Mais en ce qui concerne le code source du projet, il n'y a pas ce problème. On va donc faire en sorte de supprimer toute référence directe à JUL, commons-logging, Log4J dans notre code source, pour n'utiliser plus que SLF4J. Le processus de migration est assez simple, de l'ordre du search and replace. Mais nous allons détailler un petit peu le processus

IV-2-a - Faire un backup

Le plus simple est de faire un commit de votre code. A défaut, un zip de votre projet fera tout aussi bien l'affaire.

IV-2-b - Utiliser l'outil de migration officiel

Cet outil, qui se nomme slf4j-migrator va:

- supprimer tous les imports portant sur JUL, log4j ou commons-logging et les remplacer par l'import des classes correspondantes de slf4j

- remplacer de la même manière les déclarations de loggers dans le code.

- modifier toutes les occurences où l'on appelle le logger pour utiliser la méthode équivalente de slf4j

Mais il ne faut pas s'attendre à des miracles, il y aura forcément des cas ambigus et des erreurs qui vous seront normalement remontés par votre IDE. Ces problèmes font l'objet du prochain paragraphe.

IV-2-c - Finaliser la migration à la main.

Plusieurs types de problèmes peuvent être rencontrés par l'outil de migration, qui n'est pas extrêmement malin (utilisez votre IDE pour tracer tous les endroits où sont apparues des erreurs java):

- Il peut rencontrer des ambiguités au moment de choisir les niveaux de log à appeler. Par exemple, selon les projets, les niveaux de logs FINE, FINER et FINEST de JUL se répartiront différemment entre les niveaux slf4j TRACE et DEBUG

- Il peut rater complètement une déclaration de log qui aura été justifiée sur deux lignes, et qu'il faudra alors reprendre à la main

- Il y a également le cas où l'appel sur le logger contenait autre chose qu'une String, un cas de figure qu'il ne sait pas gérer et qu'il faut là aussi reprendre manuellement.

Dans la dernière hypothèse, si à la place d'une chaine de caractère, un objet est passé en argument au logger (typiquement une exception), le plus simple pour résoudre le problème est d'appeler explicitement la méthode toString() (ce qui était de toute manière le cas de manière implicite avec commons-logging)

Par ailleurs il y a aussi tous les messages de journalisation un petit peu construits que nous avons évoqués au III 1). Grâce au passage à slf4j, on peut désormais remplacer tous les appels de type

if (logger.isDebugEnabled()) { // pour éviter que la JVM perde du temps à construire le message dans le cas où le DEBUG n'est pas actif
logger.debug("problème avec le user "+username+" (commande n°"+numCommande+", sessionid="+sessionid+")");

par un code équivalent tel que le suivant, qui est beaucoup moins lourd syntaxiquement et ne pose pas davantage de problème de performance que le précédent:

logger.debug("problème avec le user {} (commande n°{}, sessionid={})",username,numCommande,sessionid);

En effet, lors de l'appel de la méthode, l'API va commencer par vérifier si le niveau correspondant de journalisation est actif pour cette classe avant de construire le message de log d'après les arguments qui lui sont passés.

Mais là aussi, l'outil de migration n'est pas assez malin pour le faire automatiquement, ça fait donc partie des choses à reprendre à la main.

IV-3 - Remplacement de log4j par logback comme bibliothèque de logging

Au IV 1), nous avons configuré le projet pour rediriger tous les logs vers l'API SLF4J, mais en continuant d'utiliser log4j comme framework de logging. Avec comme on l'a vu à l'étape (d), un coût en terme de performances dans le cadre de la redirection des appels sur l'API JUL. Nous allons maintenant modifier cette configuration pour remplacer log4j par logback.

L'ensemble de l'opération est assez simple, il faut commencer par exclure des dépendances toutes les références aux bibliothèques log4j et slf4j-log4j12 suivant le mode opératoire vu précédemment (cf (a)). Il faut de plus supprimer s'il y a lieu les paramétrages liés à log4jConfigLocation et Log4jConfigListener (il existe un équivalent logback qui sera abordé au VI-2).

Il faut enfin déclarer les dépendances vers logback-classic et log4j-over-slf4j comme dépendances par défaut dans le pom parent de votre projet. Ceci permet d'aboutir au schéma suivant (Fig3). Mais il reste encore à écrire le fichier de paramétrage de logback. Ce point est abordé à la partie suivante.

V - Création et paramétrage du fichier logback.xml

Votre projet est désormais configuré pour déléguer au couple slf4j/logback la gestion des logs. Mais il reste encore à écrire le fichier de configuration (et également pour tous vos tests unitaires!) car par défaut (en) logback considère que tous les niveaux de log sont actifs et envoie tout vers la console, ce qui est pratique pour tester que la configuration réalisée à l'étape précédente est fonctionnelle, mais qui va rapidement poser problème du fait de l'extrême verbosité que vous risquez d'observer.

Malheureusement, logback n'est pas compatible avec les fichiers de configuration de log4j. Heureusement les équivalences sont assez intuitives et les possibilités de configuration sont même plus poussées avec logback.

Dans le cadre d'un projet java web, il ne va pas être nécessaire de placer le fichier logback.xml dans META-INF. Si vous utilisez l'arborescence maven, placez le dans le dossier /resources idoine.

V-1 - Si votre projet était configuré avec des fichiers log4j.properties

Le projet logback met à disposition un traducteur automatique en ligne pour générer le fichier logback.xml correspondant.

V-2 - Si votre projet était configuré avec des fichiers log4j.xml

La première chose à faire va consister à le mettre à jour en accord avec les conventions modernes. Par exemple dans les vieux projets on trouve encore des tags XML tels que category et priority, qui ont été depuis longtemps remplacés respectivement par logger et level. Ensuite il faut enlever tous les appenders un peu complexes tels que par exemple le RollingFileAppender: ceux-là auront besoin d'être convertis à la main.

La suite est inspirée d'un billet de blog qui décrit comment utiliser un fichier XSLT pour convertir votre fichier log4j.xml en fichier logback.xml. Voici les librairies qui ont été utilisées dans le cadre de ce tutoriel:

- serializer-2.7.1

- xalan-2.7.0

- xercesImpl-2.6.2

- xml-apis-1.3.02

Et voici la ligne de commande qui a été utilisée (exécutée pour plus de simplicité dans le dossier où se trouveaient également les librairies, le fichier xsl et le fichier d'entrée):

java -classpath  xalan-2.7.0.jar org.apache.xalan.xslt.Process -IN log4j.xml -XSL log4j-to-logback.xsl -OUT logback.xml

Il est ensuite conseillé de réorganiser le fichier obtenu de manière à ce que loggers et appenders se suivent dans le même ordre que dans le fichier d'entrée afin de faciliter les comparaisons. Normalement le fichier obtenu est fonctionnel mais il peut y avoir eu quelques loupés dans la conversion donc c'est mieux de comparer avec le fichier d'entrée.

V-3 - Paramêtrage manuel des appenders les plus complexes

Le manuel de logback est assez facile à comprendre. Même sans comprendre l'anglais, les exemples qui sont donnés parlent d'eux-mêmes. Il existe ainsi des équivalents pour la plupart des apenders log4j. Voici un exemple de ce qu'il est possible de faire:

V-3-a - un appender façon log4j

<!-- Log4J Configuration -->
<appender name="myAppender" class="org.apache.log4j.RollingFileAppender">
<param name="File" value="${catalina.base}/logs/myApp.log" />
<param name="Append" value="true" />
<param name="MaxFileSize" value="20MB" />
<param name="MaxBackupIndex" value="10" />
<layout class="org.apache.log4j.PatternLayout">
<param name="ConversionPattern" value="%d %X{param} %-5p %m%n" />
</layout>
<filter class="org.apache.log4j.varia.StringMatchFilter">
<param name="StringToMatch" value="PATTERN1" />
<param name="AcceptOnMatch" value="true" />
</filter>
<filter class="org.apache.log4j.varia.StringMatchFilter">
<param name="StringToMatch" value="PATTERN2" />
<param name="AcceptOnMatch" value="true" />
</filter>
<filter class="org.apache.log4j.varia.DenyAllFilter"/>
</appender>

On part d'un appender log4j de type RollingFileAppender: il sert à écrire dans un fichier myApp.log, lequel doit être renommé en myApp.log.1 lorsqu'il atteint les 20MB, l'appender écrivant dés lors dans un nouveau fichier myApp.log, et ainsi de suite jsuqu'à à voir 10 fichiers myApp.log.x. Au delà de cette limite le fichier le plus ancien est effacé lorsqu'un nouveau fichier est créé. Cette configuration est particulièrement prisée en production, elle permet de garantir que l'espac disque dédié aux logs n'excèdera pas 220MB tout en conservant un certain historique.
De plus, on a configuré cet appender pour n'accepter que certains types de messages: ceux qui contiennent les mots clés PATTERN1 et PATTERN2 et pour rejeter tous les autres.

V-3-b - l'appender logback correspondant

<appender name="myAppender" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>${catalina.base}/logs/myApp.log</file>
<rollingPolicy class="ch.qos.logback.core.rolling.FixedWindowRollingPolicy">
<fileNamePattern>${catalina.base}/logs/myApp.log.%i</fileNamePattern>
<minIndex>1</minIndex>
<maxIndex>10</maxIndex>
</rollingPolicy>
<triggeringPolicy class="ch.qos.logback.core.rolling.SizeBasedTriggeringPolicy">
<maxFileSize>20MB</maxFileSize>
</triggeringPolicy>
<filter class="ch.qos.logback.core.filter.EvaluatorFilter">
<evaluator> <!-- defaults to type ch.qos.logback.classic.boolex.JaninoEventEvaluator -->
<expression>return message.contains("PATTERN1");</expression>
</evaluator>
<OnMismatch>NEUTRAL</OnMismatch>
<OnMatch>ACCEPT</OnMatch>
</filter>
<filter class="ch.qos.logback.core.filter.EvaluatorFilter">
<evaluator> <!-- defaults to type ch.qos.logback.classic.boolex.JaninoEventEvaluator -->
<expression>return message.contains("PATTERN2");</expression>
</evaluator>
<OnMismatch>DENY</OnMismatch>
<OnMatch>ACCEPT</OnMatch>
</filter>
<encoder>
<pattern>%d %X{param} %-5p %m%n</pattern>
</encoder>
</appender>

Cet appender est strictement équivalent au précédent en fonctionnalité. Il est légèrement plus verbeux, mais également plus souple au niveau des possibilités de configuration. Par exemple il est tout à fait possible de définir un dossier différent pour le fichier de log courant myApp.log et pour les déclinaisons archivées myApp.log.x, et il est également possible de définir un autre pattern de nommage, par exemple myApp.log-x.

Pour pouvoir utiliser des filtres avancés tels que les EvaluatorFilter il est nécessaire d'ajouter dans les dépendances du projet les artefacts janino et commons-compiler (de même version que slf4j)

V-4 - Configuration du LevelChangePropagator pour limiter l'impact du SLF4JBridgeHandler sur les performances

On a introduit le SLF4JBridgeHandler au IV-1-d: il permet de rediriger les appels à l'API JUL vers SLF4J mais au prix d'un coût mesurable en performance. Une fois passé à logback il est possible de pallier ce défaut. Il suffit pour cela d'ajouter dans le fichier logback la balise suivante:

<contextListener class="ch.qos.logback.classic.jul.LevelChangePropagator"/>

VI - Fonctionalités avancées

Nous allons aborder pour terminer certaines fonctionnalités avancées utiles notamment en production.

VI-1 - Modification de la configuration à chaud

Il est parfois intéressant, afin de diagnostiquer un problème précis, de pouvoir modifier à chaud le niveau de logging d'une classe spécifique, sans avoir besoin de redémarrer l'application. Vis à vis de cette problématique logback offre deux possibilités.

VI-1-a - auto reloading

La balise configuration en début du fichier logback.xml supporte un certain nombre de paramètres parmi lesquels scan et scanPeriod, qui permettent de définir si le fichier doit se recharger automatiquement (par défaut, non), et à quel intervalle. Exemple:

<configuration scanPeriod="10 seconds" scan="false">

Notez que si l'unité de scanPeriod n'est pas spécifiée, celle par défaut est la milliseconde. Notez également le coût en terme de performances n'est pas neutre dans certains cas. Des optimisations ont été apportées pour le limiter au maximum, avec pour conséquence qu'il peut y avoir une petite latence supplémentaire au délai spécifié dans le paramètre scanPeriod entre le moment où le fichier modifié est sauvegardé et le moment où le changement est observé dans les logs.

VI-1-b - via JMX

L'autre solution pour modifier un niveau de log à chaud est de le faire via JMX. Pour ce faire il suffit que votre application soit configurée pour le JMX, et d'ajouter dans le fichier logback.xml la ligne suivante:

<jmxConfigurator />

Cela va permettre de modifier les niveaux de log à l'aide de la jconsole

VI-2 - Configuration du LogbackConfigListener à la place du Log4jConfigListener

Si vous travaillez sur une application web fonctionnant avec Spring et que vous avez coutume d'utiliser le Log4jConfigListener, il y a un équivalent pour logback, qui s'appuie sur l'artefact logback-ext-spring, disponible à partir de spring 3.1.1 (en prenant la plus ancienne version).

Les paramètres les plus couramment utilisés à ajouter dans le fichier web.xml sont les suivants:

<listener>
<listener-class>ch.qos.logback.ext.spring.web.LogbackConfigListener</listener-class>
</listener>

<context-param>
<param-name>logbackConfigLocation</param-name>
<param-value>/WEB-INF/logback-${os.name}.xml</param-value>
</context-param>

VI-3 - MDC

Lors du passage à l'API SLF4J et indépendamment de si Logback est utilisé par la suite, il est possible que vous rencontriez un problème dans le cas où votre application utilisait l'outil NDC de log4j. Heureusement, SLF4J propose un outil équivalent: le MDC, qui est plus polyvalent: Contrairement au NDC qui est une sorte de Buffer dans lesquel on pousse des préfixes qui s'afficheront dans leur ordre d'entrée, le MDC est comme une Map et on donne à chaque chaine de caractère un identifiant qui va permettre de l'afficher ou non dans les logs.

Ainsi si par exemple le code java contenait l'instruction "NDC.push(message);" avec un pattern de préfix de log configuré en "%d %x %-5p [%c] %m%n", on écrira désormais par exemple ceci dans le code java: "MDC.put("sessionID", message);" avec un pattern configuré en "%d %X{sessionID} %-5p [%c] %m%n" ce qui ouvre des possibilités nouvelles pour avoir de beaux logs.

VI-4 - Astuces logback

Le but de cette sous-partie n'est pas de couvrir toutes les possibilités de Logback, mais plutôt d'en mettre en lumière quelques unes.

VI-4-a - Structures de controle

Il est possible de mettre des structures de controles dans le fichier logback.xml. Par exemple:

<!-- Pour qu'il n'y ait pas de problème lorsque logback n'est pas utilisé depuis un tomcat -->
<if condition='isDefined("catalina.home")'>
<then>
<property name="log.folder" value="${catalina.home}/logs"/>
</then>
<else>
<property name="log.folder" value="./target/logs"/>
</else>
</if>

En plaçant ce morceau de code en début du fichier logback.xml il devient par la suite possible d'utiliser le paramètre ${log.folder} dans tous les appenders déclarés ensuite ce qui est assez pratique.

VI-4-b - Mode debug

Il est possible d'activer le mode debug de logback en ajoutant `debug="true"` dans la balise configuration. Les informations correspondant au fichier qui est lu et à la manière dont il est interprété seront alors affichées dans la sortie standard à chaque rechargement, ce qui est pratique au moins au début pour comprendre ce qui se passe.

VI-4-c - Indiquer quel fichier de configuration utiliser au moyen d'un paramètre système

Un exemple vaut mieux qu'un long discours:

java -Dlogback.configurationFile=/path/to/config.xml chapters.configuration.MyApp1

VI-4-d - Afficher les informations de package dans les stacktraces

Les premières versions de logback, jusqu'à la 1.1.4 le faisaient par défaut. Cela permet d'obtenir ce genre de stack:

14:28:48.835 [btpool0-7] INFO  c.q.l.demo.prime.PrimeAction - 99 is not a valid value
java.lang.Exception: 99 is invalid
at ch.qos.logback.demo.prime.PrimeAction.execute(PrimeAction.java:28) [classes/:na]
at org.apache.struts.action.RequestProcessor.processActionPerform(RequestProcessor.java:431) [struts-1.2.9.jar:1.2.9]
at org.apache.struts.action.RequestProcessor.process(RequestProcessor.java:236) [struts-1.2.9.jar:1.2.9]
at org.apache.struts.action.ActionServlet.doPost(ActionServlet.java:432) [struts-1.2.9.jar:1.2.9]
at javax.servlet.http.HttpServlet.service(HttpServlet.java:820) [servlet-api-2.5-6.1.12.jar:6.1.12]
at org.mortbay.jetty.servlet.ServletHolder.handle(ServletHolder.java:502) [jetty-6.1.12.jar:6.1.12]
at ch.qos.logback.demo.UserServletFilter.doFilter(UserServletFilter.java:44) [classes/:na]
at org.mortbay.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1115) [jetty-6.1.12.jar:6.1.12]
at org.mortbay.jetty.servlet.ServletHandler.handle(ServletHandler.java:361) [jetty-6.1.12.jar:6.1.12]
at org.mortbay.jetty.webapp.WebAppContext.handle(WebAppContext.java:417) [jetty-6.1.12.jar:6.1.12]
at org.mortbay.jetty.handler.ContextHandlerCollection.handle(ContextHandlerCollection.java:230) [jetty-6.1.12.jar:6.1.12]

Il faut désormais ajouter le paramètre `packagingData="true"` dans la balise configuration du fichier logback.xml pour l'activer.

VII - Conclusion

Ce tutoriel a permi de voir comment passer en pas à pas d'une application s'appuyant sur des solutions de log disparates à une application configurée de manière unifiée et s'appuyant sur des technologies performantes et éprouvées. Et voici un exemple fonctionnel de ce que l'on peut avoir comme fichier de configuration au final.