VS 2010 : Personnalisation des évènements de l’IntelliTrace 4

VS 2010 : Personnalisation des évènements de l'IntelliTrace

Mon dernier billet date de 3 mois, il est temps de mit remettre :)

Aujourd’hui je vais vous montrer comment vous pouvez personnaliser l’IntelliTrace de VisualStudio 2010 afin qu’il trace les évènements de vos propres méthodes. Mais avant de commencer il faut que je vous préviennes (en rouge et gras pour que ce soit bien visible :) ) :

Microsoft ne fournit pas et ne fournira ni documentation ni support sur ce que je vais expliquer. Par ailleurs il n’est pas sur que la personnalisation de l’IntelliTrace ne soit pas modifiée dans le prochain VisualStudio (d’ou la non documentation et le non support).

Maintenant que je vous ai prévenu passons dans le vif du sujet : comment faire pour que je puisse avoir dans l’IntellTrace les appels à mes propres méthodes ! Afin de vous l’expliquer nous allons utiliser la classe suivante :

public class ColoredConsole
{
    public static void WriteLine(int color, bool value)
    {
        ColoredConsole.WriteLine((ConsoleColor)color, () => Console.WriteLine(value));
    }

    public void WriteLine2(int color, bool value)
    {
        ColoredConsole.WriteLine((ConsoleColor)color, () => Console.WriteLine(value));
    }

    public static void WriteLine(ConsoleColor color, char value)
    {
        ColoredConsole.WriteLine(color, () => Console.WriteLine(value));
    }

    public static void WriteLine(ConsoleColor color, decimal value)
    {
        ColoredConsole.WriteLine(color, () => Console.WriteLine(value));
    }

    //...

    public static void WriteLine(ConsoleColor color, string format, params object[] arg)
    {
        ColoredConsole.WriteLine(color, () => Console.WriteLine(format, arg));
    }

    private static void WriteLine(ConsoleColor color, Action writeLine)
    {
        var foreColor = Console.ForegroundColor;
        Console.ForegroundColor = color;

        writeLine();

        Console.ForegroundColor = foreColor;
    }
}

Cette classe permet d’écrire une ligne dans la console en spécifiant une couleur. Nous allons maintenant voir comment obtenir le résultat suivant dans l’IntelliTrace :

image

Comme vous pouvez le voir l’IntelliTrace a capturé l’appel aux méthodes de la classe ColoredConsole avec leurs paramètres d’entrées (ex: Console Output « 3/7/2010 12:09:14 PM » in DarkBlue).

Pour personnaliser les évènements de l’IntelliTrace il faut modifier le fichier CollectionPlan.xml se trouvant dans le sous répertoire de VisualStudio « Team Tools\TraceDebugger Tools\en ». Ce fichier xml contient la liste des évènements (méthodes) que doit pouvoir tracer l’IntelliTrace. La première chose à faire est de déclarer une nouvelle catégorie d’évènements. Pour cela on va rajouter un élément Category sous TracePointProvider/Categories :

<Categories>
  <Category Id="system.data" _locID="category.SystemData">ADO.NET</Category>
  <Category Id="system.web" _locID="category.SystemWeb">ASP.NET</Category>
  <Category Id="console" _locID="category.SystemConsole">Console</Category>
  <Category Id="data.binding" _locID="category.DataBinding">Data Binding</Category>
  <Category Id="environment.variables" _locID="category.SystemEnvironment">Environment Variables</Category>
  <Category Id="file.access" _locID="category.FileAccess">File</Category>
  <Category Id="gesture" _locID="category.Gesture">Gesture</Category>
  <Category Id="lazy" _locID="category.Lazy">Lazy Initialization</Category>
  <Category Id="registry.access" _locID="category.RegistryAccess">Registry</Category>
  <Category Id="servicemodel" _locID="category.SystemServiceModel">ServiceModel</Category>
  <Category Id="threading" _locID="category.Threading">Threading</Category>
  <Category Id="tracing" _locID="category.Tracing">Tracing</Category>
  <Category Id="user.prompt" _locID="category.UserPrompt">User Prompt</Category>
  <Category Id="workflow" _locID="category.SystemActivities">Workflow</Category>
  <Category Id="xml" _locID="category.SystemXml">XML</Category>
  <!-- Catégorie personnalisé -->
  <Category Id="qetza.coloredconsole" _locID="category.Qetza.ColoredConsole">ColoredConsole</Category>
