Home > View Post

Creating your own tokens

This is part 3 in a series introducing the Ukadc.Diagnostics project which is currently hosted on codeplex at www.codeplex.com/UkadcDiagnostics.

In the previous post we looked at using Ukadc.Diagnostics to control the output of a listener using tokens. Simple, but we're building up. In this post we'll look at how we can extend that token system by writing and using our own.

We're going to start by creating a token to read the name of the current process. In Ukadc.Diagnostics Tokens are just string representations of PropertyReaders. A PropertyReader is a class that knows how to retrieve a particular piece of information from a logging event. For example, the {Message} token is backed by a MessagePropertyReader and the {Id} token by an IdPropertyReader.

To access the name of the current process we're going to create a class that inherits from the abstract PropertyReader class. In fact, we'll go one step further and cheat. Because most property readers return strings there is an abstract StringPropertyReader class that makes creating a string based PropertyReader even easier:

using System;
using System.Diagnostics;
using Ukadc.Diagnostics.Utils.PropertyReaders;

public class ProcessNamePropertyReader : StringPropertyReader
{
    private static readonly string _processName = Process.GetCurrentProcess().ProcessName;

    public override bool TryGetValue(
        out object value,
        TraceEventCache cache,
        string source,
        TraceEventType eventType,
        int id,
        string formatOrMessage,
        object[] data)
    {
        value = _processName;
        return true;
    }
}

We use the System.Diagnostics.Process class to grab the name of the process (assuming FullTrust) and cache it on a static readonly field. PropertyReaders are the beating heart of the Ukadc.Diagnostics system and we'll refer to them continually in the coming posts.

In System.Diagnostics, both TraceListeners and TraceFilters have a very similar set of methods that look a lot like PropertyReader's TryGetValue. This means that a TraceListener or TraceFilter can hand off to a PropertyReader and rely on it to extract the appropriate data.

As in the example above, it isn't actually necessary for the PropertyReader to read its data from the parameters passed to TryGetValue. For example, the built-in ActivityIdPropertyReader (token = {ActivityId}) actually retrieves the value from the Trace.CorrelationManager.ActivityId static property [1].

Next we need to register our PropertyReader as a token with Ukadc.Diagnostics. To do this we'll need to add the ukadc.diagnostics configuration section to our app.config file. This is easy enough:

<configuration>
    <configSections>
        <section name="ukadc.diagnostics" type="Ukadc.Diagnostics.Configuration.UkadcDiagnosticsSection, Ukadc.Diagnostics"/>
    </configSections>
    <ukadc.diagnostics>
        <tokens>
            <token name="{ProcessName}"
                type="ExampleConsoleApplication.ProcessNamePropertyReader, ExampleConsoleApplication" />
        </tokens>
    </ukadc.diagnostics>

Note how we've also specified a new {ProcessName} token. Meanwhile, back in the System.Diagnostics section of the configuration file, let's see it being used in our ConsoleTraceListener

    <system.diagnostics>
        <sources>
            <source name="primes" switchValue="All">
                <listeners>
                    <add
                    name="console"
                    type="Ukadc.Diagnostics.Listeners.ConsoleTraceListener, Ukadc.Diagnostics"
                    initializeData="{ProcessName}: {Message}" />
                </listeners>
            </source>
        </sources>dd
    </system.diagnostics>
</configuration>

Console output with ProcessName

Not a particularly exciting example, but useful all the same (especially when your logging 'repository' might be receiving input from many processes!). Let's create another PropertyReader that will be a little more dynamic in its output (and doesn't return a string).

For this example we're going to use a PerformanceCounter to tell us how many Generation 0 garbage collections there have been. Crazy! I know!

In this case, the PropertyReader will return a float so we can't use the StringPropertyReader base class and have to put in the extra leg work required to implement a PropertyReader. It's pretty easy though:

public class Gen0CollectionsPropertyReader : PropertyReader
{
    private static readonly PerformanceCounter _counter =
        new PerformanceCounter(".NET CLR Memory", "# Gen 0 Collections", Process.GetCurrentProcess().ProcessName);

    public override IComparator Comparator
    {
        get { return NumericComparator.Instance; }
    }

    public override Type PropertyType
    {
        get { return typeof (float); }
    }

    public override bool TryGetValue(
        out object value,
        TraceEventCache cache,
        string source,
        TraceEventType eventType,
        int id,
        string formatOrMessage,
        object[] data)
    {
        value = _counter.NextValue();
        return true;
    }
}

Firstly, we initialize a static PerformanceCounter that will be used to read the number of Gen0 collections (again, assuming we have the appropriate permissions and note, this operation is slow so we only want to do it once). Then we override the Comparator property. The implementation of IComparator returned by this getter is used by the PropertyFilter which we'll look at in a later post. For now there's a simple rule to follow: for strings use the StringComparator (or just inherit from StringPropertyReader) and for everything else, use the NumericComparator.

Next we override PropertyType which simply returns the System.Type that the PropertyReader will return. In this case, it's a float.

Finally, we override TryGetValue as described above and, in this case, set the out value to _counter.NextValue(). In the examples we've looked at today we always assume that the Property Read is going to be successful so we just return true.

Now to configure the token:

<ukadc.diagnostics>
    <tokens>
        <token name="{ProcessName}"
            type="ExampleConsoleApplication.ProcessNamePropertyReader, ExampleConsoleApplication" />
        <token name="{Gen0Collections}"
            type="ExampleConsoleApplication.Gen0CollectionsPropertyReader, ExampleConsoleApplication" />
    </tokens>
</ukadc.diagnostics>

And the output looks like this (you should be able to guess what changes were made to the initializeData attribute of the ConsoleTraceListner by now)...

Console output with Generation 0 collections

Note: I'm not recommending this particular PropertyReader as a good example. You're almost certainly better off attaching perfmon to record this kind of data. But it does go some way to demonstrating what's possible with a property reader. There are plenty of other examples that spring to mind that you might want to implement. For example, if you're constantly worried about the number of available threads in the threadpool you could create a PropertyReader to retrieve this information or maybe you'd like to retrieve something from HttpContext.Current in a web application. Also, this would be a good example of where TryGetValue might return false - when HttpContext.Current is null for example.

Anyway, that's a lightning tour of PropertyReaders in Ukadc.Diagnostics - you can read more on the project site: PropertyReader, The DynamicPropertyReader and Tokens Next we'll be leaving the boring old ConsoleTraceListener behind and looking at some more interesting things you can do with your logging data.

[1] This is fine, provided some assumptions can be made; in the case of the ActivityId the PropertyReader must be called on the same thread as the log event. In the case of our new ProcessNamePropertyReader we assume it will always be called inside the same process as the log event.

 
Josh Post By Josh Twist
11:51 PM
29 Apr 2008

» Next Post: Using the SqlTraceListener
« Previous Post: Using tokens in Ukadc.Diagnostics to control output

Comments are closed for this post.

© 2005 - 2017 Josh Twist - All Rights Reserved.