VS 2010 : Customize IntelliTrace events 8

VS 2010 : Customize IntelliTrace events

Today i’ll show you how to customize IntelliTrace in VisualStudio 2010 so that you can trace events from your own methods. But first some warnings (in bold and red so no one misses them :) ):

Microsoft does not give any documentation or support on all that i will explain. There is also a chance that the way you can  customize IntelliTrace will be revamp in the next version of VisualStudio (which might explain the lack of documentation and support).

Now that you’re warned let start and see how we can customize IntellTrace so that we can trace our own method calls! I’ll be using the following class for this post:

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;
    }
}

This class can write a line to the Console with a specified color. We will now see how to obtain the following result in IntelliTrace:

image.png

As you can see IntelliTrace captured the calls to the methods of the ColoredConsole class with their entry parameters (ex: Console Output “3/7/2010 12:09:14 PM” in DarkBlue).

To customize the IntelliTrace events you must modify the file CollectionPlan.xml which is located in the VisualStudio’s sub directory “Team Tools\TraceDebugger Tools\en”. This xml file contains a list of events (methods) which IntelliTrace should trace. The first to do is to declare a new category for events. This is done by adding a Category element under 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>

This file is only load once during VisualStudio startup so after each modification you’ll have to restart VisualStudio so that they can be taked. When VisualStudio is restared after the previous modification you should see your new category “ColoredConsole” in the IntelliTrace options:

image.png

We will now have to fill this category. We will first have to declare a module (an assembly which IntelliTrace will look into to find events). Let’s add our assembly to the existing list under the element TracePointProvider/ModulesSepcifications:

<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>

The identifier is mandatory and you can have two optionnal attributes minVersion and/or maxVersion which can distinguish a same assembly based on it’s version.

Now that we have declare our module we must specify which methods we would like to trace. This is done using for each method the DiagnosticEventSpecification element under the element TracePointProvider/DiagnosticEventSpecifications:

<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>

A litte explanation might be usefull :)

  • The enabled attribute on the DiagnosticEventSpecification element specifies if this event should be traced by default.
  • The CategoryId element contains the category identifier under which this event should be displayed.
  • The SettingsName element specifies the event name as it will be displayed under the category in the IntelliTrace options.
  • The SettingsDescription element specifies the description text which will appear in the event name tooltip.
  • The Bindings element will contains all the binding which should be setup when the event is enabled. Each binding is describe using a Binding element. This enables you to activate the trace of more than one method with a single event by specifing more than one Binding element.
  • The ModuleSpecificationId element specifies in which module the method to trace should be found.
  • The TypeName element contains the full type name containing the method to trace (this one is obvious).
  • The MethodeName element contains the method name to trace (this one is obvious too).
  • The MethodId element contains the method identifer and should have the following format : <class full type>.<method name>(<parameters full type names separated by commas>):<return value full type>
  • The ShortDescription element contains the text which will be displayed on the first line of the IntelliTrace captured event right after the category name. This string can contains markers as in the String.Format method. The values will be the values queried using the DataQuery elements declared after in the CollectionPlan.xml file.
  • The LongDescription element contains the text which will be displayed on the second line of the captured IntelliTrace event. As for the ShortDescription this text can contains markers.

Before looking at the end of the example let’s see visually the previous elements:

image.png

With what we already seen you can start customizing IntelliTrace for your own methods. We will now see how to capture values on method calls. There is two methods:

  • The first one is to declare a DataQueries element with DataQuery sub elements in the CollectionPlan.xml file under the corresponding Binding element.
  • the second one is to write a class which will implement the IProgrammableDataQuery interface which is declared in the Microsoft.VisualStudio.IntelliTrace.dll assembly.

Of course those methods aren’t equivalent. The first one is purely declarative, thus simplier, but can only capture values of the following type:

  • Boolean
  • Char
  • Double
  • Single (float)
  • Int16
  • UInt16
  • Int32
  • UInt32
  • Int64
  • UInt64
  • String

To capture the value of one of this type you’ll have to declare a DataQuery element. This element accepts the following attributes:

  • index : This attribute is mandatory and:
    • If it’s value is –1 it indicates that the source of the value is the return value.
    • If it’s value is 0 it indicates that the source is the current instance for an instance method. If the method is a static method, it indicates that the source is the first parameter of the method.
    • If it’s 1 it indicates that the source is the first parameter for an instance method. For a static method it indicates the second parameter.
    • And it continues for the other method parameters.
  • maxSize : when the value is a string it represents the size to capture. This attribute is mandatory for string values.
  • type : this mandatory attribute specified the value type. The value must be one of the types specified previously.
  • name : This is the name which will be given to the value so that you can reconize it easily.
  • query : This “complex” attribute specifies the name of a field (public, protected or private) to capture it’s value for sources which are objects. Like for PropertyPath in WPF or Silverlight this attribut can represent a field path by separating the fields with dots. Of course, the final value must be of one of the previously specified type.

Let’s see the result visually:

image.png