</Categories>

Il faut noter que ce fichier n’est lu qu’une fois au démarrage de VisualStudio, si vous le modifier il faudra redémarrer VisualStudio pour que votre modification soit prise en compte. Une fois redémarré avec la modification ci-dessus, en ouvrant les options de l’IntelliTrace, dans la liste des catégories vous devriez voir « ColoredConsole » :

image

Il va maintenant falloir remplir cette catégorie. Pour cela il va falloir déclarer un module, une assembly dans laquelle l’IntelliTrace pourra trouver des évènements. On va donc ajouter notre assembly à la liste de celle déjà présentes dans l’élément TracePointProvider/ModulesSpecifications :

<ModuleSpecifications>
  <ModuleSpecification Id="mscorlib">mscorlib.dll</ModuleSpecification>
  <ModuleSpecification Id="mscorlib2_only" maxVersion="2.0">mscorlib.dll</ModuleSpecification>
  <ModuleSpecification Id="mscorlib4_plus" minVersion="4.0">mscorlib.dll</ModuleSpecification>
  <ModuleSpecification Id="presentationframework">PresentationFramework.dll</ModuleSpecification>
  <ModuleSpecification Id="system">System.dll</ModuleSpecification>
  <ModuleSpecification Id="system.activities4_plus" minVersion="4.0">System.Activities.dll</ModuleSpecification>
  <ModuleSpecification Id="system.data">System.Data.dll</ModuleSpecification>
  <ModuleSpecification Id="system.servicemodel">System.ServiceModel.dll</ModuleSpecification>
  <ModuleSpecification Id="system.web">System.Web.dll</ModuleSpecification>
  <ModuleSpecification Id="system.web2_only" maxVersion="2.0">System.Web.dll</ModuleSpecification>
  <ModuleSpecification Id="winforms">System.Windows.Forms.dll</ModuleSpecification>
  <ModuleSpecification Id="system.xml">System.Xml.dll</ModuleSpecification>
  <!-- Module personnalisé -->
  <ModuleSpecification Id="qetza.consoleex.coloredconsole">Qetza.ConsoleEx.dll</ModuleSpecification>
</ModuleSpecifications>

Comme vous pouvez le voir, en plus de l’identifiant qui est obligatoire, l’élément ModuleSpecification peut avoir les attributs optionnels minVersion et/ou maxVersion permettant de distinguer un même assembly en fonction de sa version.

Maintenant que notre module est déclaré il va falloir indiquer l’ensemble des méthodes que nous voulons tracer. Pour cela il faut ajouter un élément DiagnosticEventSpecification par méthode sous l’élément TracePointProvider/DiagnosticEventSepcifications :

<DiagnosticEventSpecification enabled="false">
  <CategoryId>qetza.coloredconsole</CategoryId>
  <SettingsName _locID="settingsName.Qetza.ColoredConsole.WriteLine.Char">ColoredWriteLine (Char)</SettingsName>
  <SettingsDescription _locID="settingsDescription.Qetza.ColoredConsole.WriteLine.Char">Console Output with a colored Char passed in.</SettingsDescription>
  <Bindings>
    <Binding>
      <ModuleSpecificationId>qetza.consoleex.coloredconsole</ModuleSpecificationId>
      <TypeName>Qetza.ConsoleEx.ColoredConsole</TypeName>
      <MethodName>WriteLine</MethodName>
      <MethodId>Qetza.ConsoleEx.ColoredConsole.WriteLine(System.ConsoleColor,System.Char):System.Void</MethodId>
      <ShortDescription _locID="shortDescription.Qetza.ColoredConsole.WriteLine.Char">"{0}"</ShortDescription>
      <LongDescription _locID="longDescription.Qetza.ColoredConsole.WriteLine.Char">Console Output "{0}".</LongDescription>
      <DataQueries>
        <DataQuery index="1" maxSize="0" type="Char" _locID="dataquery.Qetza.ColoredConsole.WriteLine.Char" _locAttrData="name" name="value" query="" />
      </DataQueries>
    </Binding>
  </Bindings>
