Instrumentation in the .NET framework

Lesson 1: Logging

Windows Event Log



Creating and Deleting EventLog

Use the EventLog class. For one or two actions use its static methods, otherwise create an instance and perform actions on this object where possible.

To use EventLog class must specify source property and message, e.g.

EventLog demoLog = new EventLog("Chap10Demo");

demoLog.Source = "Chap10Demo";

demoLog.WriteEntry("CreateEventLog called", EventLogEntryType.Information);

To delete an event log...


Writing to EventLog

Ten overloads of WriteEntry.

Sometimes want to write to Application or System logs so that administrators will see important events (e.g. sql injection attempts). To do this...

EventLog demoLog = new EventLog("Application");

demoLog.Source = "DemoApp";

demoLog.WriteEntry("Write to App log", EventLogEntryType.Information);

To write to system log...

EventLog demoLog = new EventLog("System");

demoLog.Source = "DemoApp";

demoLog.WriteEntry("DemoApp restart due to reboot", EventLogEntryType.Information);

Note, if event source (e.g. DemoApp) has already been registered with one event log (e.g. The Application log) then the code to write to the System event log will instead write to the Application log. An event source can be only registered once and only to a single event log.

Reading from Event Log

The EventLog object has an Entries property (of type EventLogEntryCollection) that allows iteration over event log entries.

foreach(EventLogEntry DemoEntry in DemoLog.Entries)

Clearing Event Log

Call Clear method.

Lesson 2: Debugging and Tracing

Debugger Class

Enables communication with a debugger application, key methods:

e.g. To use Break method to conditionally break when a null string encountered...

String myMessage = ReturnMessage();

if (MyMessage == null)

For the Debugger or Debug class to function the build must be in debug mode.

When using Log method its output is directed to whatever listener objects are attached to the debugger. To create a listener...


DefaultTraceListener myListener = new DefaultTraceListener();


Debugger.Log(1, "Test", "This is a test");


Debug class

Debugger class only really provides two methods - Break and Log. More granularity is provided by Debug class. Commonly used methods:

Debug Attributes

[DebuggerDisplay("Name = {_companyName}, State = {_companyState}, City = {_companyCity}")]

will result in the following displaying in a watch window:

Name = "A co", State = "FL", City = "Miami"

in place of the usual class name


Trace class very similar to Debug, but is implemented in both Debug and Release builds.

TraceSource class provides mechanism to trace code execution as well as associating trace messages with their source. Use as follows:

ConsoleTraceListener consoleTracer = new ConsoleTraceListener();
  1. Declare instance of TraceSource
  2. Name the TraceSource
TraceSource DemoTrace = new TraceSource(  "DemoApp");
  1. Call TraceSource methods
DemoTrace.Switch = new SourceSwitch("DemoApp.Switch", "Information");

DemoTrace.TraceInformation("Before write...");

TraceSwitch class dynamically alters behaviour of Trace. Can alter behaviour when calling TraceError, TraceWarning and TraceInfo methods. Can also specify verbosity levels.

Listener Objects

Both Debug and Trace class depend on Listeners to display their output. One, or more, listeners can be added to the Debug and Trace classes.


Can be added manually:

Trace.Listeners.Add(new DefaultTraceListener());

Trace.WriteLine("A test");

or via Configuration file entry:

<trace autoflush="true" indentsize="5" />


Directs output to a text file or stream. Can be enabled by code or configuration file, e.g.

Trace.Listeners.Add(new TextWriterTraceListener(@"C:\output.txt"));

Trace.WriteLine("A test");


Forwards Debug or Trace output to a TextWriter or Stream. Operates in similar fashion to TextWriterTraceListener, but also records other items of information such as call stack, machine name, time stamp, etc.


Directs output to the event log.


Similar to TextWriterTraceListener, except it takes a delimited token and separates output by this token.

Lesson 3: Monitoring Performance


A process owns one or more operating system threads. Can also open private virtual address space that can only be managed by its own threads.

