Chapter 17. Supporting and Maintaining the Application

Your work doesn't end when you deliver a new application to the users. Even the difficulty of a successful deployment doesn't end the application process. In fact, from a certain perspective, your work has just begun, because now the test of your application design and implementation begins by vicious users who will show no pity whatsoever. You may quickly find yourself longing for the days when you spent hours, coffee cup in hand, writing and debugging the initial application — the days when your hopes were high. I'm not trying to terrify you too much, but once the application leaves the test environment and goes onto user machines, everything will change. People will do the inconceivable to your application and that's just the good days — the bad days will be worse.

Supporting and maintaining your application is hard because you often have to fix errors on a short schedule without much in the way of helpful information. The best developers do listen to the user and try to understand precisely what's gone wrong with the application they're using. However, even the most coherent users won't deliver everything you need to fix the application, and all the jumping up and down in the world won't change the situation. That's why the best developers also add capabilities to their applications that make the support and maintenance process easier. This chapter describes many of the techniques that developers use.

Creating Error Logs

The focal point of most support and maintenance tasks is the error log. Error logs take a number of forms. For example, some developers actually rely on the Windows event logs to store error information, but sometimes that approach doesn't work particularly well. Most developers today rely on a custom error log setup and at least a few are relying on a serialized XML file to provide the required error information, because XML files are easier to incorporate into databases. (Chapter 22 provides additional information on working with XML serialization.) However, the kind of error log that you use matters less than the fact that you do use one to store error information. The following sections describe one technique for creating an error log within an application.

Defining an Error

The example in this section shows one way to create an error log. It certainly isn't the only way to create such an error log, but it's one of the easier ways to provide relatively comprehensive support of exception logging. Of course, you may eventually want to add support for tracking TRACE and DEBUG statements in your code, too. The level to which you extend this code depends on your personal needs. Before you can do anything else, you need a method that generates an error, catches it, and then reports it, as shown in Listing 17-1.

Example 17.1. Defining an error condition, catching it, and reporting it

private void btnTest_Click(object sender, EventArgs e)
{
   try
   {
      int Value1 = 1;
      int Value2 = 0;

      // Create an error.
      int Value3 = Value1 / Value2;
   }
   catch (DivideByZeroException DBZ)
   {
      // Output the error information to the error log.
      ErrorHandler.RegisterError(
         DBZ.Message,
         DBZ.Source,
         DBZ.TargetSite.Name,
         DBZ.TargetSite.GetParameters(),
         DBZ.StackTrace,
         DBZ.Data,
         DBZ.InnerException);

      // Display a user friendly message.
      MessageBox.Show("A Math Error Has Happened!");
   }
}

The error isn't hard to understand — the code is dividing a value by zero on purpose to generate the error. The catch code is also typical for a DivideByZeroException exception. The important piece of code is the call to ErrorHandler.RegisterError(). This special class records all the exception information you see in the parameter list for later use by you in diagnosing the problem. Not every potential piece of information appears in the list, but you obtain enough to make it easier to understand the application error. You'll see how the ErrorHandler.RegisterError() method works later in this section.

Handling Error Log Settings

One of the problems you'll encounter when working with error logs is where to put the log. You could place it on the local system, but the user might erase it, mangle it, or simply not understand what you mean when you ask for it. It's important to understand that the user's machine may not be accessible from the network after the error occurs, so you may not be able to obtain the log yourself without walking over to the user's machine and getting it. Of course, you can always rely on a network storage location, which works fine unless the user's machine has lost network access as well.

The best solution for the location problem is to provide log settings as part of the application. Using this approach lets you tune an error log location for particular systems so that you have the best possible chance of catching the information you need. As part of the log settings, you'll also want to control the log size, so you'll probably want to add a size setting as well. To add the settings to your application, choose Project

Handling Error Log Settings
Define settings for the error log so that an administrator can easily change the location later.

Figure 17.1. Define settings for the error log so that an administrator can easily change the location later.

Creating the Error Logging Class

