首页    期刊浏览 2024年11月25日 星期一
登录注册

文章基本信息

  • 标题:Building Plug-ins with C# .NET Part 4: Logging and Deployment
  • 作者:Nathan Good
  • 期刊名称:Dev Source
  • 出版年度:2004
  • 卷号:June 2004
  • 出版社:DEVsource

Building Plug-ins with C# .NET Part 4: Logging and Deployment

Nathan Good

This is the last article in a four-part series discussing techniques you can use to build solid plug-ins using C# .NET. I'll show you tweaks you can make before implementing an app or service, to take advantage of a plug-in architecture. One of them—a subject that is important in Web or Windows services—is logging. Another is using the Setup and Deployment project in the Visual Studio IDE for deployment.

Using the Exception Manager Application Block

Application Blocks let you easily provide extra functionality to an application and decrease the time you have to invest in creating some of the pieces. Among the Application Blocks are the Configuration Management Application Block, Data Application Block, and Exception Management Application Block. In this article, I'll show you how to use the Exception Management Application Block to log errors and messages as they occur in the test application and plug-ins.

Remember that the plug-ins must handle their own errors and not propagate them to the application that is calling them? In many cases, all the plug-in can do is catch all errors and log the details of the errors somewhere. Using the Exception Management Application Block, it's easy for an application to write the details of an exception out to the Windows Event Log. This has a couple benefits: the Event Log is managed by the operating system, so the app doesn't need to concern itself with logging and the management of output files, etc.; and the Event Log provides ways for a normal person to remotely view messages through the Microsoft Management Console.

Before you can begin using the Microsoft Exception Management Application Block, first download it from Microsoft's site. You get the full source code in an MSI file, so you can compile the block yourself and view the documentation and samples that come with the download.

A note, first, about using these Application Blocks in a team environment. When I use the Exception Management Application Blocks in a solution that is not under source control, I sometimes add the Microsoft.ApplicationBlocks.ExceptionManagement and Microsoft.ApplicationBlocks.ExceptionManagement.Interfaces projects to the solution from the installdir\Code\CS\ directory, where installdir is the directory in which the Application Block was installed.

If the solution is under source control, you should follow the guidelines established for your company or project; I've seen situations where components like these are checked into source control both in a central location and in a per-solution basis. There are valid arguments for doing it either way. On one hand, centrally locating the Application Block code can give your projects at least one benefit; if the projects are modified in any way after they were downloaded, all the projects that refer to them benefit from the changes. On the other hand, this means any change requires regression testing for all of your applications. One small change could break a lot of code.

Having the Application Block checked into source control under each separate solution turns the solution into an island that is safe from changes made to the same Application Block elsewhere. However, this can introduce an administrative nightmare should any Application Blocks be updated.

Alternatively, the Exception Management Application Block solution can be compiled. Then you can either add the assembly from the Application Block to the global assembly cache, or make a reference to the assembly instead of adding the project to the solution. Depending on how you use the Application Blocks, this might be the cleanest route. But it's not without extra work. The Assembly must strongly typed and added to the global assembly cache on the servers on which the app will run. Some administrators may become a little cranky about making that kind of change without a lot of testing.

After you add the references to the Microsoft.ApplicationBlocks.ExceptionManagement project or assembly to the app, add:

using Microsoft.ApplicationBlocks.ExceptionManagement;

to the top of the class that will use ExceptionManagment. In the project for the solution's testing app, AuthenticationPlugin.UI.UITester, I added the statement to the top of the Form1.cs file. In the Form1 class, there are several try...catch blocks that called a method I had created called ShowError. The purpose of this method is to pop up a message box from the testing app that tells me that something went wrong, such as an invalid cast or a plug-in that was missing the implementation class altogether. In the btnLogin_Click method, I changed the code that catches the InvalidCastException to:

catch ( System.InvalidCastException ice )

{
   /* The object didn't implement the IAuthenticationPlugin interface, and could
    * not be cast correctly */
   ShowError( "The authentication object does not implement the correct interface." );
   ExceptionManager.Publish( ice );
}

The new call to ExceptionManger.Publish, which accepts the InvalidCastException ice, publishes the details of the exception to the Application event log. You can view the log entry by going to Event Log under Administrative Tools in the Control Panel. The details will look like:

General Information 

*********************************************
Additional Info:
ExceptionManager.MachineName: MYPC
ExceptionManager.TimeStamp: 5/31/2004 10:24:05 AM
ExceptionManager.FullName: Microsoft.ApplicationBlocks.ExceptionManagement,
  Version=1.0.1612.16079, Culture=neutral, PublicKeyToken=null
ExceptionManager.AppDomainName: AuthenticationPlugin.UI.UITester.exe
ExceptionManager.ThreadIdentity: 
ExceptionManager.WindowsIdentity: MYDOMAIN\myuser