The Process class can reference operating-system processes on either local or remote machines.

Supports four static methods to associate class with process.

Performance Counters

To use performance counters the application must be granted PerformanceCounterPermission. Only grant if compelling reason to do so, should not be granted to assemblies running in a partial trust context. If the permission is granted an assembly can start and manipulate other system processes.

Allow for collection of data in clean, oo fashion. Values written to PerformanceCounter are stored in Windows Registry and so are dependent on application running on Windows platform.

Can be used either through code or via the PerformanceCounter component which allows developers to visually control the PerformanceCounter at design time.

PerformanceCounters are similar to file paths. Properties identifying a PerformanceCounter are:

Windows has many built-in counters, others will vary depending on what applications are installed. Be aware of what counters are available to avoid duplicating functionality available elsewhere.

CounterCreationData class

Container that holds properties needed to create PerformanceCounter object.

To create either create new instance of object and set each property manually, or pass values into overloaded constructor.

PerformanceCounterCategory class

Manage and manipulate PerformanceCounter objects.

PerformanceCounterCategory demoCategory = new PerformanceCounterCategory("DemoCategory");

if (!PerformanceCounterCategory.Exists("DemoCategory"))
PerformanceCounterCategory.Create("DemoCategory", "Training Demo Category", PerformanceCounterCategoryType.SingleInstance, "DemoCounter", "Training Counter Demo")l

Starting Processes

For simple processes only need to call Start method on Process class. Has five overloads, three of which are for non-command line applications. First overload takes a ProcessStartInfo class that describes process to start, e.g.

ProcessStartInfor pi = new ProcessStartInfo();

pi.FileName = tbProcessName.Textl


Another overload allows the full path to the application to start to be specified. An extension of this allows the user name and password to be used when starting the process to be specified (the password being specified by a System.Security.SecureString object).

Command Line Arguments

The ProcessStartInfo class has an Arguments property that allows as many as arguments as required to be specified. Arguments are separated by spaces, e.g.

ProcessStartInfor pi = new ProcessStartInfo();

pi.FileName = tbProcessName.Textl

pi.Arguments = "Each Word Is An Argument";


StackTrace and StackFrame classes

StackTrace provides access to .NET call stack. Each time method called a StackFrame is added to the stack, and popped off when the method finishes.

When Exception is thrown its StackTrace property is set to the current StackTrace.

Lesson 4: Management Events

Enumerating management objects

Heart of system = DirectoryObjectSearcher object.

Syntax mimics SQL.

To execute query:

  1. Declare instance of ConnectionOption class, set UserName and Password properties. Note, great care should be taken to keep this information secure. Unfortunately, the ConnectionOption class does not use a SecureString object to hold password.
  2. Declare instance of DirectoryObjectSearcher class
  3. Declare ManagementScope object, set its PathName and ConnectionOptions properties.
  4. Create ObjectQuery object and specify query to run.
  5. Create ManagementObjectCollection and set it to the return value from the DirectoryObjectSearchersGet method.

e.g. To enumerate logical drives:

ConnectionOptions demoOptions = new ConnectionOptions();

demoOptions.Username = @"dominname\username";

demoOptions.Password = "password";

ManagementScope demoScope = new ManagementScope(@"\\machinename\root\cimv2", demoOptions)

ObjectQuery demoQuery = new ObjectQuery("SELECT Size, Name from Win32_LogicalDisk where DriveType=3";

ManagementObjectSearcher demoSearcher = new ManagementObjectSearcher(demoScope, demoQuery);

ManagementObjectCollection allObjects = demoSearcher.Get();

foreach (ManagementObject demoObject in allObjects)

A simplified form can be used, for example to obtain a list of paused services:

ManagementObjectSearcher demoSearcher = new ManagementObjectSearcher("SELECT * FROM Win32_Service WHERE Started = FALSE");

ManagementObjectCollection allObjects = demoSearcher.Get();

foreach(ManagementObject pausedService in allObjects)