Pro Visual C++ 2005 for C# Developers

Troubleshooting services is different from troubleshooting normal applications. This section covers troubleshooting topics such as:

The best way to start building a service is to create an assembly with the functionality you want and a test client, before the service is actually created. Here you can do normal debugging and error handling. As soon as the application is running, you can build a service by using this assembly. Of course, there might still be problems with the service:

Interactive Services

If an interactive service runs with a logged-on user it can be helpful to display message boxes to the user. If the service should run on a server that is locked inside a computer room, the service should never display a message box. When you open a message box to wait for some user input, the user input probably won't happen for some days because nobody is looking at the server in the computer room; but it can get even worse than that: If the service isn't configured as an interactive service, the message box opens up on a different, hidden, window station. In this case, no one can answer that message box because it is hidden and the service is blocked.

Important

Never open dialog boxes for services running on a server system. Nobody will answer them.

In cases where you really want to interact with the user, an interactive service can be configured. Some examples of such interactive services are the Print Spooler that displays paper-out messages to the user, and the NetMeeting Remote Desktop Sharing service.

To configure an interactive service, you must set the option "Allow service to interact with desktop" in the Services configuration tool (see Figure 36-20). This changes the type of the service by adding the SERVICE_INTERACTIVE_PROCESS flag to the type.

Figure 36-20

Event Logging

Services can report errors and other information by adding events to the event log. A service class derived from ServiceBase automatically logs events when the AutoLog property is set to true. The ServiceBase class checks this property and writes a log entry at start, stop, pause, and continue requests.

In this section, you explore the following:

Figure 36-21 shows an example of a log entry from a service.

Figure 36-21

For custom event logging, you can use classes from the System.Diagnostics namespace.

Event Logging architecture

By default, the event log is stored in three log files: Application, Security, and System. Looking at the registry configuration of the event log service, you'll notice three entries under HKEY_LOCAL_ MACHINE\System\CurrentControlSet\Services\Eventlog with configurations pointing to the specific files. The System log file is used from the system and device drivers. Applications and services write to the Application log. The Security log is a read-only log for applications. The auditing feature of the operating system uses the Security log.

You can read these events by using the administrative tool Event Viewer. The Event Viewer can be started directly from the Server Explorer of Visual Studio by right-clicking on the Event Logs item and selecting the Launch Event Viewer entry from the context menu. The Event Viewer is shown in Figure 36-22.

Figure 36-22

In the event log, you can see this information:

Event Logging classes

The System.Diagnostics namespace has some classes for event logging:

Adding event logging

If the AutoLog property of the ServiceBase class is set to true, event logging is automatically turned on. The ServiceBase class logs an informational event at startup, stop, pause, and continue requests of the service. In the ServiceInstaller class an EventLogInstaller instance is created so that an event log source is configured. This event log source has the same name as the service. If you want to write events you can use the WriteEntry() method of the EventLog class. The Source property was already set in the ServiceBase class:

eventLog.WriteEntry("event log message");

This method logs an informational event. If warning or error events should be created, an overloaded method of WriteEvent() can be used to specify the type:

eventLog.WriteEntry("event log message", EventLogEntryType.Warning); eventLog.WriteEntry("event log message", EventLogEntryType.Error);

Adding event logging to other application types

With services, the ServiceBase class automatically adds event-logging features. If you would like to use event logging within other application types, you can easily do that by using Visual Studio.

If you do an xcopy-installation the last two steps are not really necessary. If the Source property for the EventLog instance is set, this source is automatically registered when an event log is written the first time, which is really easy to do. However, for a real application, you are better off adding the installer. With installutil /u the event log configuration is unregistered. If the application is just deleted, this registry key remains unless EventLog.DeleteEventSource() is called.

Adding event logging to the QuoteServer

The library QuoteServer used from the QuoteService currently doesn't have event logging included but this can easily be changed. To use the Visual Studio Designer to drag and drop the EventLog component to the class, you have to add designer support to the class.

Note

To add designer support to the class you have to derive the class from the base class System.ComponentModel.Component, and invoke the method InitializeComponent() inside the constructor of the class. The method InitializeComponent() that will be used from the Designer to set the properties of the components will be added automatically as soon as the first component is dropped onto the Designer surface, but you have to invoke this method yourself.

After you change the code of the QuoteServer class in the library QuoteServer with a derivation from the base class System.ComponentModel.Component, you can switch Visual Studio to the design view:

public class QuoteServer : System.ComponentModel.Component { //... public QuoteServer() : this ("quotes.txt") { } public QuoteServer(string filename) : this(filename, 7890) { } public QuoteServer(string filename, int port) { this.filename = filename; this.port = port; InitializeComponent(); }

After this change, you can drag and drop the EventLog component from the toolbox to the design view, where an instance of the EventLog class is created. Change the Log property of the object to Application and the Source property to QuoteService.

Then you can change the implementation of the method ListenerThread() in the class QuoteServer, so that an event log entry is written in case an exception is generated:

protected void ListenerThread() { try { IPAddress ipAddress = Dns.Resolve("localhost").AddressList[0]; listener = new TcpListener(ipAddress, port); listener.Start(); while (true) { Socket clientSocket = listener.AcceptSocket(); string message = GetRandomQuoteOfTheDay(); UnicodeEncoding encoder = new UnicodeEncoding(); byte[] buffer = encoder.GetBytes(message); clientSocket.Send(buffer, buffer.Length, 0); clientSocket.Close(); } } catch (SocketException ex) { string message = "Quote Server failed in Listener: " + ex.Message; eventLog.WriteEntry(message, EventLogEntryType.Error); } }

Trace

It's also possible that all your trace messages are redirected to the event log. You shouldn't really do this, because on a normal running system the event log gets overblown with trace messages, and the system administrator could miss the really important logs if this happens. Turning on trace messages to the event log can be useful when testing features for problematic services. Tracing is possible with debug as well as with release code.

To send trace messages to the event log you must create an EventLogTraceListener object and add it to the listener's list of the Trace class:

EventLogTraceListener listener = new EventLogTraceListener(eventLog1); Trace.Listeners.Add(listener);

Now, all trace messages are sent to the event log:

Trace.WriteLine("trace message");

Creating an event log listener

Next, you write an application that receives an event when a service encounters a problem. Create a simple Windows application that monitors the events of your Quote service. This Windows application consists of a list box and an Exit button only, as shown in Figure 36-23.

Figure 36-23

Add an EventLog component to the design view by dragging and dropping it from the toolbox. Set the Log property to Application, and the Source to the source of your service, QuoteService. The EventLog class also has a property, EnableRaisingEvents. The default value is false; setting it to true means that an event is generated each time this event occurs, and you can add an event handler for the EntryWritten event of the EventLog class. Add a handler with the name OnEntryWritten() to this event.

The OnEntryWritten() handler receives an EntryWrittenEventArgs object as argument, from which you can get the complete information about an event. With the Entry property an EventLogEntry object with information about the time, event source, type, category, and so on is returned:

protected void OnEntryWritten (object sender, System.Diagnostics.EntryWrittenEventArgs e) { DateTime time = e.Entry.TimeGenerated; string message = e.Entry.Message; listBoxEvents.Items.Add(time + " " + message); }

The running application displays all events for the QuoteService as shown in Figure 36-24.

Figure 36-24

Performance Monitoring

Performance monitoring can be used to get information about the normal running of the service. Performance monitoring is a great tool that helps you understand the workload of the system, and observe changes and trends.

Microsoft Windows has a lot of performance objects, such as System, Memory, Objects, Process, Processor, Thread, Cache, and so on. Each of these objects has many counts to monitor. For example, with the Process object, the user time, handle count, page faults, thread count, and so on can be monitored for all processes, or for specific process instances. Some applications, such as SQL Server, also add application-specific objects.

For the quote service sample application it might be interesting to get information about the number of client requests, the size of the data sent over the wire, and so on.

Performance monitoring classes

The System.Diagnostics namespace provides these classes for performance monitoring:

Performance Counter Builder

You can create a new performance counter category by selecting the performance counters in the Server Explorer and by selecting the menu entry Create New Category on the context menu. This launches the Performance Counter Builder (see Figure 36-25).

Figure 36-25

Set the name of the performance counter category to Quote Service. The following table shows all performance counters of the quote service.

Name

Description

Type

# of Bytes sent

Total # of bytes sent to the client

NumberOfItems32

# of Bytes sent/sec

# of bytes sent to the client in one second

RateOfCountsPerSecond32

# of Requests

Total # of requests

NumberOfItems32

# of Requests/sec

# of requests in one second

RateOfCountsPerSecond32

The Performance Counter Builder writes the configuration to the performance database. This can also be done dynamically by using the Create() method of the PerformanceCounterCategory class in the System.Diagnostics namespace. An installer for other systems can easily be added later using Visual Studio.

Adding PerformanceCounter components

Now you can add PerformanceCounter components from the toolbox. Instead of using the components from the toolbox category Components, you can directly drag and drop the previously created performance counts from the Server Explorer to the design view. This way the instances are configured automatically: the CategoryName property is set to "Quote Service Count" for all objects, and the CounterName property is set to one of the values available in the selected category. Because with this application the performance counts will not be read but written, you have to set the ReadOnly property to false.

Here is a part of the code generated into InitalizeComponent() by adding the PerformanceCounter components to the Designer and by setting the properties as indicated previously:

private void InitializeComponent() { //... // performanceCounterRequestsPerSec // this.performanceCounterRequestsPerSec.CategoryName = "Quote Service Counts"; this.performanceCounterRequestsPerSec.CounterName = "# of Requests / sec"; this.performanceCounterRequestsPerSec.MachineName = "cnagel"; this.performanceCounterReqeustsPerSec.ReadOnly = false; // // performanceCounterBytesSentTotal // this.performanceCounterBytesSentTotal.CategoryName = "Quote Service Counts"; this.performanceCounterBytesSentTotal.CounterName = "# of Bytes sent"; this.performanceCounterBytesSentTotal.MachineName = "cnagel"; this.performanceCounterBytesSentTotal.ReadOnly = false; // // performanceCounterBytesSentPerSec // this.performanceCounterBytesSentPerSec.CategoryName = "Quote Service Counts"; this.performanceCounterBytesSentPerSec.CounterName = "# of Bytes sent / sec"; this.performanceCounterBytesSentPerSec.MachineName = "cnagel"; this.performanceCounterBytesSentPerSec.ReadOnly = false; // // performanceCounterRequestsTotal // this.performanceCounterRequestsTotal.CategoryName = "Quote Service Counts"; this.performanceCounterRequestsTotal.CounterName = "# of Requests"; this.performanceCounterRequestsTotal.MachineName = "cnagel"; this.performanceCoutnerRequestsTotal.ReadOnly = false; //... }

For the calculation of the performance values, you have to add the fields requestsPerSec and bytesPerSec to the class QuoteServer:

public class QuoteServer : System.ComponentModel.Component { // Performance monitoring counts private int requestsPerSec; private int bytesPerSec;

The performance counts that show the total values are incremented directly in the ListenerThread() method (shown in the following code) of the QuoteServer class. You can use PerformanceCounter.Increment() to count the number of total requests, and IncrementBy() to count the number of bytes sent.

For the performance counts that show the value by seconds, just the two variables, requestsPerSec and bytesPerSec, are updated in the ListenerThread() method:

protected void ListenerThread() { try { listener = new TCPListener(port); listener.Start(); while (true) { Socket clientSocket = listener.Accept(); string message = GetRandomQuoteOfTheDay(); UnicodeEncoding encoder = new UnicodeEncoding(); byte[] buffer = encoder.GetBytes(message); clientSocket.Send(buffer, buffer.Length, 0); clientSocket.Close(); performanceCounterRequestsTotal.Increment(); performanceCounterBytesSentTotal.IncrementBy(buffer.Length); requestsPerSec++; bytesPerSec += buffer.Length; } } catch (SocketException ex) { string message = "Quote Server failed in Listener: " + ex.Message; EventLog.WriteEntry("QuoteService", message); } }

To show updated values every second, add a Timer component. Set the OnTimer() method to the Elapsed event of this component. The OnTimer() method is called once per second if you set the Interval property to 1000. In the implementation of this method, set the performance counts by using the RawValue property of the PerformanceCounter class:

protected void OnTimer (object sender, System.Timers.ElapsedEventArgs e) { performanceCounterBytesSentPerSec.RawValue = bytesPerSec; performanceCounterRequestsPerSec.RawValue = requestsPerSec; bytesPerSec = 0; requestsPerSec = 0; }

perfmon.exe

Now you can monitor the service. You can start the Performance tool by selecting Administrative Tools Performance. By pressing the + button in the toolbar, you can add performance counts. The Quote Service shows up as a performance object. All the counters that have been configured show up in the counter list as shown in Figure 36-26.

Figure 36-26

After you've added the counters to the performance monitor, you can see the actual values of the service over time (see Figure 36-27). Using this performance tool, you can also create log files to analyze the performance at a later time.

Figure 36-27

Категории