1) Exception Information
*********************************************
Exception Type: System.InvalidCastException
Message: Specified cast is not valid.
TargetSite: Void btnLogin_Click(System.Object, System.EventArgs)
HelpLink: NULL
Source: AuthenticationPlugin.UI.UITester

StackTrace Information
*********************************************
at AuthenticationPlugin.UI.UITester.Form1.btnLogin_Click(Object sender, 
EventArgs e) in c:\temp\autpluginspart4\authenticationpluginpart4
   authenticationplugin.ui.uitester\form1.cs:line 155

For more information, see Help and Support Center at
http://go.microsoft.com/fwlink/events.asp.

That's quite a lot of information. By passing the exception into ExceptionManager.Publish, you can get the exception name, message, and stack trace. Even the line of code in which the exception was thrown is included in the output.

The Publish method has two overrides, so it can also take a NameValueCollection that contains additional information as a parameter. The additional information can include information about the state of the object, the application, or any other message that you might want to share with the person viewing the logs, to help in tracking down and resolving the problem.

Another very useful feature about the Exception Management Application Block is that it can be extended with custom publishers. With no configuration out of the box (or MSI file), it logs to the Event Log. It can also log to a database, to a file, or to any medium that you can persist to with a custom publisher.

Keeping A Log

The Exception Management Application Block is useful for writing out information about exceptions; however, in services it is often necessary to do quite a bit more logging and very useful to have different levels of logging.

Coming from a Java background, I really enjoyed the log4j framework available from the Apache Web site. The log4net project offers almost all of the same features of the log4j framework, except in an assembly that can be used in a .NET project.

Of those features, one of the most useful I found when logging in services is the ability to set logging levels on a per-class basis. Another very useful feature is the ability to very easily create a rolling-log logging system, where logs are "rolled over" when the reach a configured size. The number of log files that are retained is also configurable, making this is an easy way to log quite a bit of information out to files without being concerned about filling up the disk.

You can download log4net from the Apache Web site. Like using the Exception Management Application Block (or any other third-party library, for that matter), you will have to compile and install the library in a manner that is consistent with the rest of your projects. Once you have added the appropriate reference to the log4net assembly, add the line:

using log4net;

at the top of the class that uses the log4net framework. After adding the references and using statement, there are three tasks that need to be done: add the configuration for log4net in the application's configuration file, declare an instance of log4net.ILog, and use the Configure method to set up and configure the log4net logger.

In these examples, I'll assume that the configuration for log4net is contained in the application's configuration file. It doesn't have to be located there; the Configure method allows the name of the file to be passed in as an argument. The logger configuration section will look like this in the AuthenticationPlugins.UI.UITester application:

Listing 1

<logger name="AuthenticationPlugins.UI.UITester.Form1">
  <level value="INFO"/>
  <appender-ref ref="RollingFileAppender" />
</logger>

The logger uses an appender to know where to log the messages. A ConsoleAppender for instance, will print the message out to the console, provided the log message is in the correct level. The level sets the minimum severity that will be printed to the appender: DEBUG, INFO, WARN, ERROR, or FATAL (listed in order from least severe to most severe). If the level is set to INFO, as it is above, any message higher than INFO will be sent to the appender.

The appender shown in Listing 1 is the RollingFileAppender, which has its own configuration section as shown in Listing 2.

Listing 2

<appender name="RollingFileAppender" type="log4net.Appender.RollingFileAppender">
  <file value="C:\\Temp\\test.log" />
  <appendToFile value="true"/>
  <rollingStyle value="Size"/>
  <maxSizeRollBackups value="10" />
  <maximumFileSize value="1000KB" />
  <staticLogFileName value="true" />
  <layout type="log4net.Layout.PatternLayout">
  <conversionPattern value="[%d] %-5p : %m%n" />
  </layout>
</appender>

This particular configuration sets the RollingFileAppender to write to a file called C:\Temp\test.log. Once the file reaches 1000KB, the appender rolls the file over to C:\Temp\test.log.1. It will keep rolling the files over until there are 10 of them, set by maxSizeRollBackups above. I won't go into the conversionPattern here, but just mention that the configuration above prints a line that is similar to the one show here in the log file:

[2004-06-01 10:00:00,343] INFO : Loading plug-ins.

Both the logger and appender sections are nested inside the log4net configuration section:

<log4net>
     <logger>
          ...
     </logger>
     <appender>
          ...
     </appender>
</log4net>

After building the log4net configuration section and adding it to the configuration file, you must add a handler for the section (for more information on why, see Part 2):

<configSections>
  <section name="log4net"
   type="log4net.Config.Log4NetConfigurationSectionHandler, 
   log4net">
