Pourquoi appeler LoggerFactory.getLogger (…) à chaque fois n’est pas recommandé?

J’ai lu des tonnes de publications et de documents (sur ce site et ailleurs) indiquant que le modèle recommandé pour la journalisation SFL4J est le suivant:

public class MyClass { final static Logger logger = LoggerFactory.getLogger(MyClass.class); public void myMethod() { //do some stuff logger.debug("blah blah blah"); } } 

Mon patron préfère que nous utilisions simplement une enveloppe pour intercepter les appels de journal et éviter le code de la plaque de la chaudière pour déclarer l’enregistreur sur chaque classe:

 public class MyLoggerWrapper { public static void debug(Class clazz, Ssortingng msg){ LoggerFactory.getLogger(clazz).debug(msg)); } } 

et simplement l’utiliser comme ceci:

 public class MyClass { public void myMethod() { //do some stuff MyLoggerWrapper.debug(this.getClass(), "blah blah blah"); } } 

Je suppose que l’instanciation d’un enregistreur à chaque fois que nous nous connectons est un peu coûteuse, mais je n’ai pas pu trouver de document à l’appui de cette hypothèse. En outre, il affirme que le framework (LogBack ou Log4J que nous décidons toujours) va “mettre en cache” les enregistreurs et que, de toute façon, les serveurs fonctionnent bien en dessous de leur capacité, ce n’est donc pas un problème.

Une aide pour identifier des problèmes potentiels avec cette approche?

Voici un problème évident avec cette approche: les messages Ssortingng seront construits à chaque appel à debug() , il n’y a pas de moyen évident d’utiliser une clause guard avec votre wrapper.

Le langage standard avec log4j / commons-logging / slf4j consiste à utiliser une clause de garde telle que:

 if (log.isDebugEnabled()) log.debug("blah blah blah"); 

Dans le but d’être que si le niveau DEBUG n’est pas activé pour le journal, le compilateur peut éviter de concaténer des chaînes plus longues que vous pourriez lui envoyer:

 if (log.isDebugEnabled()) log.debug("the result of method foo is " + bar + ", and the length is " + blah.length()); 

Voir “Quel est le moyen le plus rapide de (non) enregistrer?” dans la FAQ SLF4J ou log4j .

Je recommanderais contre le “wrapper” que votre patron suggère. Une bibliothèque comme slf4j ou commons-logging est déjà une façade autour de l’implémentation de journalisation sous-jacente réelle utilisée. En outre, chaque invocation de l’enregistreur est beaucoup plus longue – comparez ce qui précède avec

  MyLoggerWrapper.debug(Foo.class, "some message"); 

C’est le type de “wrapping” et d’obfuscation sortingviaux et sans importance qui ne sert à rien d’autre que d’append des couches d’indirection et de mordre votre code. Je pense que votre patron peut trouver des questions plus importantes à obséder.

Les objects de journalisation sont certainement réutilisés, donc aucune instation supplémentaire ne se produira dans un sens ou dans l’autre. Le plus gros problème que je vois est que vos informations de fichier / numéro de ligne seront inutiles, car le journal enregistrera toujours fidèlement que chaque message a été émis par la classe LoggerWrapper , ligne 12 🙁

OTOH SLF4J est déjà une façade enveloppante pour masquer la structure de journalisation spécifique utilisée, vous permettant de changer librement entre les différentes implémentations de journalisation. Par conséquent, je ne vois absolument aucun intérêt à cacher cela derrière un autre emballage.

Les appels répétés à LoggerFactory.getLogger(clazz) ne doivent pas entraîner un nouvel object Logger à chaque fois. Mais cela ne signifie pas que les appels sont gratuits. Alors que le comportement réel dépend du système de journalisation derrière la façade, il est fort probable que chaque getLogger entraîne une recherche dans une structure de données simultanée ou synchronisée 1 pour rechercher une instance préexistante.

Si votre application effectue de nombreux appels à votre méthode MyLoggerWrapper.debug , cela peut avoir un impact significatif sur les performances. Et dans une application multithread, cela peut être un goulot d’étranglement de concurrence.

D’autres questions mentionnées par d’autres réponses sont également importantes:

  • Votre application ne peut plus utiliser logger.isDebugEnabled() pour réduire les frais généraux lorsque le débogage est désactivé.
  • La classe MyLoggerWrapper masque les noms de classe et les numéros de ligne des appels de débogage de votre application.
  • Le code utilisant MyLoggerWrapper sera probablement plus verbeux si vous faites plusieurs appels de consignateur. Et la verbosité sera dans le domaine où elle affecte le plus la lisibilité; c’est-à-dire dans les méthodes qui font des choses qui nécessitent une journalisation.

Finalement, ce n’est “pas comme ça”.


1 – Apparemment, il s’agit d’un Hashtable dans Logback et Log4j, ce qui signifie que le risque de goulot d’étranglement existe. Notez qu’il ne s’agit pas d’une critique de ces frameworks de journalisation. getLogger méthode getLogger n’a pas été conçue / optimisée pour être utilisée de cette manière.

Pour append aux raisons déjà mentionnées, la suggestion de votre patron est mauvaise car:

  • Il vous oblige à taper à plusieurs resockets quelque chose qui n’a rien à voir avec la journalisation, chaque fois que vous voulez vous connecter quelque chose: this.getClass()
  • Crée une interface non uniforme entre les contextes statiques et non statiques (car cela n’existe pas dans un contexte statique)
  • Les parameters inutiles supplémentaires créent une marge d’erreur, ce qui permet aux instructions de la même classe d’accéder à des enregistreurs différents (pensez à un collage de copie imprudent)
  • Bien qu’il enregistre 74 caractères de la déclaration de l’enregistreur, il ajoute 27 caractères supplémentaires à chaque appel de consignation. Cela signifie que si une classe utilise le journal plus de 2 fois, vous augmentez le code standard en termes de nombre de caractères.

Lorsque vous utilisez quelque chose comme: MyLoggerWrapper.debug(this.getClass(), "blah") Vous obtiendrez de mauvais noms de classe lors de l’utilisation de frameworks AOP ou d’outils d’injection de code. Les noms de classe ne sont pas comme l’origine, mais un nom de classe généré. Et un autre inconvénient de l’utilisation du wrapper: Pour chaque instruction de journal, vous devez inclure le code supplémentaire "MyClass.class" .

La mise en cache des enregistreurs dépend des frameworks utilisés. Mais même si c’est le cas, il doit toujours rechercher le journal souhaité pour chaque déclaration de journal que vous effectuez. Donc, avoir 3 énoncés dans une méthode, il faut la chercher 3 fois. En l’utilisant comme une variable static , il ne doit la chercher qu’une fois!

Et dit avant: vous perdez la possibilité d’utiliser if( log.isXXXEnabled() ){} pour un ensemble d’instructions.

Qu’est-ce que votre patron contre le “défaut de la communauté accepté et recommandé”? L’introduction du wrapper n’ajoute pas plus d’efficacité. Au lieu de cela, vous devez utiliser le nom de classe pour chaque instruction de journal. Après un moment, vous voulez “améliorer” cela, vous ajoutez donc une autre variable, ou un autre wrapper le rendant plus difficile pour vous-même.

Voici une possibilité de faciliter la connexion à Java 8: définissez une interface pour le faire pour vous. Par exemple:

 package logtesting; import java.util.Arrays; import org.slf4j.Logger; import org.slf4j.LoggerFactory; public interface Loggable { enum LogLevel { TRACE, DEBUG, INFO, WARN, ERROR } LogLevel TRACE = LogLevel.TRACE; LogLevel DEBUG = LogLevel.DEBUG; LogLevel INFO = LogLevel.INFO; LogLevel WARN = LogLevel.WARN; LogLevel ERROR = LogLevel.ERROR; default void log(Object...args){ log(DEBUG, args); } default void log(final LogLevel level, final Object...args){ Logger logger = LoggerFactory.getLogger(this.getClass()); switch(level){ case ERROR: if (logger.isErrorEnabled()){ logger.error(concat(args)); } break; case WARN: if (logger.isWarnEnabled()){ logger.warn(concat(args)); } break; case INFO: if (logger.isInfoEnabled()){ logger.info(concat(args)); } case TRACE: if (logger.isTraceEnabled()){ logger.trace(concat(args)); } break; default: if (logger.isDebugEnabled()){ logger.debug(concat(args)); } break; } } default Ssortingng concat(final Object...args){ return Arrays.stream(args).map(o->o.toSsortingng()).collect(Collectors.joining()); } } 

Ensuite, tout ce que vous avez à faire est de vous assurer que vos classes déclarent implémenter Logged , et à partir de l’une d’elles, vous pouvez faire des choses comme:

 log(INFO, "This is the first part ","of my ssortingng ","and this ","is the last"); 

La fonction log () prend en charge la concaténation de vos chaînes, mais uniquement après qu’elle a été activée. Il se connecte au débogage par défaut, et si vous voulez vous connecter pour déboguer, vous pouvez omettre l’argument LogLevel. Ceci est un exemple très simple. Vous pouvez faire un certain nombre de choses pour améliorer cela, comme l’implémentation des méthodes individuelles, à savoir error (), trace (), warn (), etc. Vous pouvez aussi simplement implémenter “logger” comme fonction renvoyant un logger:

 public interface Loggable { default Logger logger(){ return LoggerFactory.getLogger(this.getClass()); } } 

Et puis, il devient assez sortingvial d’utiliser votre enregistreur:

 logger().debug("This is my message"); 

Vous pouvez même le rendre entièrement fonctionnel en générant des méthodes de délégation pour toutes les méthodes de journalisation, de sorte que chaque classe d’implémentation soit une instance de Logger.

