Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / C#

Use TraceSource Efficiently

4.60/5 (4 votes)
28 Aug 2016CPOL4 min read 34.2K  
Create and use System.Diagnostics.TraceSource efficiently

Introduction

These tips introduce a few practices that had not been commonly introduced in various MSDN documentation and tutorials that you could find on the Internet. Such practices may improve the maintainability of application codes when you need a lot of tracing in codes.

Background

System.Diagnostics has been around since .NET 1 which had not been sufficient enough for tracing and logging, so here came NLog and Log4Net, etc. However, since .NET 2 in year 2006, the features of System.Diagnostics had surpassed those logging components started during the .NET 1 age. Unfortunately, nowadays, 10 years after the introduction of .NET 2, many .NET developers are ignoring what is offered in System.Diagnostics and continue to use NLog and Log4Net in green fields projects, or use only what is available in .NET 1.

Here are 2 good articles giving you some overview about how System.Diagnostics could impact on our developers' productivity.

Create TraceSource

In my VS sln, I generally would have a CS project that contain most (if not all) basic types, enum types, and constants shareable across projects of the same sln.

C#
public static class TraceSourceNames
{
    public const string Source1 = "TS1";
    public const string Source2 = "TS2";
}

Then when I create a new instance, I could have:

C#
var ts1 = new TraceSource(TraceSourceNames.Source1);

This makes it easier to locate all trace source statements by the same trace source name through the "Find All References" of the context menu of VS IDE.

Obviously, creating TraceSource instances with the same name everywhere is working, and is not a bad practice, however is not always looking nice, and things may be more troublesome when your codes conform to the Single Responsibility Principle, and there will may be a lot of TraceSource constructions in classes. It may be better that an application/component/library or appDomain has only one TraceSource instance per name. This is a design decision upfront that your team has to have agreement.

TraceSources

This is a singleton class maintaining a dictionary of trace source names and TraceSource instances. Through lazy instantiation, you will have one TraceSource instance per name in an AppDomain.

C#
/// <summary>
/// Store a dictionary of TraceSource objects.
/// </summary>
public class TraceSources
{
    ConcurrentDictionary<string, TraceSource> dic;

    TraceSources()
    {
        dic = new ConcurrentDictionary<string, TraceSource>();
    }

    /// <summary>
    /// Create a TraceSource in lazy way.
    /// </summary>
    /// <param name="name"></param>
    /// <returns></returns>
    public TraceSource GetTraceSource(string name)
    {
        TraceSource r;
        if (dic.TryGetValue(name, out r))
            return r;

        r = new TraceSource(name);
        dic.TryAdd(name, r);
        return r;
    }

    public TraceSource this[string name]
    {
        get
        {
            return GetTraceSource(name);
        }
    }

    public static TraceSources Instance { get { return Nested.instance; } }

    private static class Nested
    {
        static Nested()
        {
        }

        internal static readonly TraceSources instance = new TraceSources();
    }
}

And you may write a trace like this:

C#
TraceSources.Instance["MySource"].TraceEvent(...)

A TraceSource object named "MySource" is created the first time when TraceSources is referenced by key "MySource".

Remarks

It is pretty fine to create a new instance of TraceSource with the same name every time you need to trace. The singleton dictionary just saves you 1 or 2 lines of codes: declaring the trace source variable, and creating an instance. Both approaches are equally good in performance.

TraceSource Without Explicit Literal Name

TraceSource has been widely used inside .NET Framework since .NET 2, for example, in Network Tracing and WPF Tracing. If you want to trace in your application in the same style, you may create a static helper class in a shared assembly across application assemblies.

C#
public static class MyAppTraceSources
{
    public static TraceSource Source(string name)
    {
        return TraceSources.Instance.GetTraceSource(name);
    }

    public static TraceSource Logic1Source
    {
        get
        {
            return TraceSources.Instance.GetTraceSource("Logic1");
        }
    }

    public static TraceSource Logic2Source
    {
        get
        {
            return TraceSources.Instance.GetTraceSource("Logic2");
        }
    }
}

So you may write a trace like this:

C#
MyAppTraceSources.Logic1Source.TraceEvent(...);

Use TraceSource like Trace

TraceSource is more powerful and flexible than Trace, however, when writing traces, you may not always care about the event Id, and a short handed expression like mySource.TraceWarning(myMessage) may be looking better than mySource.TraceEvent(TraceEventType.Warning, 0, myMessage). And this can be done through extension.