</configSections>

Now that the application's configuration section is added and a handler is set up to process it, I'll move on to the remaining two tasks for setting up log4net: declaring an object that uses the log4net.ILog interface, and setting up the object in the application.

To declare and create an instance of the logger, type:

private static readonly log4net.ILog Logger =
  log4net.LogManager.GetLogger( typeof(Form1) );

just under the ArrayList plugins declaration line for Form1. Back in Listing 1, I named the logger the fully-qualified class name of Form1. I can really name it anything, like "moo", and retrieve it with that name, but I've found that naming it the class name makes referencing the logger in the configuration file much easier.

The last thing to do before you can be off and using log4net to merrily log messages is to initialize it in the code by adding:

log4net.Config.DOMConfigurator.Configure( 
  new System.IO.FileInfo( 
    AppDomain.CurrentDomain.SetupInformation.ConfigurationFile) );

in a method that will automatically be called when the application is loaded, like the Form1 constructor. Once the Configure method is called, you can call the methods of the ILog interface on Logger to log messages:

Logger.Info( "Starting UITester." );

The ILog interface has methods that correspond with the log levels. Debug is used for DEBUG level messages, Info logs INFO messages, and so on.

Deployment

The Visual Studio IDE provides a relatively painless way to build MSI files for deployment. The Deployment projects can refer to other projects in the solution to build installation wizards that install the output of a project into a folder, with all of the dependencies and files.

Ideally, the setup for each plug-in should be in a separate MSI file to keep the plug-in truly modular. The following few paragraphs go over creating your own projects for deployment.

To create a deployment project for one of the plug-ins, first open the solution in the Visual Studio IDE. Open the Solution Explorer window from the View menu if it's not already open. Then highlight the plug-in project in the Solution Explorer that you want to deploy. For the purposes of this example, let's assume you want to deploy the AuthenticationPlugin.Plugins.LdapAuthenticator project. With the project highlighted, pick Add Project->New Project from the File menu.

In the Add New Project window, pick Setup and Deployment Projects from the Project Types list, and then choose Setup Project from the Templates list. A new project will appear in the solution. Next, you need to add the output of the AuthenticationPlugin.Plugins.LdapAuthenticator project so you can install it. To add the output of the project, highlight the LdapAuthenticatorPluginSetup project in the solution explorer and click Add->Project Output from the Project menu. In the Add Project Output Group box, select AuthenticationPlugin.Plugins.LdapAuthenticator from the Project list and select Primary Output from the list below it. This automatically adds the assembly created by AuthenticationPlugin.Plugins.LdapAuthenticatorto the setup project, and also adds all the dependencies that the IDE finds.

With only the project output in the setup project, the configuration file that was created in Part 3 will not be deployed with the plug-in. Never fear; other types of files from the project can be added to the LdapAuthenticationPluginSetup project. To add other files, such as the custom configuration files, highlight the LdapAuthenticatorPluginSetup project in the solution explorer and again select Add->Project Output from the Project menu. Select the AuthenticationPlugin.Plugins.LdapAuthenticator project from the list again, but this time select Content Files from the list instead of Primary Ouput. Now, make sure the configuration file is set up as a content file, by changing the properties on the AuthenticationPlugin.Plugins.LdapAuthenticator.dll.config file to use Content as the Build Action property. That's it. When the LdapAuthenticationPluginSetup project is built, the custom configuration file will be included along with the necessary assemblies.

Summary

This article, the last in a four-part series focusing on concepts that can be used to build modular applications using plug-ins, covered logging, a finishing touch that is a useful addition to any application. Two methods of logging exceptions and messages are using the Microsoft Exception Management Application Block, and the log4net framework.

Both the Exception Management Application Block and log4net can be extended to log exceptions and messages to different locations. Unlike the Application Block, log4net lends itself to logging messages that don't only occur inside a try catch block, such as informational or debug messages.

Finally, I covered using the Visual Studio IDE to create deployment projects to deploy some of the plug-ins that I've created throughout the series. The downloadable solution accompanying this article contains a few sample deployment projects that when built install a plug-in and its configuration files.

I hope this four-part series has given you some ideas that will help you deploy a modular, plug-in style application easily. At the very least, I hope you will have picked up some pointers on using reflection, interfaces, XML configuration, logging, and deployment.

I did not re-distribute the logging packages in the attached files. However, there is code wrapped with #if directives that will allow you to run the solution after you have downloaded the packages and linked to them. Uncomment the appropriate #define statement at the top of Form1.cs to watch logging in action.

Copyright © 2004 Ziff Davis Media Inc. All Rights Reserved. Originally appearing in Dev Source.

联系我们|关于我们|网站声明
国家哲学社会科学文献中心版权所有