Un petit billet au sujet de la gestion des logs avec Java :

Si on se réfère au règles diverses (Checkstyle, PMD ...), voici la bonne façon de déclarer un logger en Java

  1. // Pour Log4J avec commons-logging
  2. private static final Log logger = LogFactory.getLog(MyClass.class);
  3.  
  4. // Pour logback avec slf4j
  5. private static final Logger logger = LoggerFactory.getLogger(MyClass.class);

...Hein.. c'est quoi logback ???

Logback est le digne hériter de log4j, il a été conçu en même que les api slf4j par le créateur lui même de log4j: Ceki Gülcü

Il y a de nombreuses raisons d'utiliser logback, et elles sont toutes expliquées sur le site http://logback.qos.ch/reasonsToSwitch.html

Le seul défaut est que vous perdrez le niveau fatal, mais bon toutes les erreurs ne devraient-elles pas êtres évitées, et un niveau fatal devraient entrainer l'arrêt de l'application ??

Donc switchons tous ensemble :D

Revenons à nos moutons si on veut logger des évènements qui ont lieu dans une classe abstraite commune la bonne solution est de déclarer le logger protégé et de l'initialiser de manière statique.

  1. protected transient final Logger logger = LoggerFactory.getLogger(this.getClass());

Toutefois cette déclaration n'est pas compatible avec une règle (qui à dit 'à la con') de PMD à savoir LoggerIsNotStaticFinal

Certains puristes veulent que la classe Abstraite et la classe fille ai chacune leur propre logger pour permettre de les filtrés différemment avec le fichier de configuration du gestionnaire de log. (cf ce billet )

Personnellement j'ai jamais voulu séparer les logs des méthodes abstraites hérités de celles déclarées dans la classe fille, surtout quand la pile d'appel est imbriqué et que plusieurs classes filles peuvent être appelés.

On pourrait ajouter l'information getClass() dans chaque log de la classe abstraite, mais c'est quand même relativement pénible

Donc comment faire ??

Une des solutions peut-être d'utiliser AOP => en bon français la Programmation par aspect (soit en bon anglais Aspect Oriented Programmation à opposer à la OOP)

Avec Spring AOP par exemple on peut ajouter des logs sur des points de coupe définissant des endroits où logger.

Voici la déclaration via le conteneur léger de spring (spring-beans):

  1. <bean id="loggingAdvice" class="fr.webeo.rapidoo.aop.LoggingAdvice" />
  2.  
  3. <bean id="loggingAdvisor" class="org.springframework.aop.support.RegexpMethodPointcutAdvisor">
  4. <property name="advice" ref="loggingAdvice" />
  5. <property name="pattern" value="fr.webeo.rapidoo.services" />
  6. <property name="order" value="50" />
  7. </bean>

A vous de spécialiser votre point de coupe si vous voulez plus de précision, ici toutes les méthodes de toutes les classes du package fr.webeo.rapidoo.services seront traitées à la même enseigne !

Et l'advice qui va logger tout seul les classes filles

  1. public class LoggingAdvice implements MethodBeforeAdvice, AfterReturningAdvice, ThrowsAdvice {
  2.  
  3. /**
  4.   * Default Constructor.
  5.   */
  6. public LoggingAdvice() {
  7. // Nothing to do
  8. }
  9.  
  10. /**
  11.   * {@inheritDoc}
  12.   */
  13. @Override
  14. public void before(final Method method, final Object[] args, final Object target) throws Throwable {
  15. final Logger logger = LoggerFactory.getLogger(target.getClass());
  16. if (logger.isInfoEnabled()) {
  17. logger.info("Calling : " + method.getName() + " " + target.getClass().getName());
  18. }
  19. if (logger.isDebugEnabled()) {
  20. final StringBuilder sb = new StringBuilder();
  21. sb.append("Parameters : ");
  22. if (method.getParameterTypes().length == args.length) {
  23. for (int i = 0; i < args.length; i++) {
  24. sb.append("\r\nParam : ").append(method.getParameterTypes()[i]).append(" - ").append(args[i] == null ? "null" : args[i].toString());
  25. }
  26. }
  27. if (sb.length() > 1000) {
  28. logger.debug(sb.substring(0, 1000));
  29. } else {
  30. logger.debug(sb.toString());
  31. }
  32. }
  33. }
  34.  
  35. /**
  36.   * {@inheritDoc}
  37.   */
  38. @Override
  39. public void afterReturning(final Object returnValue, final Method method, final Object[] args, final Object target) throws Throwable {
  40. final Logger logger = LoggerFactory.getLogger(target.getClass());
  41.  
  42. if (logger.isInfoEnabled()) {
  43. logger.info("Returning : " + method.getName());
  44. }
  45. if (logger.isDebugEnabled() && returnValue != null) {
  46. if (returnValue instanceof List) {
  47. logger.debug("Return value is list of " + ((List<?>) returnValue).size() + " elements.");
  48. } else {
  49. logger.debug("Return Value = " + returnValue.toString());
  50. }
  51. }
  52. }
  53.  
  54. /**
  55.   * Call after an exception thrown.
  56.   *
  57.   * @param method the method called
  58.   * @param args the method arguments
  59.   * @param target the object who call the method
  60.   * @param exception the exception thrown
  61.   */
  62. public void afterThrowing(final Method method, final Object[] args, final Object target, final Throwable exception) {
  63. final Logger logger = LoggerFactory.getLogger(target.getClass());
  64. if (logger.isErrorEnabled()) {
  65. logger.error("Throwing : " + method.getName() + " \r\n=> " + exception.getMessage(), exception);
  66. }
  67. }
  68. }

Pour conclure il existe 3 possibilités pour logger des classes filles héritant d'une même classe abstraite :

  • Un logger par classe

Avantages : Chaque ligne de la log renvoi vers la bonne classe physique

Défauts : les log contiendront des log par enfant et pour la classe mère, donc si quelque chose se passe mal dans la classe abstraite il faut espérer trouver à proximité une log indiquant quel est la classe fille concernée (ou bien la rajouter explicitement dans la ligne loggée)

  • Déclaration du logger de la classe abstraite en protected non static

Avantages : Toutes les logs seront générés au nom de la classe fille

Défauts : les numéros de lignes des méthodes héritées concerneront la classe abstraite

  • Déclaration des loggers via AOP :

Avantages : Déclaration très rapide, bonne personnalisation

Defauts : Seulement utile pour les log génériques, induit donc une bonne conception objet avec des méthodes courtes et atomiques
Et vous comment faîtes-vous ??