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
// Pour Log4J avec commons-logging private static final Log logger = LogFactory.getLog(MyClass.class); // Pour logback avec slf4j 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.
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):
<bean id="loggingAdvice" class="fr.webeo.rapidoo.aop.LoggingAdvice" /> <bean id="loggingAdvisor" class="org.springframework.aop.support.RegexpMethodPointcutAdvisor"> <property name="advice" ref="loggingAdvice" /> <property name="pattern" value="fr.webeo.rapidoo.services" /> <property name="order" value="50" /> </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
public class LoggingAdvice implements MethodBeforeAdvice, AfterReturningAdvice, ThrowsAdvice { /** * Default Constructor. */ public LoggingAdvice() { // Nothing to do } /** * {@inheritDoc} */ @Override final Logger logger = LoggerFactory.getLogger(target.getClass()); if (logger.isInfoEnabled()) { logger.info("Calling : " + method.getName() + " " + target.getClass().getName()); } if (logger.isDebugEnabled()) { final StringBuilder sb = new StringBuilder(); sb.append("Parameters : "); if (method.getParameterTypes().length == args.length) { for (int i = 0; i < args.length; i++) { sb.append("\r\nParam : ").append(method.getParameterTypes()[i]).append(" - ").append(args[i] == null ? "null" : args[i].toString()); } } if (sb.length() > 1000) { logger.debug(sb.substring(0, 1000)); } else { logger.debug(sb.toString()); } } } /** * {@inheritDoc} */ @Override final Logger logger = LoggerFactory.getLogger(target.getClass()); if (logger.isInfoEnabled()) { logger.info("Returning : " + method.getName()); } if (logger.isDebugEnabled() && returnValue != null) { logger.debug("Return value is list of " + ((List<?>) returnValue).size() + " elements."); } else { logger.debug("Return Value = " + returnValue.toString()); } } } /** * Call after an exception thrown. * * @param method the method called * @param args the method arguments * @param target the object who call the method * @param exception the exception thrown */ final Logger logger = LoggerFactory.getLogger(target.getClass()); if (logger.isErrorEnabled()) { logger.error("Throwing : " + method.getName() + " \r\n=> " + exception.getMessage(), exception); } } }
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 ??