La gestion correcte des logs en .Net

Bien souvent l'enregistrement des evènements d'une application est oublié faute de moyen rapide et efficace. Dans cet article nous verrons ce que met le framework .NET à notre disposition afin de "logger" son application.

N'hésitez pas à commenter cet article ! Commentez Donner une note à l'article (5)

Article lu   fois.

L'auteur

Profil ProSite personnel

Liens sociaux

Viadeo Twitter Facebook Share on Google+   

I. Introduction

Tout au long de cet article, j'utiliserais le terme "logger" une application pour désigner l'action d'enregistrer à un endroit les événements qui surviennent dans l'application. Faute d'avoir un terme simple en français, j'ai gardé l'anglais.

Nous connaissons tous les fameux fichiers de log des applications comme Apache ou ceux du systèmes.
Mais ce que nous ne faisons pas généralement c'est logger sa propre application.
Pourtant c'est une grave erreur qui peut vous couter très cher en temps et en aspirine. Sans compter que vous perdez la possibilité d'avoir une explication claire du problème par vos utilisateurs quand ceux-ci vous appelleront au secours.

Dans cet article nous verrons quels moyens sont mis à la disposition du programmeur afin de facilement gérer les logs de son application et comment les utiliser.

II. A la recherche du bug ...

La dernière étape avant le déploiement d'une application reste la recherche de bug dans l'application. Pour ce faire il peut être bien utile d'avoir une sortie des événements, des états des variables ou des connexions dans un fichier facilement lisible. Mais ces précieuses informations si utiles au développeur ne sont pas utiles à l'utilisateur et auront même tendance à lui faire peur. De plus, tout le monde sait que l'écriture dans un fichier ou sur une sortie console est une opération longue, ce qui entraine une perte de performance de l'application.
Toutefois il n'est pas question d'écrire du code pour faire le débogage puis de le supprimer avant de recompiler l'application en "release".
Alors comment faire pour concilier les deux ?
C'est la que le framework intervient en nous fournissant la constante de compilation DEBUG et les classes de manipulation d'informations de débogage.

II-A. La constante de compilation DEBUG

Qu'est ce qu'une constante de compilation ? Tout simplement une petite chaîne de caractères qui permet au compilateur d'effectuer certaines actions en sa présence ou non.
Les développeurs C/C++ connaissent bien ce principe notamment par les fameuses directives de pré-compilation #ifdef (qui au passage n'est pas morte dans .NET elle s'appelle #if CONSTANTE #endif).
Cette constante est passée au compilateur, par votre EDI préféré, par défaut en mode Debug.

II-B. La base de l'utilisation des classes Debug et Trace

Les classes Debug et Trace sont deux classes de l'espace de nom System.Diagnostics. Ce sont elles qui vont vous permettre d'enregistrer l'activité de votre application que ce soit pour le débogage ou pour un suivi en production.
Quelle est la différence entre les deux classes ?
Tout simplement la classe Debug permet moins de chose que la classe Trace. Mais pourquoi faire 2 classes alors ? C'est à cause des constantes de compilation : la classe Debug est active si la constante DEBUG est définie alors que la classe Trace est active si la constante TRACE est définie. Cela donne une souplesse au système comme par exemple permettre de ne pas avoir les informations de débogage sur une version de production.
Ces classes ont toutes deux besoin qu'on leur fournisse des écouteurs. Que sont ces écouteurs ? Ce sont des instances de classe permettant de sauvegarder les informations que vous allez transmettre aux classes Debug et Trace. Dans les faits, les classes Debug et Trace se contentent de relayer les informations aux écouteurs qui eux enregistrent les informations pour de bon.
Pourquoi un tel système ? Cela vous permet d'avoir plusieurs écouteurs et donc d'enregistrer les informations à plusieurs endroits à la fois.

II-B-1. Les options communes à tous les écouteurs

Tous les écouteurs dérivent d'une classe de base : TraceListener.
Cette classe expose un ensemble de méthodes de base permettant de gérer les écouteurs.
La propriété IndentLevel permet de définir le niveau courant d'indentation de la sortie de l'écouteur.
La propriété TraceOuputOptions définie les options de sortie des écouteurs pour la classe TraceSource. Ces options vous permettent de définir les sorties automatiques qui seront ajoutées aux sorties des écouteurs. Vous pouvez ainsi ajouter automatiquement la date (TraceOptions.DateTime), la pile d'appel (TraceOptions.CallTrace), ou encore le numéro du processus (TraceOptions.ProcessId). Pour avoir la totalité des options possibles, consultez la documentation du framework. Ces options peuvent être ajoutées, de manière cumulative, grâce à une addition bit à bit.

 
Sélectionnez