C#
    public static class TraceSourceExtension
    {

        public static void TraceWarning(this TraceSource traceSource, string message)
        {
            traceSource.TraceEvent(TraceEventType.Warning, 0, message);
        }

        public static void TraceWarning(this TraceSource traceSource, string format, params object[] args)
        {
            traceSource.TraceEvent(TraceEventType.Warning, 0, format, args);
        }


        public static void TraceError(this TraceSource traceSource, string message)
        {
            traceSource.TraceEvent(TraceEventType.Error, 0, message);
        }

        public static void TraceError(this TraceSource traceSource, string format, params object[] args)
        {
            traceSource.TraceEvent(TraceEventType.Error, 0, format, args);
        }

        public static void TraceInformation(this TraceSource traceSource, 
                                            string format, params object[] args)
        {
            traceSource.TraceInformation(format, args);
        }

        public static void TraceInformation(this TraceSource traceSource, string message)
        {
            traceSource.TraceInformation(message);
        }

        public static void WriteLine(this TraceSource traceSource, string message)
        {
            traceSource.TraceEvent(TraceEventType.Verbose, 0, message);
        }

        public static void WriteLine(this TraceSource traceSource, string format, params object[] args)
        {
            traceSource.TraceEvent(TraceEventType.Verbose, 0, format, args);
        }

        public static void TraceData
        (this TraceSource traceSource, TraceEventType eventType, int id, params object[] data)
        {
            traceSource.TraceData(eventType, id, data);
        }

        public static void TraceData
        (this TraceSource traceSource, TraceEventType eventType, int id, object data)
        {
            traceSource.TraceData(eventType, id, data);
        }

        public static void TraceEvent
        (this TraceSource traceSource, TraceEventType eventType, int id)
        {
            traceSource.TraceEvent(eventType, id);
        }

        public static void TraceEvent
        (this TraceSource traceSource, TraceEventType eventType, int id, string message)
        {
            traceSource.TraceEvent(eventType, id, message);
        }

        public static void TraceEvent
        (this TraceSource traceSource, TraceEventType eventType, 
		int id, string format, params object[] args)
        {
            traceSource.TraceEvent(eventType, id, format, args);
        }

        public static void TraceTransfer
        (this TraceSource traceSource, int id, string message, Guid relatedActivityId)
        {
            traceSource.TraceTransfer(id, message, relatedActivityId);
        }

    }

Then you can write a trace like this:

C#
MyAppTraceSources.Logic1Source.TraceWarning("Logic1 warning.");

Benefits

  1. Shorter application codes.
  2. If you have legacy application with a lot of Trace.TraceWarning() statements and alike, and you want to replace some if not all with distinct TraceSource objects, this extension makes it easier to refactor the legacy codes.

Create a TraceSource for Each Application Assembly

Sometimes, your intention of distinguishing trace sources may be well mapped to the names of application assemblies. While you could explicitly define a trace source name after an assembly, it will be even better if such trace source name could be automatically defined when calling TraceEvent for the first time within the assembly.

C#
/// <summary>
/// Output trace info using a TraceSource object named after the calling assembly
/// </summary>
public static class AssemblyTrace
{
    [System.Runtime.CompilerServices.MethodImplAttribute
    (System.Runtime.CompilerServices.MethodImplOptions.NoInlining)]
    public static void TraceWarning(string message)
    {
        var assemblyName = System.Reflection.Assembly.GetCallingAssembly().GetName().Name;
        TraceSources.Instance.GetTraceSource(assemblyName).TraceEvent
        (TraceEventType.Warning, 0, message);
    }

    [System.Runtime.CompilerServices.MethodImplAttribute
    (System.Runtime.CompilerServices.MethodImplOptions.NoInlining)]
    public static void TraceWarning(string format, params object[] args)
    {
        var assemblyName = System.Reflection.Assembly.GetCallingAssembly().GetName().Name;
        TraceSources.Instance.GetTraceSource
        (assemblyName).TraceEvent(TraceEventType.Warning, 0, format, args);
    }

    [System.Runtime.CompilerServices.MethodImplAttribute
    (System.Runtime.CompilerServices.MethodImplOptions.NoInlining)]
    public static void TraceError(string message)
    {
        var assemblyName = System.Reflection.Assembly.GetCallingAssembly().GetName().Name;
        TraceSources.Instance.GetTraceSource(assemblyName).TraceEvent
    (TraceEventType.Error, 0, message);
    }

