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ème.
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. À la recherche du bug…▲
La dernière étape avant le déploiement d'une application reste la recherche de bugs 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 choses que la classe Trace. Mais pourquoi faire deux 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éfinit 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.
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 :
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.
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 :
EventLogTraceListener eventLog =
new
EventLogTraceListener
(
"MaSuperApplication"
);
Avec ce code vous allez écrire les informations dans le journal Application avec comme nom de source « MaSuperApplication ».
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.
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 lignes suffisent à mettre en place un système de suivi en mode débogage.
À cette étape, vous avez un écouteur de configuré qui va s'occuper d'écrire dans un fichier de log toutes 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.
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édemment nous avons vu une utilisation basique des classes Debug et Trace. En effet nous nous sommes contentés 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.
Debug.
WriteLineIf
(
condition,
"MonTexte"
);
La classe TraceSwitch possède quatre niveaux de trace (dans l'ordre de celui qui doit écrire le moins à celui qui doit écrire le plus) : Error, Warning, Info, Verbose. Ces quatre 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 renseignements sans devoir recompiler l'application.
<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 :
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 :
<configuration>
<system.diagnostics>
<switches>
<add
name
=
"MySwitch"
value
=
"4"
/>
<add
name
=
"MySwitch2"
value
=
"2"
/>
</switches>
</system.diagnostics>
</configuration>
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 œuvre 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 élevée afin d'obtenir davantage 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▲
VII. 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.