Now that you have an error to record and settings to describe where to place the information, it's time to look at the error class itself. Normally, you'd create an assembly with your error handler and include that assembly with your application. To make things simple, the example provides the error logging class as part of the application. Listing 17-2 shows the error logging class code. The class contains a single static method, RegisterError(). You could possibly create special methods for certain kinds of errors or to include the content of DEBUG and TRACE calls as part of your error log (you need to use a custom trace listener to perform this task — see the example at http://msdn.microsoft.com/en-us/library/ms180974.aspx).

Example 17.2. Logging the error to disk

class ErrorHandler
{
   public static void RegisterError(
      String Message,
      String Source,
      String MethodName,
      ParameterInfo[] Parameters,
      String StackTrace,
      IDictionary Data,
      Exception InnerException
      )
   {
      // Obtain the log file settings.
      String LogFileLocation =
         Properties.Settings.Default.LogFileLocation;
      String LogFileName =
         Properties.Settings.Default.LogFileName;
      Int64 LogFileSize =
         Properties.Settings.Default.LogFileSize;

      // Check for the log file.
      if (File.Exists(LogFileLocation + LogFileName))
      {
         // Verify the log isn't too large.
         if (
            File.OpenRead(
               LogFileLocation + LogFileName).Length >= LogFileSize)

            // Delete the old file if the file is too large.
            File.Delete(LogFileLocation + LogFileName);
      }

      // Open the log file for writing.
      TextWriter LogFile =
         new StreamWriter(LogFileLocation + LogFileName, true);

      // Write an error header.
      LogFile.WriteLine(DateTime.Now.ToLongDateString());
      LogFile.WriteLine(DateTime.Now.ToLongTimeString());
      LogFile.WriteLine(Environment.MachineName);
      LogFile.WriteLine();
// Write the error information.
      if (Message != null)
         LogFile.WriteLine(
            "Message: " + Message);
      if (Source != null)
         LogFile.WriteLine(
            "Source: " + Source);
      if (MethodName != null)
         LogFile.WriteLine(
            "Method Name: " + MethodName);
      if (Parameters != null)
      {
         foreach (ParameterInfo ThisInfo in Parameters)
            LogFile.WriteLine(
               "	Parameter: " + ThisInfo.ToString());
      }
      if (Data != null)
      {
         IDictionaryEnumerator DataEnum = Data.GetEnumerator();
         while (DataEnum.MoveNext())
         {
            LogFile.Write(
               "Key: " + DataEnum.Key.ToString());
            LogFile.WriteLine(
               " Value: " + DataEnum.Value.ToString());
         }
      }
      if (StackTrace != null)
         LogFile.WriteLine(
            "Stack Trace:
" + StackTrace);
      LogFile.WriteLine();

      // Process the inner exception.
      if (InnerException != null)
      {
         LogFile.WriteLine("Inner Exception Message: "
            + InnerException.Message);
         LogFile.WriteLine("Inner Exception Source: "
            + InnerException.Source);
         LogFile.WriteLine();
      }

      // Close the log file.
      LogFile.Flush();
      LogFile.Close();
   }
}

The example code illustrates a number of principles that are essential for you to provide in any disk-based error log solution. The code begins by obtaining the error log parameters from the application settings you created earlier. The code then checks the status of the error log before it does anything else. If the error log is too large, the example deletes it. Of course, you could simply rename the log, but then you also need to provide logic for removing old logs or the hard drive will end up with a host of potentially useless log information.

When you open the log file for writing, the easiest approach is to use a TextWriter object as shown in the example code. A TextWriter can handle most data types easily, and the code for using it is relatively simple, too. Make sure you open the log file for appending by adding true as the second method call parameter or you'll overwrite old log entries.

Error logs aren't particularly useful if you don't provide some time and location specifics in them. The example adds this information as a header at the beginning of every log entry to make it easy to identify individual entries. You can add other information, such as the username, if desired. However, don't fill up the log with information you really don't need. If there's only one user per machine, you already know who's using the machine, so it's not necessary to record that information in most cases (unless users routinely share machines).

The next step is to write the error information for this particular exception. You must check every parameter passed to the method because some exceptions generate more information than others do and you don't want to create another error by trying to process information that isn't there. Most of the information you receive is straight text. However, notice that some of the parameters, such as Data, require special processing because they contain multiple pieces of information. It's easy to overdo the information from these complex sources — make sure you actually need the information they provide before you record it in the log file. The example code shows a typical level of information, but there isn't any reason to avoid other specifics if you need them.

The final step is to record the information found in the inner exception, when it exists. An exception can have multiple levels of inner exceptions. Depending on the complexity of your code, you may want to process every level iteratively. However, most error logs need, at most, one additional level of error information, because it's quite possible that the inner exception has already appeared in the error log. You may choose not to process the inner exception at all in some cases. One way to handle this variable level of processing is simply to pass a null value as the InnerException parameter.

Viewing the Error Log Output

It's time to run the application. When you click Test, you'll see a simple message telling that the error has happened, but the message box doesn't provide much information. The developer-level information actually appears in the error log, which you can open using Notepad. Figure 17-2 shows a typical example of the error log output for this example. The information you see when you run the example will probably vary a little from the information shown in the figure, but it should be essentially the same.

In this case, the DivideByZeroException exception provides some basics that tell you where to find the error and which control caused the error. The source is the ErrorLog application, of course, and it occurred during a call to btnTest_Click(), which provides two parameters, "sender" and "e". The stack trace tells you that the error actually occurred on or near line 33 of the code.

The error log provides enough information for you to start debugging.

Figure 17.2. The error log provides enough information for you to start debugging.

Sometimes the stack trace is quite accurate about the location of an error. In this case, line 33 has the int Value3 = Value1 / Value2; code associated with it. However, you can't always count on the stack trace providing completely accurate information. The stack trace may simply show the point at which the application first detected the error, rather than the point at which the error occurred. For example, in this case, someone could make the point that the error actually occurs on line 30 when the code first assigns a value of 0 to Value2.

Using Error Logs to Provide Support

Error logs can do a lot more than simply provide the developer with a good idea of where to look for information — error logs can also help determine the approach an organization uses to deal with a particular problem. For example, if you notice that a particular error occurs when someone clicks the Test button after typing a specific value in an application field, you can use that information to provide better user training. The support staff can also rely on that information as part of its troubleshooting strategy. In other words, the patterns you see in the error log are important to everyone, even though you might be the only one who understands the error log content completely. The following sections discuss using error logs for support tasks in more detail.

Defining Errors, Sources, and Solutions

An error log alone doesn't provide much information. However, when you place a number of error logs into a database and perform analysis on that database, you can begin seeing the patterns that support staff need to provide better application support. You can divide the output of the analysis into three categories:

  • Errors: The easiest information to obtain from the error logs is the errors. However, errors alone don't say much. It's when you say that errors are occurring for a specific group or when users perform specific activities that the error information becomes most useful. You may even find that the error information occurs in a certain area of the organization or on a particular machine type. All these patterns help support provide better information to users.

  • Sources: Errors occur due to a specific cause or source. For example, typing Hello into a field and clicking Test may not cause an error, but typing Hello World into the same field and clicking Test does cause the error. Meanwhile, clicking an associated button, Test2, never causes the problem. This source information is essential when creating scripts that support can use to localize a problem before arriving at a specific solution.

  • Solutions: Sometimes the error log data suggests a solution to a problem. Even before you create a fix for the problem (and all problems do require a fix at some point), you can use the error log information to provide a solution that overcomes the issue. Often, you can analyze the error logs and create such a short-term solution in hours, rather than the days needed to implement the permanent fix.

The error logs you obtain represent a source of information. In order to get the most from this information source, you normally must analyze the logs and then deduce a course of action based on that information. As with many aspects of computer science, analyzing error logs is more art than science, so you need to exercise your skills before you begin to see the patterns that error logs can provide.

Determining the Need for Direct Developer Interaction

As previously mentioned, no one understands the error log content like the developers who create the application do. This fact may frustrate developers who can look directly at the log and see that something terrible's happening that the user could easily prevent, but that's why, as they say, you earn the big money. In many cases, the support staff can look at the logs until they're quite blind and never figure out the solution that you can come up with in a few seconds. It's times like these when it's best not to hide in your office and hope no one sees you, but to roll up your sleeves and fix the problem.

Of course, once you prove your skill at resolving problems, users and support staff alike will have a tendency to come to you with every little problem. Most developers don't have time to address every problem that occurs with their application, so you need to provide a priority system to ensure the problem is handled at the lowest possible level. Yes, this approach is quite annoying and frustrating for the user who really doesn't care about your application in the first place. But the situation still requires that less skilled staff at least take a crack at the problem before you attempt to address it.

A proactive approach on your part can provide a sort of middle ground where you can reduce stress for everyone. Analyzing error logs over time and showing trends in problems can help you direct your efforts to the fires that you need to put out today so you don't encounter the wrath of the user tomorrow. For example, an error that occurs inconsistently, but at an increasing level, is a potential target for your attention. If you address the problem before too many people encounter it, you'll have the time to think about it properly — without the stress of the boss breathing down your neck.

Creating Support Logs

Error logs are only part of the solution. As you encounter problems, analyze them, and create solutions that support uses, you must also determine whether these fixes actually work and define how well they work. Most problems have multiple fixes. If you settle for the first fix that seems to address the problem, you may actually buy yourself more trouble later. Consequently, you need to create support logs that help the support staff track the efficiency of your solutions.

Relying on Error Log and Support Logs for Maintenance

The error logs you create can have a significant impact on how you perform application maintenance. For example, the error logs may show that only one or two people are encountering an error that's not only difficult to fix but expensive as well. Because the error logs help you understand the nature of the error better, you can use this information to place the maintenance task of fixing this error at a lower priority. However, error logs can also show that everyone is encountering a particular error, so you need to give the fix a high priority, no matter how much it costs to fix. The following sections describe using error logs for maintenance in more detail.

Prioritizing Maintenance Activities

Everyone wants their problem fixed today. The problem is that you only have so many hours in a given day and they aren't all available for fixing problems. It's quite likely, in fact, that even if you give up eating and sleeping, you won't address every problem in a single day, or a week for that matter. Problem resolution requires time, a lot of it in many cases. Besides, if you resolved every problem in your application today, what would you do tomorrow? Each day has a specific amount of work you can perform in it. In short, you need to prioritize maintenance activities to ensure that you address the tasks that you need to perform most, first.

Most prioritizing schemes have problems. In almost every case, a problem will appear on the list year after year and never receive the attention it needs. The unresolved problem will likely cause issues for someone who really does want to see a fix. To prevent the unresolved problem from appearing on your list, you should review the work list from time to time and increase the priority of longtime problems to ensure they receive a review. It's also important to clean your list — verify that the problem still exists before you do anything else with it.

Error logs can help you prioritize maintenance activities by pointing out how critical a particular application error has become. In fact, you should consider the following criteria when prioritizing maintenance activities for your applications:

  • Severity of the error (application crash versus inconvenience)

  • Number of people seeing the problem

  • Error source

  • Error effect (lost data is always bad)

  • Area affected (if you run a catalog business and data entry is experiencing the problem, you should probably fix it sooner rather than later)

  • Potential noncoding short-term problem solution effectiveness

Verifying Maintenance Requirements

Before you begin any maintenance task, make sure you understand all the maintenance requirements. You need to understand the problem completely and should already have a fix in mind. The error logs you collect can help you significantly in this regard, because they can provide you with precise information about the source and potential cause of the error. Of course, the error logs have to have the required information in them before you can use them in this way and you must perform specific exception handling to determine the particular cause of an error. If you handle every error using the System.Exception class, your error logs are going to be relatively useless and you'll waste hours looking for the problem before you can fix it.

Testing Maintenance Actions

After you complete a maintenance action, the error logs you create can help you define testing scripts. It's important to test any new code you create completely, but users are definitely going to complain if the fix you create doesn't actually fix anything. The very error logs that alerted you to the problem in the first place can provide the input you require to test the fix. A comparison of error log output can help you verify the effectiveness of a fix and make it possible to verify that you haven't introduced new problems as a result.

At no time should you consider abandoning the testing techniques described in Chapter 15. The maintenance action testing described in this section of the chapter is in addition to the normal testing you perform. It's still important to validate the application as a whole to ensure the user sees an increase in application reliability due to the fix you provide. Some developers will put a quick fix in place and end up creating more problems than the application had in the first place. This problem even occurs with shrink-wrapped software, as witnessed by the patch recalls a number of vendors have had to perform.

Deploying Patches

The final step in maintaining your application is to issue a patch. The patch must go through the complete testing and QA process that your application normally relies on to maintain application quality. Of course, you'll have to perform these checks while users are protesting outside your door, but you need to perform them nonetheless. One of the major problems with software today is that developers often bend to the will of the users to get a patch out quickly without the proper testing.

You'll very likely want to deploy the patch in stages. Distributing a testing release is always a good idea because you can't be sure how the patch will react on the production system. If the testing release works as anticipated, perform a local deployment next to see how the patch works with a larger group. Finally, distribute the patch on the enterprise level.

The final task to perform is to ensure everyone actually has the patch installed. Often, a small group of machines somehow misses the patching process. When these users begin calling into support with an error that looks very much like the one you just fixed, it could create chaos. Consequently, making sure that everyone has the patch installed will save you both time and effort.

Coding Your Application

This chapter has helped you understand some of the tools you can use to make support and maintenance of your application easier. What you should take away from this chapter is that the user won't provide the information you need, so you must have some type of error log support for your application, even if that support is based on the event log. If you have a large application, you may want to use a third-party product to provide the error log support you require. However, most developers can get by with one of the hand-rolled solutions found in this chapter.

If you haven't included plans yet for an error-logging module in your application, now is the time to do it. Error logging is incredibly important for your sanity, not to mention the sanity of your coworkers. You don't have to create anything fancy. In fact, when it comes to error logs, simpler is better because the application is in an unstable state in most situations when you need an error log. Make sure you define the specifics of error-log handling for your application at the outset. For example, many developers make the mistake of not making the error log available at the right times. Select a format that will work well for your organization as well. Finally, decide on a method for handling log file recycling.

Chapter 18 begins a new section of the book. In Chapter 18, you examine the performance triangle in detail. By using the techniques in this chapter and those that follow (Chapters 19 through 21), you can reduce your need for the error log techniques found in this chapter. No, you won't get rid of the error log completely, but consider each time you don't need it to answer a support or maintenance need as a gift. The best way to avoid problems is to prevent them from happening at all, which is the purpose of this next section of the book.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset