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.
public static class TraceSourceNames
{
public const string Source1 = "TS1";
public const string Source2 = "TS2";
}
Then when I create a new instance, I could have:
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
.
public class TraceSources
{
ConcurrentDictionary<string, TraceSource> dic;
TraceSources()
{
dic = new ConcurrentDictionary<string, TraceSource>();
}
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:
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.
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:
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.
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:
MyAppTraceSources.Logic1Source.TraceWarning("Logic1 warning.");
Benefits
- Shorter application codes.
- 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.
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:
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.
<system.diagnostics>
<sources>
<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>
<add name="SourceSwitch" value="Verbose" />
</switches>
<sharedListeners>
<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>
<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