Logging and Systems Management in Microsoft .NET Framework Application Development Foundation

  • 11/12/2008

Lesson 1: Logging Application State

Systems administrators rely heavily on the Windows event log, a central repository for information about operating system and application activities and errors. For example, Windows adds events each time the operating system starts or shuts down. Applications typically add events when users log on or off, when users change important settings, or when serious errors occur.

By taking advantage of the Windows event log (rather than creating a text-based log file), you allow systems administrators to use their existing event management infrastructure. Most enterprise IT departments have software in place to monitor event logs for important events and forward those events to a central help desk for further processing. Using the Windows event log saves you from writing custom code to support these capabilities.

This lesson describes how to add events, read the event log, and create custom event logs.

Reading and Writing Events

Systems administrators use the Windows event log to monitor and troubleshoot the operating system. By adding events to the event log, you can provide systems administrators with useful details about the inner workings of your application without directly displaying the information to the user. Because many IT departments have an event management infrastructure that aggregates events, the simple act of adding events to the event log can allow your application to be monitored in enterprise environments.

How to View the Event Logs

Use the Event Viewer snap-in to view event logs. You can open the Event Viewer snap-in by following these steps in Windows Vista:

  1. Click Start, right-click Computer, and then click Manage. Respond to the User Account Control (UAC) prompt if it appears.

  2. Expand the Computer Management, System Tools, and Event Viewer nodes.

  3. Browse the subfolders to select an event log.

Recent versions of Windows include the following three event logs (among other less frequently used event logs, depending on the version of Windows and the components installed), located within Event Viewer\Windows Logs in Windows Vista:

  • System. Stores all non-security-related operating system events.

  • Security. Stores auditing events, including user logons and logoffs. If nonstandard auditing is enabled, the Security event log can store events when users access specific files or registry values. Applications cannot write to the Security event log.

  • Application. Originally intended to store all events from all applications that do not create an application-specific event log.

How to Register an Event Source

Events always include a source, which identifies the application that generated the event. Before you log events, you must register your application as a source.

Adding an event source requires administrative privileges. Because Windows Vista does not run programs with administrative privileges by default, adding an event source is best done during the setup process (which typically does have administrative privileges).

If your application is not running as an administrator, you can register an event source manually by following these steps:

  1. Log on as an administrator to the application server.

  2. Start the registry editor by running Regedit.exe.

  3. Locate the following registry subkey:

    HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\Eventlog\Application

  4. Right-click the Application subkey, click New, and then click Key.

  5. Type the name of your event source for the key name (for example, My Application), and then press Enter.

  6. Close the registry editor.

To create an event log source programmatically, call the static EventLog.CreateEventSource method with administrative privileges. You can then create events with the registered source. The following code sample determines whether a source already exists and registers the event source with the Application event log if the source does not yet exist:

' VB
If Not EventLog.SourceExists("My Application") Then
   EventLog.CreateEventSource("My Application", "Application")
End If
// C#
if (!EventLog.SourceExists("My Application"))
   EventLog.CreateEventSource("My Application", "Application");

You can also use EventLog.CreateEventSource to create a custom event log, simply by specifying the name. For example, the following code sample creates an event log named My Log and registers a source named My App:

' VB
If Not EventLog.Exists("My Log") Then
   EventLog.CreateEventSource("My App", "My Log")
End If
// C#
if (!EventLog.Exists("My Log") )
   EventLog.CreateEventSource("My App", "My Log");

In the Windows Vista Event Viewer snap-in, the custom event log appears under Applications And Services Logs. Because calling EventLog.CreateEventSource requires administrative privileges, you should call it during your application’s setup procedure.

How to Log Events

Once your application is registered as a source, you can add an event by using an instance of the EventLog class (in the System.Diagnostics namespace), defining the EventLog.Source property, and then calling the EventLog.WriteEntry method. EventLog.WriteEntry supports the following parameters:

  • message A text message that should describe the condition as thoroughly as possible.

  • type The EventLogEntryType enumeration, which can be Information, Warning, Error, FailureAudit (used when a user is denied access to a resource), or SuccessAudit (used when a user is allowed access to a resource).

  • eventID A number that uniquely identifies the event type. Administrators might use this to search for specific events. You can create your own application-specific event IDs.

  • category A number that identifies the event category. Like the event ID, this is application-specific.

  • rawData A byte array that you can provide if you want to give administrators more information about the event.