</DiagnosticEventSpecification>

Une petite explication s’impose :)

  • L’attribut enabled de l’élément DiagnosticEventSpecification permet de spécifier si la trace doit être active ou non par défaut.
  • L’élément CategoryId contient l’identifiant de la catégorie dans laquelle cet évènement doit se trouver. Dans l’exemple ci-dessus il s’agit de notre catégorie.
  • L’élément SettingsName permet de spécifier le nom de l’évènement tel qu’il apparaîtra dans sous la catégorie dans les options
  • L’élément SettingsDescription permet de spécifier le texte de description qui apparaîtra en tooltip sur le nom de l’évènement.
  • L’élément Bindings va contenir l’ensemble des binding à mettre en place lorsque l’évènement sera sélectionné. Chaque binding est décrit par un élément Binding. Vous pouvez donc activer la trace pour plusieurs méthodes via une seule entrée en spécifiant plus d’un élément Binding.
  • L’élément ModuleSpecificationId permet de spécifier dans quel module se trouve la méthode à tracer.
  • L’élément TypeName contient le nom complet du type contenant la méthode à tracer.
  • L’élément MethodeName contient le nom de la méthode à tracer.
  • L’élément MethodId contient l’identifiant de la méthode à tracer et doit avoir le format suivant : <type complet de la classe>.<nom de la méthode>(<types complets des paramètres séparés par des virgules>):<type complet de la valeur de retour>
  • L’élément ShortDescription contient le texte qui sera afficher dans l’IntelliTrace sur la première ligne juste après le nom de la catégorie. Cette chaine de caractère peut contenir des marqueurs comme pour la méthode String.Format. Les valeurs qui seront fournit sont les valeurs récupérées via les éléments DataQuery déclaré dans le fichier CollectionPlan.xml.
  • L’élément LongDescription contient le texte qui sera afficher sur la deuxième dans l’IntelliTrace. Comment pour ShortDescription ce texte peut contenir des marqueurs.

Avant de voir la suite voilà une petite image représentant visuellement les éléments que nous venons de voir :

image

Avec ce que nous avons vu vous pouvez déjà personnaliser l’IntelliTrace afin qu’il trace les appels à vos méthodes. Il reste maintenant à voir comment lui indiquer qu’elle sont les valeurs que vous souhaitez capturer à l’appel de la méthode. Pour cela il existe deux méthodes :

  • La première est de déclarer un élément DataQueries avec ses sous éléments DataQuery dans le fichier CollectionPlan.xml sous le Binding correspondant.
  • La deuxième est d’écrire une classe implémentant l’interface IProgrammableDataQuery déclarée dans l’assembly Microsoft.VisualStudio.IntelliTrace.dll.

Bien entendu ces méthodes ne sont pas équivalente. La première, entièrement déclarative, est la plus simple à mettre en place mais à une limitation, elle ne peut capturer que des valeurs de type simple :

  • Boolean
  • Char
  • Double
  • Single (float)
  • Int16
  • UInt16
  • Int32
  • UInt32
  • Int64
  • UInt64
  • String (une exception mais surement car ce type est très utilisé)

