Pourquoi?

Lors du développement d'un programme complexe (mettons 100 lignes ou plus), il apparait souvent que le programmeur, emporté par son élan lors des tests, ne comprennent pas "pourquoi ça marche pô?". Le reflexe de base passe souvent par l'ajout un peu partout de lignes du type:

  • System.out.println(...) (avec un peu de chance ce sera System.err mais c'est généralement pas dans le réflexe.)
  • printf(...)
  • echo "..."
  • etc...

La plupart des langages proposent deux flux de sortie (pour un seul d'entrée, généralement le clavier) au démarrage, c'est le cas du C (stdout et stderr) et du java (System.out et System.err) par exemple. D'autres n'en proposent pas du tout comme le Javascript, dans ce cas, on peut toujours utiliser alert() (mais c'est vraiment pas pratique), un popup ou un document.write dans l'urgence.

Cette pratique bien mignonne fini généralement par laisser trainer des traces du type "içi" un peu partout dans le programme final à la fin des tests, entrainant une chasse aux traces mal placées qui bousillent les sorties du résultat final. Et c'est encore pire en cas de régression puisqu'il faut tout refaire ou dé-commenter pour bien comprendre ce qu'il se passe. Que de temps perdu et de doutes au moment de rendre sa copie (au client ou au prof). Heureusement, aujourd'hui il existe d'excellentes librairies dans la plupart des langages qui prennent en compte cette problématique et offrent aux programmeurs un moyen de laisser le code nécessaire au suivit tout en s'assurant que cela ne transparaitra pas dans le produit final.

Comment?

Je vais traiter de log4j, puisque j'ai eu l'occasion de l'utiliser et de l'apprécier, mais il faut savoir qu'il existe des bibliothèques appartenant au même projet pour le C++, le .net et sans doute bientôt pour le PHP (en incubation dit la page du projet, et c'est bien dommage, surtout parce que j'en ai besoin) et des dérivés non officiels tels que log4Javascript (ou log4JS) ou log4c (je ne sais pas comment c'est fait, mais puisque le C n'est pas un langage objet, ça risque d'être intéressant). Ou des projets totalement différents comme, par exemple, les packages logging de python (intégrés directement dans le langage).

Dans le cas des librairies de type log4... elles se basent sur plusieurs composants:

Les Loggers dont le rôle est d'acheminer le message jusqu'à un ou plusieurs Appender en mesure de le traiter. les loggers sont répartis en un arbre, le logger par défaut étant situé à sa racine et il appartient à chaque logger de décider si un message le concerne ou non avant de faire remonter le message... ou non. (si un message à été affiché il peut quand même être remonté).

Les Appenders qui sont à même de traiter le message, en l'affichant dans une liste, envoyant par e-le réseau, l'écrivant dans un fichier,... Il existe un certain nombre d'appenders, par exemple, il est possible:

  • d'utiliser la console, comme auparavant, mais cette fois en disposant du filtrage mis en place par les loggers (ou d'un filtre propre à l'appender) afin d'éviter que l'utilisateur final aie accès à des informations sur l'implémentation ou simplement des secrets industriels (dans un logiciel propriétaire, c'est mieux)
  • d'envoyer le message d'erreur dans un fichier spécifique avec (ou sans) procédure de rotation[1] lorsque le fichier devient trop conséquent.
  • d'envoyer le message à un autre ordinateur sur le réseau (en mode client-serveur, il est possible que Log4j agisse comme client ou comme serveur). Cela peut être intéressant pour une application située dans un environnement comprenant de la répartition de charge (les messages sont centralisés et l'administrateur est heureux) ou bien dans un environnement que l'on désire sécurisé (dans ce cas, si le serveur est corrompu, il est toujours possible de remonter le fil des événements qui ont conduit à cet état alors que le serveur est injoignable ou si les logs locaux sont formatés.)
  • d'utiliser une base de données afin de d'intégrer les messages au sein d'une plus grande application d'administration. (par exemple, en conjonction avec un outil de gestion de parc tel que GLPI)
  • d'utiliser une application graphique (LF5) qui affichera les éléments avec plein de cases à cocher pour faire le tri. (pratique quand on ne sais pas bien ce qu'on cherche)
  • d'utiliser l'un des "autres" moyens prévus par défaut c'est à dire: au travers de JMS (un composant de JNDI destiné à transmettre des "messages"), du système de journalisation de windows, de "rien" (oui, c'est pas forcément utile dans la vie de tous les jours), d'une boite mail (avec un tampon quand même, mais merci de ne pas utiliser mon adresse pour envoyer vos messages, quand le niveau est à debug (ou tous les autres)), du système de journalisation de linux (le même syslog), d'une connection telnet effectué par l'administrateur, ou n'importe quel "flux de sortie" (Outpustream ou writer) java. (sait on jamais)
  • d'utiliser VOTRE moyen, il suffit alors d'implémenter l'interface kivabien "Appender" (ou de surcharger "AppenderSkeleton" pour les plus malins) et *pouf* vous pouvez creer votre appender[2]

