Log4J: stratégies pour créer des instances de journal

J’ai décidé d’utiliser la structure de journalisation Log4J pour un nouveau projet Java. Je me demande quelle stratégie dois-je utiliser pour créer / gérer des instances de Logger et pourquoi?

  • une instance de Logger par classe

    class Foo { private static final Logger log = Logger.getLogger(Foo.class); } 
  • une instance de Logger par thread
  • une instance de Logger par application
  • découpage horizontal: une instance de Logger dans chaque couche d’une application (par exemple, la couche de vue, la couche de contrôleur et la couche de persistance)
  • découpage vertical: une instance de Logger dans les partitions fonctionnelles de l’application

Note: Ce problème est déjà considéré dans une certaine mesure dans ces articles:

Quel est le coût de la création d’un Log4j Logger?

En règle générale, les enregistreurs sont configurés par classe car c’est un composant logique intéressant. Les threads font déjà partie des messages de journalisation (si votre filtre les affiche).

En ce qui concerne les enregistreurs basés sur des applications ou des calques, le problème est que vous devez trouver un endroit pour coller cet object Logger. Pas vraiment grave. Le plus gros problème est que certaines classes peuvent être utilisées à plusieurs niveaux à partir de plusieurs applications … il peut être difficile d’obtenir votre enregistreur correctement. Ou du moins difficile.

… et la dernière chose que vous voulez, ce sont de mauvaises hypothèses dans votre configuration de journalisation.

Si vous vous souciez des applications et des couches et que vous avez des points de séparation faciles, le NDC est la voie à suivre. Le code peut parfois être un peu excessif mais je ne sais pas combien de fois j’ai été sauvegardé par une stack de contexte précise montrant que Foo.bar () a été appelé depuis l’application X dans la couche Y.

La stratégie la plus utilisée consiste à créer un enregistreur par classe. Si vous créez de nouveaux threads, donnez-leur un nom utile, de sorte que leur journalisation se distingue facilement.

La création d’enregistreurs par classe présente l’avantage de pouvoir activer / désactiver la journalisation dans la structure de package de vos classes:

 log4j.logger.org.apache = INFO log4j.logger.com.example = DEBUG log4j.logger.com.example.verbose = ERROR 

Ce qui précède définirait le code de la bibliothèque Apache au niveau INFO , basculer la journalisation de votre propre code au niveau DEBUG à l’exception du package détaillé.

Je suis certain que ce n’est pas une bonne pratique, mais j’ai mis du temps de démarrage sur les applications avant d’enregistrer des lignes de code. Plus précisément, lors du collage:

 Logger logger = Logger.getLogger(MyClass.class); 

… les développeurs oublient souvent de remplacer “MyClass” par le nom de la classe actuelle, et plusieurs enregistreurs finissent toujours par pointer au mauvais endroit. C’est mauvais.

J’ai parfois écrit:

 static Logger logger = LogUtil.getInstance(); 

Et:

 class LogUtil { public Logger getInstance() { Ssortingng callingClassName = Thread.currentThread().getStackTrace()[2].getClass().getCanonicalName(); return Logger.getLogger(callingClassName); } } 

Le “2” dans ce code peut être faux, mais l’essentiel est là; Apportez un hit de performance à (sur une charge de classe, en tant que variable statique) recherchez le nom de la classe, de sorte qu’un développeur n’ait pas vraiment le moyen de le saisir ou d’introduire une erreur.

Je ne suis généralement pas ravi de perdre des performances pour éviter les erreurs de développement lors de l’exécution, mais si cela se produit en singleton, une fois? Cela ressemble souvent à un bon métier pour moi.

Comme cela a été dit par d’autres, je créerais un enregistreur par classe:

 private final static Logger LOGGER = Logger.getLogger(Foo.class); 

ou

 private final Logger logger = Logger.getLogger(this.getClass()); 

Cependant, j’ai trouvé utile d’avoir d’autres informations dans le journal. Par exemple, si vous avez un site Web, vous pouvez inclure l’ID utilisateur dans chaque message de journal. De cette façon, vous pouvez tracer tout ce que fait un utilisateur (très utile pour résoudre des problèmes de débogage, etc.).

La méthode la plus simple consiste à utiliser un MDC, mais vous pouvez utiliser un enregistreur créé pour chaque instance de la classe avec le nom, y compris l’ID utilisateur.

Un autre avantage de l’utilisation d’un MDC est que si vous utilisez SL4J, vous pouvez modifier les parameters en fonction des valeurs de votre MDC. Donc, si vous souhaitez enregistrer toutes les activités pour un utilisateur particulier au niveau DEBUG et laisser tous les autres utilisateurs à ERROR, vous pouvez le faire. Vous pouvez également redirect différentes sorties vers différents endroits en fonction de votre MDC.

Quelques liens utiles:

http://logging.apache.org/log4j/1.2/apidocs/org/apache/log4j/MDC.html