The following code sample adds an event to the Application event log, assuming that the source “My Application” has already been registered with the Application event log:

' VB
Dim myLog As New EventLog("Application")
myLog.Source = "My Application"
myLog.WriteEntry("Could not connect", EventLogEntryType.Error, 1001, 1S)
// C#
EventLog myLog = new EventLog("Application");
myLog.Source = "My Application";
myLog.WriteEntry("Could not connect", EventLogEntryType.Error, 1001, 1);

How to Read Events

To read events, create an EventLog instance. Then, access the EventLog.Entries collection. The following application displays all Application events to the console:

' VB
Dim myLog As New EventLog("Application")
For Each entry As EventLogEntry In myLog.Entries
   Console.WriteLine(entry.Message)
Next
// C#
EventLog myLog = new EventLog("Application");
foreach (EventLogEntry entry in myLog.Entries)
   Console.WriteLine(entry.Message);

Logging Debugging and Trace Information

Often, during the development process, developers write messages to the console or display dialog boxes to track the application’s processes. Although this information can be useful, you wouldn’t want it to appear in a production application. To add debug-only code that will not run in release builds, you can use the System.Diagnostics.Debug class.

Use the static Debug.Indent method to cause all subsequent debugging output to be indented. Use the static Debug.Unindent method to remove an indent. Set the Debug.IndentSize property to specify the number of spaces with each indent (the default is four), and set the Debug.IndentLevel property to specify an indentation level.

The following code sample demonstrates how to use the Debug class to mark the beginning and end of an application. If you build this code in Visual Studio with the build type set to Debug, you will see the Starting Application and Ending Application messages. If you build this code with the build type set to Release, you will not see those messages. However, you will still see the “Hello, world!” message:

'VB
Debug.Listeners.Add(New ConsoleTraceListener())
Debug.AutoFlush = True
Debug.Indent()
Debug.WriteLine("Starting application")
Console.WriteLine("Hello, world!")
Debug.WriteLine("Ending application")
Debug.Unindent()
//C#
Debug.Listeners.Add(new ConsoleTraceListener());
Debug.AutoFlush = true;
Debug.Indent();
Debug.WriteLine("Starting application");
Console.WriteLine("Hello, world!");
Debug.WriteLine("Ending application");
Debug.Unindent();

Debug.Write and Debug.WriteLine function exactly the same as Console.Write and Console.WriteLine. To reduce the amount of code that you need to write, the Debug class adds the WriteIf and WriteLineIf methods, each of which accepts a boolean value as the first parameter and writes the output only if the value is True.

Debug.Assert also accepts a boolean condition. In general, you should use assertions to verify something that you know should always be true. For example, in a financial application, you might use an assertion to verify that the due date of a bill is after the year 2000. If an assertion fails, the Common Language Runtime (CLR) stops program execution and displays a dialog box similar to that shown in Figure 10-1. You should not use asserts in production applications.

Figure 10-1

Figure 10-1 A failed call to Debug.Assert

Debug.AutoFlush determines whether debug output is written immediately. If you always want Debug output to be displayed immediately (the most common option), set Debug.AutoFlush to True. If you want to store Debug output and display it all at once (such as when an application exits), set Debug.AutoFlush to False and call Debug.Flush to write the output.

Using Trace

The Trace class functions almost identically to the Debug class. However, calls to Trace are executed in both Debug and Release builds. Therefore, use the Debug class to write messages only in the Debug build, and use the Trace class to write messages regardless of the build type. For example, consider the following code sample:

'VB
Debug.Listeners.Add(New ConsoleTraceListener())
Debug.AutoFlush = True
Debug.Indent()
Debug.WriteLine("Debug: Starting application")
Trace.WriteLine("Trace: Starting application")

Console.WriteLine("Hello, world!")

Debug.WriteLine("Debug: Ending application")
Trace.WriteLine("Trace: Ending application")
//C#
Debug.Listeners.Add(new ConsoleTraceListener());
Debug.AutoFlush = true;
Debug.Indent();