 package logtesting; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.slf4j.Marker; public interface Loggable extends Logger { default Logger logger(){ return LoggerFactory.getLogger(this.getClass()); } default Ssortingng getName() { return logger().getName(); } default boolean isTraceEnabled() { return logger().isTraceEnabled(); } default void trace(Ssortingng msg) { logger().trace(msg); } default void trace(Ssortingng format, Object arg) { logger().trace(format, arg); } default void trace(Ssortingng format, Object arg1, Object arg2) { logger().trace(format, arg1, arg2); } default void trace(Ssortingng format, Object... arguments) { logger().trace(format, arguments); } default void trace(Ssortingng msg, Throwable t) { logger().trace(msg, t); } default boolean isTraceEnabled(Marker marker) { return logger().isTraceEnabled(marker); } default void trace(Marker marker, Ssortingng msg) { logger().trace(marker, msg); } default void trace(Marker marker, Ssortingng format, Object arg) { logger().trace(marker, format, arg); } default void trace(Marker marker, Ssortingng format, Object arg1, Object arg2) { logger().trace(marker, format, arg1, arg2); } default void trace(Marker marker, Ssortingng format, Object... argArray) { logger().trace(marker, format, argArray); } default void trace(Marker marker, Ssortingng msg, Throwable t) { logger().trace(marker, msg, t); } default boolean isDebugEnabled() { return logger().isDebugEnabled(); } default void debug(Ssortingng msg) { logger().debug(msg); } default void debug(Ssortingng format, Object arg) { logger().debug(format, arg); } default void debug(Ssortingng format, Object arg1, Object arg2) { logger().debug(format, arg1, arg2); } default void debug(Ssortingng format, Object... arguments) { logger().debug(format, arguments); } default void debug(Ssortingng msg, Throwable t) { logger().debug(msg, t); } default boolean isDebugEnabled(Marker marker) { return logger().isDebugEnabled(marker); } default void debug(Marker marker, Ssortingng msg) { logger().debug(marker, msg); } default void debug(Marker marker, Ssortingng format, Object arg) { logger().debug(marker, format, arg); } default void debug(Marker marker, Ssortingng format, Object arg1, Object arg2) { logger().debug(marker, format, arg1, arg2); } default void debug(Marker marker, Ssortingng format, Object... arguments) { logger().debug(marker, format, arguments); } default void debug(Marker marker, Ssortingng msg, Throwable t) { logger().debug(marker, msg, t); } default boolean isInfoEnabled() { return logger().isInfoEnabled(); } default void info(Ssortingng msg) { logger().info(msg); } default void info(Ssortingng format, Object arg) { logger().info(format, arg); } default void info(Ssortingng format, Object arg1, Object arg2) { logger().info(format, arg1, arg2); } default void info(Ssortingng format, Object... arguments) { logger().info(format, arguments); } default void info(Ssortingng msg, Throwable t) { logger().info(msg, t); } default boolean isInfoEnabled(Marker marker) { return logger().isInfoEnabled(marker); } default void info(Marker marker, Ssortingng msg) { logger().info(marker, msg); } default void info(Marker marker, Ssortingng format, Object arg) { logger().info(marker, format, arg); } default void info(Marker marker, Ssortingng format, Object arg1, Object arg2) { logger().info(marker, format, arg1, arg2); } default void info(Marker marker, Ssortingng format, Object... arguments) { logger().info(marker, format, arguments); } default void info(Marker marker, Ssortingng msg, Throwable t) { logger().info(marker, msg, t); } default boolean isWarnEnabled() { return logger().isWarnEnabled(); } default void warn(Ssortingng msg) { logger().warn(msg); } default void warn(Ssortingng format, Object arg) { logger().warn(format, arg); } default void warn(Ssortingng format, Object... arguments) { logger().warn(format, arguments); } default void warn(Ssortingng format, Object arg1, Object arg2) { logger().warn(format, arg1, arg2); } default void warn(Ssortingng msg, Throwable t) { logger().warn(msg, t); } default boolean isWarnEnabled(Marker marker) { return logger().isWarnEnabled(marker); } default void warn(Marker marker, Ssortingng msg) { logger().warn(marker, msg); } default void warn(Marker marker, Ssortingng format, Object arg) { logger().warn(marker, format, arg); } default void warn(Marker marker, Ssortingng format, Object arg1, Object arg2) { logger().warn(marker, format, arg1, arg2); } default void warn(Marker marker, Ssortingng format, Object... arguments) { logger().warn(marker, format, arguments); } default void warn(Marker marker, Ssortingng msg, Throwable t) { logger().warn(marker, msg, t); } default boolean isErrorEnabled() { return logger().isErrorEnabled(); } default void error(Ssortingng msg) { logger().error(msg); } default void error(Ssortingng format, Object arg) { logger().error(format, arg); } default void error(Ssortingng format, Object arg1, Object arg2) { logger().error(format, arg1, arg2); } default void error(Ssortingng format, Object... arguments) { logger().error(format, arguments); } default void error(Ssortingng msg, Throwable t) { logger().error(msg, t); } default boolean isErrorEnabled(Marker marker) { return logger().isErrorEnabled(marker); } default void error(Marker marker, Ssortingng msg) { logger().error(marker, msg); } default void error(Marker marker, Ssortingng format, Object arg) { logger().error(marker, format, arg); } default void error(Marker marker, Ssortingng format, Object arg1, Object arg2) { logger().error(marker, format, arg1, arg2); } default void error(Marker marker, Ssortingng format, Object... arguments) { logger().error(marker, format, arguments); } default void error(Marker marker, Ssortingng msg, Throwable t) { logger().error(marker, msg, t); } } 

Bien sûr, comme mentionné précédemment, cela signifie que chaque fois que vous vous connectez, vous devez passer par le processus de recherche Logger dans votre LoggerFactory – à moins que vous ne remplaciez la méthode logger (), auquel cas vous pouvez aussi bien le faire. la manière “recommandée”.