TextWriterTraceListener textWriter = new TextWriterTraceListener("trace.log");
//Ajout bit à bit de deux options de sortie
textWriter.TraceOutputOptions = TraceOptions.CallStack|TraceOptions.ProcessId;

II-B-2. Les fonctions communes

Les écouteurs disposent de trois méthodes principales pour vous permettre d'enregistrer les événements.
Les méthodes Write et WriteLine vous permettent d'écrire un texte dans l'écouteur.
La méthode Fail vous permet de lever un message d'erreur et d'afficher une boîte de dialogue standard de description d'erreur.

II-B-3. Les différents écouteurs du framework

II-B-3-a. Le TextWriterTraceListener

Le premier et le plus simple des écouteurs est le TextWriterTraceListener. Cet écouteur vous permet d'enregistrer les activités dans un fichier texte.
Il se déclare et s'initialise comme ceci :

 
Sélectionnez

TextWriterTraceListener debugTextListener = new TextTraceListener("error.log");

Ce code vous permet de créer un fichier de log nommé error.log.

II-B-3-b. Le ConsoleTraceListener

L'écouteur ConsoleTraceListener permet d'afficher les informations sur la console.

 
Sélectionnez

ConsoleTraceListener console = new ConsoleTraceListener(true);

La valeur booléenne du constructeur permet de spécifier si nous souhaitons utiliser la sortie standard d'erreur (true) ou non (false). Par défaut cette valeur vaut vrai.

II-B-3-c. L'EventLogTraceListener

Cet écouteur vous permet d'écrire dans le journal d'événement de Windows. Les entrées seront écrites dans le journal Application par défaut mais rien ne vous empêche de créer votre propre journal dans le système de log de Windows.
Il s'utilise de manière très simple :

 
Sélectionnez

EventLogTraceListener eventLog = new EventLogTraceListener("MaSuperApplication");

Avec ce code vous allez écrire les informations dans le journal Application avec comme nom de source "MaSuperApplication".

Le message affiché dans le journal d'évènement de Windows
Le message affiché dans le journal d'évènement de Windows

II-B-3-d. Le WebPageTraceListener

Cet écouteur vous permet de diriger les messages vers une page ASP.NET. Il s'utilise comme les autres écouteurs à la seule différence qu'il est situé dans l'espace de nom System.Web.

II-C. Utilisation de Debug

Tout le travail de log en débogage se fait grâce à la classe statique Debug de l'espace de nom System.Diagnostics.
Voyons comment l'utiliser afin de créer un fichier de log.

 
Sélectionnez

using System.Diagnostics;

public class ClasseADebugger
{
	public ClassesADebugger()
	{
		Debug.Listeners.Add(new TextWriterTraceListener("./debug.log")); //Création d'un "listener" texte pour sortie dans un fichier texte
		Debug.AutoFlush = true; //On écrit directement, pas de temporisation.
		
		Debug.WriteLine("Constructeur " + this.ToString());
	}
}

Et c'est tout ! En effet ces quelques ligne suffisent à mettre en place un système de suivi en mode débogage.
A cette étape, vous avez un écouteur de configuré qui va s'occuper d'écrire dans un fichier de log toute les informations que vous lui transmettez. Mais rien ne nous empêche d'envoyer ces informations dans plusieurs écouteurs. Afin de faire ceci, il nous suffit de rajouter un écouteur à Debug.

 
Sélectionnez

Debug.Listeners.Add(new EventLogTraceListener("MaSuperApplication"));

Voilà, vous avez maintenant un système de suivi des événements dans votre application pour le débogage.

III. Suivez votre application à la trace

La classe Trace fait exactement la même chose que la classe Debug à la seule différence qu'elle le fait quand l'application est compilée en mode Debug et en mode Release. Je ne vais donc pas détailler l'utilisation de cette classe, il suffit de se reporter à l'utilisation de la classe Debug.

IV. Utilisation avancée des méthodes de suivi d'événement

Précédement nous avons vu une utilisation basique des classes Debug et Trace. En effet nous nous sommes contenté d'écrire des informations sans distinction quant à l'importance de l'erreur. Nous allons maintenant corriger cet oubli.

IV-A. Utilisation du TraceSwitch

La classe TraceSwitch permet de spécifier le niveau d'information que nous souhaitons écrire dans les écouteurs. Afin de pouvoir l'utiliser, nous allons modifier un peu notre façon d'utiliser les classes Debug et Trace en utilisant non pas WriteLine mais WriteLineIf.

 
Sélectionnez

Debug.WriteLineIf(condition, "MonTexte");