Debug.WriteLine("Debug: Starting application");
Trace.WriteLine("Trace: Starting application");

Console.WriteLine("Hello, world!");

Debug.WriteLine("Debug: Ending application");
Trace.WriteLine("Trace: Ending application");

This code sample generates the following output in a Debug build:

    Debug: Starting application
    Trace: Starting application
Hello, world!
    Debug: Ending application
    Trace: Ending application

The code sample generates the following, shorter output in a Release build. Notice that the output is not indented because the call to Debug.Indent was not executed:

Trace: Starting application
Hello, world!
Trace: Ending application

Properties that you configure for the Debug class also apply to the Trace class. For example, if you add a listener to the Debug class, you do not need to add the same listener to the Trace class.

Using Listeners

By default, Debug and Trace write to the Output window in Visual Studio (if you are running the application directly from Visual Studio) because they have a default listener: DefaultTraceListener. This allows you to view trace output without directly affecting the application’s user interface.

If viewing debug and trace output in the Output window is not sufficient, you can also add the following listeners to the Debug.Listeners collection:

  • ConsoleTraceListener Sends output to the console or the standard error stream.

  • TextWriterTraceListener Sends output to a text file or a stream. Use Console.Out to write output to the console.

  • XmlWriterTraceListener Sends output to an Extensible Markup Language (XML) file using a TextWriter or Stream instance. This is useful for creating log files.

  • EventSchemaListener Sends output to an XML schema–compliant log file. This is useful only if you need output to comply to an existing schema.

  • DelimitedListTraceListener Sends output to a delimited text file. You can configure the delimiter using the DelimitedListTraceLister.Delimiter property.

  • EventLogTraceListener Writes output to the event log. Each time output is flushed, a separate event is generated. To avoid generating large numbers of events (and possibly affecting performance) set Debug.AutoFlush to False.

Configuring Debugging Using a .config File

Often, it’s useful to allow users to view trace output. For example, they might be able to use the trace output to isolate a problem or to provide detailed information to you about the internal workings of the application in a production environment. To allow users to enable tracing, add the <system.diagnostics> section to your application’s .config file.

The following .config file configures a console trace listener and provides instructions for users that allow them to enable tracing selectively:

<configuration>
 <system.diagnostics>
   <trace autoflush="false" indentsize="4">
       <listeners>
           <add name="configConsoleListener"
               type="System.Diagnostics.ConsoleTraceListener" />
       </listeners>
   </trace>
   <switches>
       <!-- This switch controls data messages. In order to receive
          data trace messages, change value="0" to value="1" -->
       <add name="DataMessagesSwitch" value="0" />
       <!-- This switch controls general messages. In order to
          receive general trace messages change the value to the
          appropriate level. "1" gives error messages, "2" gives
          errors and warnings, "3" gives more detailed error
          information, and "4" gives verbose trace information -->
       <add name="TraceLevelSwitch" value="0" />
   </switches>
 </system.diagnostics>
<configuration>

The following .config file directs tracing output to a text file and removes the default listener. Notice that you use the initializeData attribute when adding the listener to specify the output file—this is true for other listeners that require a filename as well:

<configuration>
 <system.diagnostics>
   <trace autoflush="false" indentsize="4">
       <listeners>
           <add name="TextTraceListener"
               type="System.Diagnostics.TextWriterTraceListener"
               initializeData="output.txt" />
           <remove name="Default" />
       </listeners>
   </trace>
 </system.diagnostics>
<configuration>

Lab: Working with Event Logs

In this lab, you will create a WPF application that adds events to a custom event log.

Exercise 1: Create an Event Log and Log an Event