For return values, as i explained you can capture them using a DataQuery element with an index attribute of –1. But you also need to specify the following attribute on the containing Binding element: onReturn=”true”. This implies that you cannot capture input and outputs parameters in the same trace but you can regroup the two Binding elements under the same DiagnosticEventSpecification element:

<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>

 

Here’s for the first method :) The second method will allow us to do much more by using the IProgrammableDataQuery interface. Let’s first look at this interface:

public class ColoredWriteLineDataQuery : IProgrammableDataQuery
{
    #region IProgrammableDataQuery Members

    public object[] EntryQuery(object thisArg, object[] args)
    {
        // thisArg contains the traced instance
        // args contains the input parameters

        // returns the inputs parameters to use for formatting strings
    }

    public object[] ExitQuery(object returnValue)
    {
        // returnValue contains the return value

        // returns output parameters to use for formatting string
    }

    public List<CollectedValueTuple> FormatCollectedValues(object[] results)
    {
        // returns a list of tupples describing the input or output parameters
        // the list contains tuples of string (name, value, type)
    }

    public string FormatLongDescription(object[] results)
    {
        // returns the long description using the parameters values
        // those values comes from EntryQuery or ExitQuery
    }

    public string FormatShortDescription(object[] results)
    {
        // returns the short description using the parameters values
        // those values comes from EntryQuery or ExitQuery
    }

    public List<Location> GetAlternateLocations(object[] results)
    {
        // don't know yet...
    }

    #endregion
}

As you can see you can do pretty much anything with the input and output parameters. When this class is created you’ll have to copy the assembly to VisualStudio sub directory “Team Tools\TraceDebugger Tools\” and update the CollectionPlan.xml file:

<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>

When using IProgrammableDataQuery you replace the DataQueries element with a ProgrammableDataQuery element which will contains all the necessary informations:

  • ModuleName : the assembly name containing the interface implementation(s).
  • TypeName : the class full type name.

For all those which had the patience to read until here: bravo ! :)

And now it’s the end of this long (who said extremly long ? :) ) post about customizing VisualStudio’s IntelliTrace to trace your own methods. One last remark before ending this post, you can specify multiple DiagnosticEventSpecification elements for the same method, they will all be called.

I’d like to thanks Ian Huff of Microsoft for his help: Thanks!

 

Carpe Diem.

 

You can find the solution i used for this post at the following url http://blog.qetza.net/wp-content/uploads/2010/03/Qetza.IntelliTrace.zip.

8 thoughts on “VS 2010 : Customize IntelliTrace events

  1. Reply Julien Brunet Jul 2,2010 01:41

    Hi Guillaume,

    based on your fascinating blog post, i have built this tiny fx/regutil exe to automate the update of CollectionPlan.xml. Take a look if you have some time..

    http://www.topian.net/post/2010/07/01/setup-custom-IntelliTrace-Events-with-ease.aspx

  2. Reply Guillaume Rouchon Jul 23,2010 17:26

    Hi Julien, i’m glad like my post. Your project looks great, i’ll try it on some personal projects :)

  3. Reply Rajendra Mar 27,2014 08:08

    Hi Guillaume,

    Wow ! this is grate article. Its posted in 2010 and even today I don’t find anything as useful as this on collectionplan.xml.

    BTW Can you guild me on how to record value of local variable ?

  4. Pingback: Customize IntelliTrace to Collect Enums – Not Defined

  5. Reply Alex Mar 10,2017 11:05

    Hi! Thanks for a good intro. I tried following the guide but it doesn’t seem to work. Did you need to copy the dll containing the methods anywhere? or would it just work if the project was the same dll?

  6. Reply Guillaume Rouchon May 8,2017 11:28

    Hi Alex,
    When doing this i didn’t copy the dll anywhere specific but this was with Visual Studio 2010 and i didn’t try to do any test with a newer version of VS. Which version are you using?

  7. Reply Juliano Goncalves Jul 17,2021 00:42

    Very interesting and thorough post. Do you know if there is a more modern approach for performing these customizations in later versions of Visual Studio?

    Say, for example, that I have a library project that sets and gets ambient/contextual data. Now, I want these gets and sets to appear as intellitrace events everytime I’m debugging my solution. However, I’d like to have these settings shared with the other developers in the company, so that everybody can see the events.

    Is it possible to have this customization at the solution level somehow, perhaps by adding a xml configuration file to the project and making Visual Studio load it automatically? Or perhaps there is something using NuGet packages, like analyzers work?

  8. Reply Guillaume Rouchon Jul 19,2021 09:07

    Hi Juliano,
    Thanks for the comment. It’s been a long time i didn’t look into intellitrace and by doing a quick search there seems that no features were added to simplify/extend the extensibility :(
    One suggestion would be to use DiagnosticSource and Activity to instrument your code (https://docs.microsoft.com/en-us/dotnet/core/diagnostics/distributed-tracing).

Leave a Reply to Alex Cancel Reply

  

  

  

CAPTCHA *