Afin de capturer une valeur d’un de ces types il faut donc spécifier un élément DataQuery. Cet élément accepte les attributs suivant :

  • index : L’attribut est obligatoire et :
    • S’il vaut –1 cela indique que la source est la valeur de retour (voir plus bas).
    • S’il vaut 0 cela indique que la source est l’instance courante si la méthode est une méthode d’instance. Si la méthode est une méthode statique alors 0 indique que la source est le premier paramètre de la méthode.
    • S’il vaut 1 cela indique que la source est dans le cas d’une méthode d’instance le premier paramètre et dans le cas d’une méthode statique le deuxième paramètre.
    • Et ainsi de suite pour les autres paramètres de la méthode.
  • maxSize : dans le cas d’une chaine de caractère cet attribut spécifie le nombre maximum de caractère à capturer. Il est obligatoire dans le cas d’une chaine de caractère.
  • type : cet attribut obligatoire spécifie le type de la valeur à capturer. Le type doit être un des types spécifié ci-dessus.
  • name : Il s’agit du nom que l’on va donner à  la valeur capturer afin de la reconnaitre facilement.
  • query : l’attribut le plus « complexe ». Lorsque la valeur spécifié par l’attribut index est un objet, cet attribut permet de spécifier le nom d’un champs (public, protected ou private) dont on veut capturer la valeur ou l’ensemble des champs permettant d’accéder à la valeur que l’on souhaite capturer (un peu comme un PropertyPath en WPF ou Silverlight mais sur des champs). Bien entendu la valeur finale doit être d’un des types spécifiés ci-dessus.

Comme pour les autres éléments, voici une représentation visuelle :

image

Concernant le cas particulier des valeurs de retour, comme indiqué ci-dessus pour pouvoir capturer la valeur de retour il faut ajouter un élément DataQuery avec un index de -1. Mais il faut aussi ajouter à l’élément Binding qui va contenir ce DataQuery l’attribut onReturn= »true ». Cela implique que vous ne pouvez pas capturer dans une même trace les paramètres d’entrés et les paramètres de sorties. Vous pouvez par contre regrouper les deux Binding dans un même élément DiagnosticEventSpecification :

<DiagnosticEventSpecification enabled="false">
  <CategoryId>test</CategoryId>
  <SettingsName _locID="test">Test</SettingsName>
  <SettingsDescription _locID="Test">Test input/output trace.</SettingsDescription>
  <Bindings>
    <Binding>
      <ModuleSpecificationId>test</ModuleSpecificationId>
      <TypeName>Test.TestClass</TypeName>
      <MethodName>Read</MethodName>
      <MethodId>Test.TestClass.Read(System.String):System.String</MethodId>
      <ShortDescription _locID="read">Reading {0}</ShortDescription>
      <LongDescription _locID="read">Trying to read "{0}".</LongDescription>
      <DataQueries>
        <DataQuery index="1" maxSize="256" type="String" _locID="test.String" _locAttrData="name" name="value" query="" />
      </DataQueries>
    </Binding>
    <Binding onReturn="true">
      <ModuleSpecificationId>test</ModuleSpecificationId>
      <TypeName>Test.TestClass</TypeName>
      <MethodName>Read</MethodName>
      <MethodId>Test.TestClass.Read(System.String):System.String</MethodId>
      <ShortDescription _locID="read.return">Read {0}</ShortDescription>
      <LongDescription _locID="read.return">Tried to read "{0}".</LongDescription>
      <DataQueries>
        <DataQuery index="-1" maxSize="256" type="String" _locID="test.String" _locAttrData="name" name="value" query="" />
      </DataQueries>
    </Binding>
  </Bindings>
</DiagnosticEventSpecification>

Voila pour la première méthode :) La deuxième méthode permet via l’implémentation de l’interface IProgrammableDataQuery d’aller un beaucoup plus loin lorsque l’on veut capturer des objets. Commençons par implémenter l’interface :

public class ColoredWriteLineDataQuery : IProgrammableDataQuery
{
    #region IProgrammableDataQuery Members

    public object[] EntryQuery(object thisArg, object[] args)
    {
        // thisArg contient l'instance de l'objet tracé
        // args contient les paramètres d'entrés

        // renvoi les paramètres d'entrés à prendre en compte pour le formattage
    }

    public object[] ExitQuery(object returnValue)
    {
        // returnValue contient la valeur de retour

        // renvoi les paramètre de sortie à prendre en compte pour le formattage
    }

    public List<CollectedValueTuple> FormatCollectedValues(object[] results)
    {
        // renvoi une liste décrivant les paramètres d'entrés ou de sorties
        // la liste contient des triplets de chaines de caractères (nom, valeur, type)
    }

    public string FormatLongDescription(object[] results)
    {
        // renvoi la description longue à partir des valeurs reçues
        // ces valeurs proviennent soit de EntryQuery soit de ExitQuery
    }

    public string FormatShortDescription(object[] results)
    {
        // renvoi la description courte à partir des valeurs reçues
        // ces valeurs proviennent soit de EntryQuery soit de ExitQuery
    }

    public List<Location> GetAlternateLocations(object[] results)
    {
        // je ne sais pas...
    }

    #endregion
}

Comme vous pouvez le voir on peut faire à peu près ce que l’on veut. Une fois cette classe créée il faut copier l’assembly la contenant dans le sous répertoire de VisualStudio « Team Tools\TraceDebugger Tools\ » et mettre à jour le fichier CollectionPlan.xml :

<DiagnosticEventSpecification enabled="false">
  <CategoryId>qetza.coloredconsole</CategoryId>
  <SettingsName _locID="settingsName.Qetza.ColoredConsole.WriteLine.DateTime">ColoredWriteLine (DateTime)</SettingsName>
  <SettingsDescription _locID="settingsName.Qetza.ColoredConsole.WriteLine.DateTime">Console Output with a colored DateTime passed in.</SettingsDescription>
  <Bindings>
    <Binding>
      <ModuleSpecificationId>qetza.consoleex.coloredconsole</ModuleSpecificationId>
      <TypeName>Qetza.ConsoleEx.ColoredConsole</TypeName>
      <MethodName>WriteLine</MethodName>
      <MethodId>Qetza.ConsoleEx.ColoredConsole.WriteLine(System.ConsoleColor,System.DateTime):System.Void</MethodId>
      <ProgrammableDataQuery>
        <ModuleName>Qetza.ConsoleEx.DataQueries.dll</ModuleName>
        <TypeName>Qetza.ConsoleEx.DataQueries.ColoredWriteLineDataQuery</TypeName>
      </ProgrammableDataQuery>
    </Binding>
  </Bindings>
</DiagnosticEventSpecification>

Dans le cas de l’utilisation de IProgrammableDataQuery on remplace l’élément DataQueries par l’élément ProgrammableDataQuery qui va contenir les informations nécessaire pour trouver la classe à utiliser :

  • ModuleName : le nom de l’assembly contenant l’implémentation de l’interface.
  • TypeName : le nom complet de la classe implémentant l’interface.

Pour les quelques courageux qui ont eu la patience de tout lire : bravo ! :)

Et voilà c’en ait finit de ce long (qui a dit très long ? :) ) billet sur comment personnaliser l’IntellTrace de VisualStudio 2010 afin de tracer ces propres méthodes. Une dernière petite remarque avant la fin, vous pouvez bien entendu spécifier plusieurs DiagnosticEventSpecification pour une même méthode, ils seront alors tous appelés.

Je tiens aussi à remercie Ian Huff de Microsoft pour son aide : Merci !

Carpe Diem.

 

Vous retrouverez la solution que j’ai utilisée pour ce billet à l’adresse http://blog.qetza.net/wp-content/uploads/2010/03/Qetza.IntelliTrace.zip.

4 thoughts on “VS 2010 : Personnalisation des évènements de l’IntelliTrace

  1. Pingback: setup custom IntelliTrace Events with ease

  2. Pingback: Greg Sohl » Blog Archive » IntelliTrace Resources

  3. Pingback: John Robbins' Blog : IntelliTrace: Making On Time Shipping Possible

  4. Pingback: VS 2010 : Custom graph dependency analyzer ← Guillaume Rouchon's Blog

Leave a Reply

  

  

  

CAPTCHA *