In this exercise, you must create a solution that includes three projects: a WPF application, a class derived from Installer, and a Setup project. You must create a Setup project to add the custom event log during the setup process because the user typically has administrative credentials only during setup.

  1. Use Visual Studio to create a new WPF Application project named LoggingApp in either Visual Basic.NET or C#.

  2. In the Extensible Application Markup Language (XAML) for the LoggingApp window, add an event handler for the Loaded event. The XAML now resembles the following:

    <Window x:Class="LoggingApp.Window1"
       xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
       xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
       Loaded="Window_Loaded"
       Title="Window1" Height="300" Width="300">
       <Grid>
    
       </Grid>
    </Window>
  3. In the code file, add the System.Diagnostics namespace. Then implement the Loaded event handler to add an event indicating that the application has started. Add the event to a custom event log named LoggingApp Log with a source of LoggingApp. The following code demonstrates how to do this:

    ' VB
    Dim myLog As New EventLog("LoggingApp Log")
    myLog.Source = "LoggingApp"
    myLog.WriteEntry("LoggingApp started!", _
       EventLogEntryType.Information, 1001)
    // C#
    EventLog myLog = new EventLog("LoggingApp Log");
    myLog.Source = "LoggingApp";
    myLog.WriteEntry("LoggingApp started!",
       EventLogEntryType.Information, 1001);
  4. Add a new project to the solution using the Class Library template, and name it LoggingAppInstaller.

  5. In the LoggingAppInstaller namespace, derive a custom class named InstallLog from the Installer class. As described in Lesson 3 of Chapter 9, “Installing and Configuring Applications,” implement the Install, Rollback, and Uninstall methods to add and remove an event log named LoggingApp and a source named Logging AppSource. Note that you need to add a reference to the System.Configuration.Install dynamic-link library (DLL). The following code sample demonstrates how to write the code:

    ' VB
    Imports System.Diagnostics
    Imports System.Configuration.Install
    Imports System.ComponentModel
    Imports System.Collections
    
       <RunInstaller(True)> _
       Public Class InstallLog
           Inherits Installer
           Public Sub New()
               MyBase.New()
           End Sub
    
           Public Overrides Sub Commit( _
               ByVal mySavedState As IDictionary)
               MyBase.Commit(mySavedState)
           End Sub
    
           Public Overrides Sub Install( _
               ByVal stateSaver As IDictionary)
               MyBase.Install(stateSaver)
               If Not EventLog.Exists("LoggingApp Log") Then
                   EventLog.CreateEventSource("LoggingApp", "LoggingApp Log")
               End If
           End Sub
    
           Public Overrides Sub Uninstall( _
               ByVal savedState As IDictionary)
               MyBase.Uninstall(savedState)
               RemoveLog()
           End Sub
    
           Public Overrides Sub Rollback( _
               ByVal savedState As IDictionary)
               MyBase.Rollback(savedState)
               RemoveLog()
           End Sub
    
           Public Sub RemoveLog()
               If EventLog.Exists("LoggingApp Log") Then
                   EventLog.DeleteEventSource("LoggingApp")
                   EventLog.Delete("LoggingApp Log")
               End If
           End Sub
       End Class
    // C#
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    
    using System.Diagnostics;
    using System.Configuration.Install;
    using System.ComponentModel;
    using System.Collections;
    
    namespace LoggingAppInstaller
    {
       [RunInstaller(true)]
       public class InstallLog : Installer
       {
           public InstallLog()
               : base()
           {
           }
    
           public override void Commit(IDictionary mySavedState)
           {
               base.Commit(mySavedState);
           }
    
           public override void Install(IDictionary stateSaver)
           {
               base.Install(stateSaver);
               if (!EventLog.Exists("LoggingApp Log"))
                   EventLog.CreateEventSource(
                       "LoggingApp", "LoggingApp Log");
           }
    
           public override void Uninstall(IDictionary savedState)
           {
               base.Uninstall(savedState);
               RemoveLog();
           }
    
           public override void Rollback(IDictionary savedState)
           {
               base.Rollback(savedState);
               RemoveLog();
           }
    
           public void RemoveLog()
           {
               if (EventLog.Exists("LoggingApp Log"))
               {
                   EventLog.DeleteEventSource("LoggingApp");
                   EventLog.Delete("LoggingApp Log");
               }
           }
       }
    }
  6. Add a Setup project to your solution named LoggingApp Setup.

  7. Right-click Application Folder in the left pane, click Add, and then click Project Output. In the Add Project Output Group dialog box, click Primary Output for the LoggingApp project and then click OK.

  8. Right-click Primary Output From LoggingApp, and then click Create Shortcut To Primary Output From LoggingApp. Name the shortcut LoggingApp and then drag it to the User’s Programs Menu folder.

  9. Add custom actions to the Setup Project to call the appropriate InstallLog methods. Right-click LoggingApp Setup in Solution Explorer, click View, and then click Custom Actions.

  10. Right-click Install and then click Add Custom Action. In the Select Item In Project dialog box, double-click Application Folder and then click Add Output. In the Add Project Output Group dialog box, click the Project drop-down list, click LoggingAppInstaller, select Primary Output, and then click OK. Then, click OK again. Accept the default name, and notice that the InstallerClass property for the primary output is set to True.

  11. Right-click Rollback and then click Add Custom Action. In the Select Item In Project dialog box, double-click Application Folder, click Primary Output From LoggingAppInstaller, and then click OK.

  12. Repeat step 11 to add the LoggingAppInstaller DLL for the Uninstall custom action.

  13. Build your solution. Right-click LoggingApp Setup in Solution Explorer, and then click Build.

  14. Open the LoggingApp Setup build destination folder and double-click LoggingApp Setup.msi to start the installer. Accept the default settings to install the application. If you are running Windows Vista, respond appropriately when User Account Control (UAC) prompts you for administrative credentials.

  15. Click Start, click All Programs, and then click LoggingApp to start the program. Then, close the window.

  16. Open Event Viewer. In Windows Vista, you can do this by clicking Start, right-clicking Computer, and then clicking Manage. Respond to the UAC prompt, expand System Tools, and select Event Viewer.

  17. Navigate to Event Viewer, Applications And Services Logs, and LoggingApp Log to verify that the new event log exists. Notice the single event in the event log, indicating that LoggingApp started.

  18. Uninstall LoggingApp using the Programs And Features tool in Control Panel.

  19. Close and reopen Event Viewer. Notice that LoggingApp Log has been removed.