Comme ça, ça peut faire "un peu beaucoup" de fonctions à gérer, mais en réalité, le code à mettre en place est très simple et le voila: ruuuuummmmmbl

Logger logger = Logger.getLogger("Mon.nom.de.classe.complet");//vaut aussi pour les noms fonctionnels, exemple: monprog.chargement.agenda.rendezvous
logger.trace("un message pour celui qui veut TOUT savoir");
logger.debug("Un message utilisé pour la chasse au bugs");
logger.info("Un d'information, parce que tout va bien");
logger.warn("un message qui signale qu'un incident est arrivé");
logger.error("OUPS, c'était pas prévu");
logger.fatal("*pouf*, planté");

Facile non? normal, la difficulté n'est pas là. ("surprise!") mais dans un fichier de configuration à part (on peut le faire dans le code, mais on perds tout l'intérêt et c'est moins pratique (pourquoi tout re-compiler alors qu'un bloc notes suffit ?). Ce fichier peut être au format XML ou sous forme de ".properties" bien connu des développeurs java (ceux qui aiment ce qu'ils font, les vrais, les forts). Inutile de dire que ma préférence va au .properties, le XML étant, pour moi, l'équivalent de la dynamite pour casser un oeuf. Voilà un petit exemple de comment configurer quelques appenders:

#On affiche sur la console, le résultat est le même, mais c'est propre et plus facile à changer
log4j.appender.stderr=org.apache.log4j.ConsoleAppender
#On utilise un joli masque pour que ce soit sous la forme qu'on veut
log4j.appender.stderr.layout=org.apache.log4j.PatternLayout
log4j.appender.stderr.layout.ConversionPattern=%6.6p %d{MM/dd/yyyy HH:mm:ss}  %c - %m%n
#ici, on met le tout dans un joli fichier qui s'efface tout seul avec le temps
log4j.appender.fichier=org.apache.log4j.RollingFileAppender
log4j.appender.fichier.File=monAppli.log
log4j.appender.fichier.MaxFileSize=1MB
log4j.appender.fichier.Append=false
log4j.appender.fichier.MaxBackupIndex=0
log4j.appender.fichier.layout=org.apache.log4j.PatternLayout
log4j.appender.fichier.layout.ConversionPattern=%d{MM/dd/yyyy HH:mm:ss} %-90.90c (%6.6p)  - %m%n

Le %d{...} est assez explicite une date, une heure suivit d'un %c qui au passage force la longueur du nom de la classe/fonctionnalité qui souhaite afficher un message à 90 (séré à gauche, les autres sont possibles comme indiqué ici

On continue

log4j.appender.lf5=org.apache.log4j.lf5.LF5Appender
log4j.appender.lf5.MaxNumberOfRecords=10000

Et hop, on démarre une fenêtre swing plutot qu'un fichier moche (attention quand même, les messages de trace sont traités comme info (sic). Par contre, il est assez utile de préciser MaxNumberOfRecords parceque la puce mémoire infinie n'existe pas (faut pas rêver)

Concluons

Malgré la diversité des frameworks de logs, la majorité fonctionne sur ces principes. Certains oublient certaines des fonctionnalités de log4J comme la bibliothèque "Log" de PEAR (en PHP) qui n'est pas aussi simple à configurer et ne dispose pas de la notion d'arborescence. Malgré cela, ce n'est pas une raison pour s'en passer et se retrouver avec des "print" en moche dans le code et au final rendre des pages avec de gros warnings au millieu du contenu ou encore des "boucle1" dans le contenu. Et chasser les sorties de debug et les commenter pendant des heures... c'est bien gentil, mais sans rire, qui aime faire ça?

Notes

[1] on retrouve ce concept avec syslog sous linux où les fichiers, lorsqu'ils dépassent une certaine taille, voient leur nom étendu n'un nombre allant croissant le système purge alors les journaux trop anciens et commencent un nouveau fichier.

[2] dans un jeu 3D ou un petit bonhomme doit attraper dans un panier les messages et les stacktraces qui tombent du ciel sinon, le message est perdu et l'administrateur perds son emploi parce qu'il n'as pas été capable de diagnostiquer le problème).... enfin, pour les plus tordus d'entre nous.