http://www.slf4j.org/api/index.html?org/slf4j/MDC.html

  • Créez un enregistreur par classe.
  • Si vous avez des dépendances qui requièrent Commons (très probablement), utilisez le pont de slf4j pour Commons Logging. Instanciez vos enregistreurs (par classe) à l’aide de l’interface de journalisation Commons: private static final Log log = LogFactory.getLog(MyClass.class);
  • Manifestez ce modèle dans votre IDE en utilisant des raccourcis. J’utilise les modèles en direct d’IDEA à cette fin.
  • Fournir des informations contextuelles aux threads en utilisant un NDC (thread local stack of ssortingngs) ou un MDC (thread local map of Ssortingng →?).

Exemples de modèles:

 private static final Log log = LogFactory.getLog($class$.class); // live template 'log' if (log.isDebugEnabled()) log.debug(Ssortingng.format("$ssortingng$", $vars$)); // live template 'ld', 'lw', 'le' ... 

Une autre option: vous pouvez essayer la coupe transversale AspectJ sur la journalisation. Vérifiez ici: Simplifiez votre journalisation . (Si vous ne voulez pas utiliser AOP , vous pouvez regarder slf4j )

 //Without AOP Class A{ methodx(){ logger.info("INFO"); } } Class B{ methody(){ logger.info("INFO"); } } //With AOP Class A{ methodx(){ ...... } } Class B{ methody(){ ...... } } Class LoggingInterceptor{ //Catched defined method before process public void before(...xyz){ logger.info("INFO" + ...xyz); } //Catched defined method after processed public void after(...xyz){ logger.info("INFO" + ...xyz); } ..... } 

PS: AOP sera mieux, il est sec (ne pas se répéter) .

La méthode la meilleure et la plus simple pour créer des enregistreurs personnalisés, sans lien avec un nom de classe, est la suivante:

 // create logger Logger customLogger = Logger.getLogger("myCustomLogName"); // create log file, where messages will be sent, // you can also use console appender FileAppender fileAppender = new FileAppender(new PatternLayout(), "/home/user/some.log"); // sometimes you can call this if you reuse this logger // to avoid useless traces customLogger.removeAllAppenders(); // tell to logger where to write customLogger.addAppender(fileAppender); // send message (of type :: info, you can use also error, warn, etc) customLogger.info("Hello! message from custom logger"); 

maintenant, si vous avez besoin d’un autre enregistreur dans la même classe, pas de problème 🙂 créez-en un nouveau

 // create logger Logger otherCustomLogger = Logger.getLogger("myOtherCustomLogName"); 

maintenant voir le code ci-dessus et créer un nouveau filtre de fichiers afin que votre sortie soit envoyée dans un autre fichier

Ceci est utile pour (au moins) 2 situations

  • quand vous voulez une erreur séparée de l’information et avertit

  • lorsque vous gérez plusieurs processus et que vous avez besoin de sortie de chaque processus

ps. avoir des questions ? N’hésitez pas à demander! 🙂

La convention commune est “une classe de journal et utilise le nom de la classe comme nom”. C’est un bon conseil.

Mon expérience personnelle est que cette variable logger ne doit PAS être déclarée statique mais une variable d’instance récupérée pour chaque nouvelle. Cela permet à la structure de journalisation de traiter deux appels différemment selon leur provenance. Une variable statique est la même pour toutes les instances de cette classe (dans cette classe loader).

De plus, vous devriez apprendre toutes les possibilités avec votre backend de choix. Vous avez peut-être des possibilités auxquelles vous ne vous attendiez pas.

Lors du déploiement de plusieurs fichiers EAR / WAR, il peut être préférable de regrouper log4j.jar dans la hiérarchie des chargeurs de classes.
c’est-à-dire pas dans WAR ou EAR, mais dans le chargeur de classe système de votre conteneur, sinon plusieurs instances de Log4J écriront simultanément dans le même fichier, conduisant à un comportement étrange.

Si votre application suit les principes SOA, pour chaque service A, vous aurez les composants suivants:

  1. Un contrôleur
  2. Une implémentation de service
  3. Un exécuteur
  4. Une persistance

Donc, il est plus facile d’avoir un aController.log aService.log aExecutor.log et aPersistance.log

Il s’agit d’une séparation par couche afin que toutes vos classes Remoting / REST / SOAP écrivent dans aController.log

Tout votre mécanisme de planification, service backend, etc. écrira à aService.log

Et toutes les exécutions de tâches sont écrites dans aExecutor.log et ainsi de suite.

Si vous avez un exécuteur multi-thread, vous devrez peut-être utiliser un accumulateur de journal ou une autre technique pour aligner correctement les messages de journal pour plusieurs threads.

De cette façon, vous aurez toujours 4 fichiers journaux qui ne sont pas beaucoup et pas trop, je vous le dis par expérience, cela rend la vie vraiment plus facile.