Lesson Summary

  • Before you can add events, you must register an event source by calling EventLog.CreateEventSource. You can then call EventLog.WriteEntry to add events. Read events by creating an instance of the EventLog class and accessing the EventLog.Entries collection.

  • Use the Debug and Trace classes to log the internal workings of your application for troubleshooting purposes. Debug functions only in Debug releases. Trace can function with any release type. Users can configure listeners for Debug and Trace using the .config files.

Lesson Review

You can use the following questions to test your knowledge of the information in Lesson 1, “Logging Application State.” The questions are also available on the companion CD if you prefer to review them in electronic form.

  1. You are creating a custom installer for an application that needs to add events to the Application event log. Which of the following do you need to do during the setup process?

    1. Call EventLog.CreateEventSource

    2. Call EventLog.Create

    3. Call EventLog.GetEventLogs

    4. Call EventLog.WriteEntry

  2. You are creating a custom tool for your IT department that analyzes failure audits generated by the operating system. Which event log should you examine?

    1. Application

    2. System

    3. Security

    4. Setup

  3. When running a Debug build of an application, you want to display a dialog box if the result of a calculation (stored in the result integer) is less than zero. Which of the following methods does this correctly?

    1. Debug.Assert(result >= 0, “Result error”)

    2. Trace.Assert(result >= 0, “Result error”)

    3. Debug.WriteIf(result >= 0, “Result error”)

    4. Trace.WriteIf(result >= 0, “Result error”)

  4. You are creating a Console application, and you want Debug and Trace output displayed directly to the console. Which code sample does this correctly?

    1. 'VB
      Debug.Listeners.Add(New DefaultTraceListener())
      Debug.AutoFlush = True
      //C#
      Debug.Listeners.Add(new DefaultTraceListener ());
      Debug.AutoFlush = true;
    2. 'VB
      Debug.Listeners.Add(New ConsoleTraceListener())
      Debug.AutoFlush = True
      //C#
      Debug.Listeners.Add(new ConsoleTraceListener());
      Debug.AutoFlush = true;
    3. 'VB
      Debug.Listeners.Add(New EventLogTraceListener())
      Debug.AutoFlush = True
      //C#
      Debug.Listeners.Add(new EventLogTraceListener());
      Debug.AutoFlush = true;
    4. 'VB
      Debug.Listeners.Add(New XmlWriterTraceListener())
      Debug.AutoFlush = True
      //C#
      Debug.Listeners.Add(new XmlWriterTraceListener());
      Debug.AutoFlush = true;