Instrumentation in the .NET framework
Lesson 1: Logging
Windows Event Log
Benefits:
- Mechanism to record specific events regarding applications state
- Mechanism to record situations considered to be out of ordinary
- Mechanism for users to check state of running applications
Costs:
- Creating EventLog object, writing to it and passing to code executing in partial trust is security vulnerability. EventLog objects (including EventLogEntry and EventLogEntryCollection) should never be passed to less trusted code.
- EventLogPermission is required for many EventLog manipulations. Granting to partially trusted code represents security vulnerability, e.g. Ability to spoof other applications(shut down virus scanner and yet make it appear to still be running).
- Resource intensive. EventLog objects can get filled up after which attempts to write will raise exception.
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...
EventLog.Delete("Chap10Demo");
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)
{
Console.WriteLine(DemoEntry.Message);
}
Clearing Event Log
Call Clear method.
Lesson 2: Debugging and Tracing
Debugger Class
Enables communication with a debugger application, key methods:
- Break - signals break to debugger
- IsAttached - is debugger attached to process
- IsLogging - is the debugger logging
- Launch - launches debugger and attaches it to process
- Log - post message to debugger
e.g. To use Break method to conditionally break when a null string encountered...
String myMessage = ReturnMessage();
if (MyMessage == null)
{
Debugger.Break();
}
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...
Trace.Listeners.Clear();
DefaultTraceListener myListener = new DefaultTraceListener();
Trace.Listeners.Add(myListener);
Debugger.Log(1, "Test", "This is a test");
Console.ReadLine();
Debug class
Debugger class only really provides two methods - Break and Log. More granularity is provided by Debug class. Commonly used methods:
- Assert - evaluates condition and displays message if condition is false. Load code with Asserts wherever there is a condition that should be true or false. When code executes if condition is not true then debugger will be launched. Can use highly detailed messages that ease finding problems. When compiled in release mode Debug commands (including Assert) are not processed.
- Close - flushes output buffer and calls Close on each attached listener
- Fail - output failure message. Fail causes the debugger to break at an offending line of code. Unlike Assert it doesn't use an evaluation.
- Flush - flush output buffer, causing buffered data to be written to listeners
- Indent - increment indent level by one
- Unindent - decrement indent level by one
- Write - write information to attached debug or trace listeners
- WriteIf - write information to attached debug or trace listeners if specified condition met
- WriteLine - write information + carriage return to attached debug or trace listeners
- WriteLineIf - write information + carriage return to attached debug or trace listeners if specified condition met
Debug Attributes
-
DebuggerBrowsable - controls how the element the attribute is applied to displays itself in the debugger. Possible values:
- Never - element not visible in debugger
- Collapsed - element shows, but in collapsed state
- RootHidden - does not display the root element, but does show children if they are members of a collection or array
-
DebuggerDisplay - controls what information an element shows in the debugger. For example if applied to a class with member variables _companyName, _companyState and _companyCity the following attribute
[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
- DebuggerHidden - stops breakpoint being set inside anything it decorates. If ones is set, then it is ignored.
- DebuggerNonUserCode - similar to DebuggerHidden. Mainly used to decorate code generated by designer / wizards and so servers little purpose to a developer. The decorated code is not displayed in the debugger.
- DebuggerStepperBoundary - used within code wrapped by DebuggerNonUserCode. When encountered debugger will stop in code decorated by this attribute. Use when code created by designer / wizard contains one or more interesting methods or properties.
- DebuggerStepThrough - indicates that the code should be stepped over. Use when sure code is correct, but don't mind showing it in debugger.
- DebuggerTypeProxy - override how a given type is shown
- DebuggerVisualizer - Visualizers provide a mechanism (rather like IntelliSense) to see all the properties of an object. This attribute tells the debugger that there exists a visualizer for the specified class.
Trace
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();
- Declare instance of TraceSource
- Name the TraceSource
TraceSource DemoTrace = new TraceSource( "DemoApp");
- 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.
DefaultTraceListener
Can be added manually:
Trace.Listeners.Add(new DefaultTraceListener());
Trace.WriteLine("A test");
or via Configuration file entry:
<configuration>
<system.diagnostics>
<trace autoflush="true" indentsize="5" />
</system.diagnostics>
</configuration>
TextWriterTraceListener
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");
XmlWriterTraceListener
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.
EventLogTraceListener
Directs output to the event log.
DelimitedListTraceListener
Similar to TextWriterTraceListener, except it takes a delimited token and separates output by this token.
Lesson 3: Monitoring Performance
Process
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.
- Process.GetCurrentProcess() provides class representing the currently active process.
- Process.GetProcessById(int id, string machineName) provides class representing the processes identified by the integer provided. Will throw exception if specified process does not exist.
- Process.GetProcessByName(string processName, string machineName) provides class representing the process identified by the name provided.
- Process.GetProcesses(string machineName) returns an array of process classes, one for each processes currently running.
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:
- Computer Name
- Category Name
- Category Instance
- Counter Name
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.
- CounterHelp - counter friendly description
- CounterName - the counter name
- CounterType - The counters PerformanceCounterType
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.
- Create - create PerformanceCounterCategory class
- Delete - delete PerformanceCounterCategory class
- Exists - determine if PerformanceCounterCategory class exists
- GetCategories - list each of the PerformanceCounterCategory classes on a machine
- ReadCategory - read performance and counter data associated with a PerformanceCategory
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
Process.Start(pi);
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";
Process.Start(pi);
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:
- 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.
- Declare instance of DirectoryObjectSearcher class
- Declare ManagementScope object, set its PathName and ConnectionOptions properties.
- Create ObjectQuery object and specify query to run.
- 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)
{
Console.WriteLine(demoObject["Name"].ToString());
}
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)
{
Console.WriteLine(pausedService["Caption"]);
}