La classe TraceSwitch possède quatres niveaux de trace (dans l'ordre de celui qui doit écrire le moins à celui qui doit écrire le plus) : Error, Warning, Info, Verbose. Ces quatres niveaux vous permettent de choisir la quantité d'information écrite en fonction du besoin.
Les TraceSwitchs se configurent grâce au fichier de configuration de l'application "nomDuProgramme.exe.config". Cette méthode qui est la plus simple, et surtout la meilleure, vous permet de facilement modifier la valeur du TraceSwitch et ainsi, en cas de problème, d'obtenir plus de renseignement sans devoir recompiler l'application.

 
Sélectionnez

<configuration>
  <system.diagnostics>
    <switches>
      <add name="MySwitch" value="4"/>
    </switches>
  </system.diagnostics>
</configuration>

L'extrait de fichier de configuration au dessus montre la configuration d'un TraceSwitch nommé MySwitch qui est configuré pour écrire les informations en mode verbeux (niveau 4). Le niveau 0 (value="0") désactive le TraceSwitch et plus rien n'est écrit. Ensuite les niveaux 1, 2, 3 et 4 suivent l'ordre donné plus haut.
Afin de faire le lien entre les valeurs du fichier de configuration et l'instance du TraceSwitch il faut respecter cette règle simple : Le "displayName" du constructeur du TraceSwitch doit être égal à la valeur de la propriété name du fichier de configuration. Ainsi, dans le cas précédent, cela donne :

 
Sélectionnez

traceSwitch = new TraceSwitch("MySwitch", "TraceSwitch de test");
Debug.WriteLineIf(traceSwitch.TraceError, "Trace erreur");
Debug.WriteLineIf(traceSwitch.TraceWarning, "Trace warning");
Debug.WriteLineIf(traceSwitch.TraceInfo, "Trace info");
Debug.WriteLineIf(traceSwitch.TraceVerbose, "Trace Verbeux");

Si vous exécutez l'exemple précédent vous remarquerez qu'avec le niveau 4 vous obtenez tous les messages. En effet les niveaux d'affichage sont cumulatifs, ce qui veut dire que si vous définissez votre TraceSwitch au niveau Warning les messages du niveau Error seront également traités.
Dans les exemples précédents nous avons manipulé un seul TraceSwitch mais rien ne nous empêche d'en utiliser plusieurs :

 
Sélectionnez

<configuration>
  <system.diagnostics>
    <switches>
      <add name="MySwitch" value="4"/>
      <add name="MySwitch2" value="2" />
    </switches>
  </system.diagnostics>
</configuration>
 
Sélectionnez

tS2 = new TraceSwitch("MySwitch2", "TraceSwitch numéro 2");
Debug.WriteLineIf(tS2.TraceError, "Trace erreur 2");
Debug.WriteLineIf(tS2.TraceVerbose, "Message non affiché par le traceSwitch numéro 2");

D'un point de vue concret quelles sont les actions que vous, développeurs, devez mettre en oeuvre pour avoir une gestion correcte des logs ?
Tout d'abord en phase de débogage vous devez utiliser les méthodes de la classe Debug. En effet ces appels ne seront pas présents dans la version release et cela vous permet donc d'écrire des quantités importantes d'informations sans pénaliser l'utilisateur final avec des ralentissements.
Ensuite vous devez, aux points importants de votre application, utiliser les méthodes de la classe Trace combinées à un TraceSwitch afin d'obtenir le niveau d'information adapté à la situation.
Ainsi, par défaut, il est préférable de positionner le TraceSwitch à 1 afin de n'avoir dans le fichier de log que les erreurs qui sont survenues. Si ces erreurs vous intriguent, il vous suffit de positionner le TraceSwitch à une valeur plus élévée afin d'obtenir d'avantage d'informations.
L'utilisation judicieuse des niveaux d'information d'un TraceSwitch est un atout important dans la gestion de vos fichiers de log qui vous permettra de bien hiérarchiser les informations sur votre application. Dans cette optique, il est très fortement recommandé de définir les TraceSwitchs dans le fichier de configuration de la l'application et non pas en "dur" dans le code. Cela vous permet de n'avoir qu'une valeur à modifier dans un fichier texte, plutôt que de devoir recompiler la totalité de l'application puis de la redéployer.

V. Conclusion

En conclusion, nous pouvons dire qu'encore une fois le framework nous permet de facilement accéder, utiliser et gérer un système qui peut s'avérer complexe. Les classes fournies par le framework permettent un grand nombre de choses et avec beaucoup de souplesse.

VI. Bibliographie et téléchargements

Bibliographie :

Les classes Debug et Trace.

Téléchargements :
L'article au format pdf
Les sources de l'application exemple.

VI. Remerciements

Je tiens à remercier StormimOn pour sa correction orthographique, ainsi que toute l'équipe dotnet pour son aide dans le règlement des petits détails de cet article.

Vous avez aimé ce tutoriel ? Alors partagez-le en cliquant sur les boutons suivants : Viadeo Twitter Facebook Share on Google+   

  

Copyright © 2006 Lainé Vincent. Aucune reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.