    [System.Runtime.CompilerServices.MethodImplAttribute
    (System.Runtime.CompilerServices.MethodImplOptions.NoInlining)]
    public static void TraceError(string format, params object[] args)
    {
        var assemblyName = System.Reflection.Assembly.GetCallingAssembly().GetName().Name;
        TraceSources.Instance.GetTraceSource(assemblyName).TraceEvent
        (TraceEventType.Error, 0, format, args);
    }

    [System.Runtime.CompilerServices.MethodImplAttribute
    (System.Runtime.CompilerServices.MethodImplOptions.NoInlining)]
    public static void TraceInformation(string format, params object[] args)
    {
        var assemblyName = System.Reflection.Assembly.GetCallingAssembly().GetName().Name;
        TraceSources.Instance.GetTraceSource(assemblyName).TraceInformation(format, args);
    }

    [System.Runtime.CompilerServices.MethodImplAttribute
    (System.Runtime.CompilerServices.MethodImplOptions.NoInlining)]
    public static void TraceInformation(string message)
    {
        var assemblyName = System.Reflection.Assembly.GetCallingAssembly().GetName().Name;
        TraceSources.Instance.GetTraceSource(assemblyName).TraceInformation(message);
    }

    [System.Runtime.CompilerServices.MethodImplAttribute
    (System.Runtime.CompilerServices.MethodImplOptions.NoInlining)]
    public static void WriteLine(string message)
    {
        var assemblyName = System.Reflection.Assembly.GetCallingAssembly().GetName().Name;
        TraceSources.Instance.GetTraceSource
        (assemblyName).TraceEvent(TraceEventType.Verbose, 0, message);
    }

    [System.Runtime.CompilerServices.MethodImplAttribute
    (System.Runtime.CompilerServices.MethodImplOptions.NoInlining)]
    public static void WriteLine(string format, params object[] args)
    {
        var assemblyName = System.Reflection.Assembly.GetCallingAssembly().GetName().Name;
        TraceSources.Instance.GetTraceSource
        (assemblyName).TraceEvent(TraceEventType.Verbose, 0, format, args);
    }

    [System.Runtime.CompilerServices.MethodImplAttribute
    (System.Runtime.CompilerServices.MethodImplOptions.NoInlining)]
    public static void TraceData(TraceEventType eventType, int id, params object[] data)
    {
        var assemblyName = System.Reflection.Assembly.GetCallingAssembly().GetName().Name;
        TraceSources.Instance.GetTraceSource(assemblyName).TraceData(eventType, id, data);
    }

    [System.Runtime.CompilerServices.MethodImplAttribute
    (System.Runtime.CompilerServices.MethodImplOptions.NoInlining)]
    public static void TraceData(TraceEventType eventType, int id, object data)
    {
        var assemblyName = System.Reflection.Assembly.GetCallingAssembly().GetName().Name;
        TraceSources.Instance.GetTraceSource(assemblyName).TraceData(eventType, id, data);
    }

    [System.Runtime.CompilerServices.MethodImplAttribute
    (System.Runtime.CompilerServices.MethodImplOptions.NoInlining)]
    public static void TraceEvent(TraceEventType eventType, int id)
    {
        var assemblyName = System.Reflection.Assembly.GetCallingAssembly().GetName().Name;
        TraceSources.Instance.GetTraceSource(assemblyName).TraceEvent(eventType, id);
    }

    [System.Runtime.CompilerServices.MethodImplAttribute
    (System.Runtime.CompilerServices.MethodImplOptions.NoInlining)]
    public static void TraceEvent(TraceEventType eventType, int id, string message)
    {
        var assemblyName = System.Reflection.Assembly.GetCallingAssembly().GetName().Name;
        TraceSources.Instance.GetTraceSource(assemblyName).TraceEvent(eventType, id, message);
    }

    [System.Runtime.CompilerServices.MethodImplAttribute
    (System.Runtime.CompilerServices.MethodImplOptions.NoInlining)]
    public static void TraceEvent(TraceEventType eventType, int id,
        string format, params object[] args)
    {
        var assemblyName = System.Reflection.Assembly.GetCallingAssembly().GetName().Name;
        TraceSources.Instance.GetTraceSource(assemblyName).TraceEvent(eventType, id, format, args);
    }

