Toujours dans la lignée de mon article sur Activer efficacement mes logs en production, je me suis rendu compte que je n’avais pas traité le cas particulier de JUL (java.util.logging).La documentation de SLF4J est assez succincte sur le sujet et j’ai donc mis du temps à faire passer JUL par SLF4J . Je vous résumerais ici la solution que j’ai pu trouver.
Le problème JUL
Rappelons le principe des bridges SLF4J:
- Une bibliothèque externe (springframework par exemple) utilise Commons-Logging comme API de log.
- Vous voulez ne gérer que SLF4J comme framework de Log.
- Vous retirez commons-logging comme dépendance de votre projet et vous le remplacez par la dépendance jcl-over-slf4j.
- cette dépendance apporte l’api commons-logging de sorte à ce que la dépendance tierce ne voit rien du tout.
JUL par contre est un autre sujet. Il est fourni dans la JDK et vous ne pouvez donc pas le retirer.Vous verrez donc les api JSF par exemple continuer à logger sur STDERR même après une tentative de surcharge.
La solution JUL-To-SLF4J
En fait, la solution est déjà prévu par JUL, nous allons donc utiliser une fonctionnalité prévue: j.u.l.Handler .
SLF4J fournit un Handler SLF4JBridgeHandler . Le Handler récupère les messages de log et les exportent à SLF4J. Seulement, si vous vous arrétez à la doc sur le site SLF4J sans aller dans la javadoc du Handler, vous ne saurez pas comment l’activer.
Programmatic installation:
// Optionally remove existing handlers attached to j.u.l root logger SLF4JBridgeHandler.removeHandlersForRootLogger(); // (since SLF4J 1.6.5) // add SLF4JBridgeHandler to j.u.l's root logger, should be done once during // the initialization phase of your application SLF4JBridgeHandler.install();Installation via logging.properties configuration file:
// register SLF4JBridgeHandler as handler for the j.u.l. root logger handlers = org.slf4j.bridge.SLF4JBridgeHandlerOnce SLF4JBridgeHandler is installed, logging by j.u.l. loggers will be directed to SLF4J. Example:
import java.util.logging.Logger; ... // usual pattern: get a Logger and then log a message Logger julLogger = Logger.getLogger("org.wombat"); julLogger.fine("hello world"); // this will get redirected to SLF4J
Dans mon cas, j’ai décidé de l’activer de manière programmatique, et sur ma webapp. Pour cela, je crée un Listener que je déclare dans mon web.xml.
Encore mieux !
Nous venons d’installer le Handler, et certains messages sont redirigés sur SLF4J, les autres n’apparaissent nul part. 🙁
Cela est dû à un premier filtre que fait JUL sur le niveau de log (configuré sur JUL). Mon besoin, c’est aussi que JUL se plie à mon niveau de log SLF4J. Je ne veux pas qu’il fasse du processing inutile pour logger en DEBUG alors même que SLF4J filtrera après sur le niveau WARN.
La javadoc du Bridge nous dit ceci:
Please note that translating a java.util.logging event into SLF4J incurs the cost of constructing
LogRecord
instance regardless of whether the SLF4J logger is disabled for the given level. Consequently, j.u.l. to SLF4J translation can seriously increase the cost of disabled logging statements (60 fold or 6000% increase) and measurably impact the performance of enabled log statements (20% overall increase). Please note that as of logback-version 0.9.25, it is possible to completely eliminate the 60 fold translation overhead for disabled log statements with the help of LevelChangePropagator.
Je dois donc installer le LevelChangePropagator. 2 solutions: Par configuration ou Programmatiquement.
Pourquoi changer une équipe qui gagne?! Je le fais dans mon Listener
LevelChangePropagator listener = new LevelChangePropagator(); LoggerContext loggerContext = ((ch.qos.logback.classic.Logger) LOGGER).getLoggerContext(); loggerContext.addListener(listener); listener.setContext(loggerContext); loggerContext.start();
A présent, via SLF4J, je conduis JUL de A à Z, de mon niveau de log, à mon output.
J’espère que mon expérience vous aura été utile et n’hésitez pas à me faire des retours ou me demander des compléments.
Le listener JULToSLF4JListener.java .