    [System.Runtime.CompilerServices.MethodImplAttribute
    (System.Runtime.CompilerServices.MethodImplOptions.NoInlining)]
    public static void TraceTransfer(int id, string message, Guid relatedActivityId)
    {
        var assemblyName = System.Reflection.Assembly.GetCallingAssembly().GetName().Name;
        TraceSources.Instance.GetTraceSource(assemblyName).TraceTransfer
        (id, message, relatedActivityId);
    }

    public static SourceSwitch Switch
    {
        [System.Runtime.CompilerServices.MethodImplAttribute
        (System.Runtime.CompilerServices.MethodImplOptions.NoInlining)]
        get
        {
            var assemblyName = System.Reflection.Assembly.GetCallingAssembly().GetName().Name;
            return TraceSources.Instance.GetTraceSource(assemblyName).Switch;
        }

        [System.Runtime.CompilerServices.MethodImplAttribute
        (System.Runtime.CompilerServices.MethodImplOptions.NoInlining)]
        set
        {
            var assemblyName = System.Reflection.Assembly.GetCallingAssembly().GetName().Name;
            TraceSources.Instance.GetTraceSource(assemblyName).Switch = value;
        }
    }

    public static TraceListenerCollection Listeners
    {
        [System.Runtime.CompilerServices.MethodImplAttribute
        (System.Runtime.CompilerServices.MethodImplOptions.NoInlining)]
        get
        {
            var assemblyName = System.Reflection.Assembly.GetCallingAssembly().GetName().Name;
            return TraceSources.Instance.GetTraceSource(assemblyName).Listeners;
        }
    }
}

So you may be writing:

C#
AssemblyTrace.WriteLine("I would be OK");

and the trace source name will be the assembly name of the assembly executing the WriteLine() statement.

Configuration

Generally, it is preferable to configure in app.config rather than in codes, since the app.config will be processed before the first line of your application codes is executed. This is especially important for the functionality of System.Diagnostics, since there may be some errors occurring during startup before the first line of application codes is executed.

XML
<system.diagnostics>
  <sources>
    <!-- The TraceSource used is named Application. TooltipListener is added inside the application-->
    <source name="TestWpfApplication"
    switchName="SourceSwitch" switchType="System.Diagnostics.SourceSwitch">
      <listeners>
        <clear />
        <add name="rollingfile" />
      </listeners>
    </source>
    <source name="DemoLib1" switchName="SourceSwitch"
    switchType="System.Diagnostics.SourceSwitch">
      <listeners>
        <clear />
        <add name="rollingfile" />
      </listeners>
    </source>
    <source name="MySource" switchName="SourceSwitch"
    switchType="System.Diagnostics.SourceSwitch">
      <listeners>
        <clear />
        <add name="rollingfile" />
      </listeners>
    </source>

    <source name="Logic1" switchName="SourceSwitch"
    switchType="System.Diagnostics.SourceSwitch">
      <listeners>
        <clear />
        <add name="rollingfile" />
      </listeners>
    </source>

  </sources>
  <switches>
    <!-- You can set the level at which tracing is to occur. In production, Warning may be desired -->
    <add name="SourceSwitch" value="Verbose" />
  </switches>
  <sharedListeners>
    <!--*** Write to daily rollover log files.-->
    <add name="rollingfile" type="Essential.Diagnostics.RollingFileTraceListener,
    Essential.Diagnostics" template="{LocalDateTime:HH':'mm':'ss}
    [{Thread}] {EventType} {Source}: {Message}{Data}"
    initializeData="c:\logs\TestSharedLib-{DateTime:yyyy-MM-dd}.log" />
  </sharedListeners>
  <!--This will handle Debug and Trace output.-->
  <trace autoflush="true">
    <listeners>
      <add name="rollingfile" />
    </listeners>
  </trace>
</system.diagnostics>

Points of Interests

Having TraceSource objects being buffered in a singleton dictionary surely makes the objects long live. However, not likely you will have hundreds or thousands of named trace sources in your application. So such overhead against GC is insignificant.

In ASP.NET SignalR under the System.Diagnostics namespace, there is class TraceSourceExtensions, so you may use TraceSource like Trace, as introduced above, however, your application may not want to depend on ASP.NET or SignalR.

Use the Codes

You may copy/paste the codes listed in this article to your C# projects.

Or you may check out the git repository and build.

Or you may import NuGet package Fonlow.Diagnostics.

References

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)