//=============================================================================== // Microsoft Exception Management Application Block for .NET // http://msdn.microsoft.com/library/en-us/dnbda/html/emab-rm.asp // // ExceptionManager.cs // This file contains the ExceptionManager class, which manages all publishing // of exceptions, and the default publisher class, which publishes exception // information to the Event Log. // // For more information see the Implementing the Exception Manager Component // section of the Exception Management Application Block Implementation Overview. //=============================================================================== // Copyright (C) 2000-2001 Microsoft Corporation // All rights reserved. // THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY // OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT // LIMITED TO THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR // FITNESS FOR A PARTICULAR PURPOSE. //=============================================================================== using System; using System.Collections.Specialized; using System.Configuration; using System.Reflection; using System.Resources; using System.Runtime.Serialization; using System.Security; using System.Security.Principal; using System.Text; using System.Threading; using System.Xml; namespace AyAEM.ExceptionManagement { #region ExceptionManager Class /// /// The Exception Manager class manages the publishing of exception information based on settings in the configuration file. /// public sealed class ExceptionManager { /// /// Private constructor to restrict an instance of this class from being created. /// private ExceptionManager() { } // Member variable declarations private const string EXCEPTIONMANAGEMENT_CONFIG_SECTION = "exceptionManagement"; private readonly static string EXCEPTIONMANAGER_NAME = typeof(ExceptionManager).Name; // Resource Manager for localized text. // Stuff fixed when access is denied to write and when there is not way to send email private static ResourceManager resourceManager = new ResourceManager("AyAEM.ExceptionManagerText", Assembly.GetExecutingAssembly()); /// /// Static method to publish the exception information. /// /// The exception object whose information should be published. public static void Publish(Exception exception) { ExceptionManager.Publish(exception, null); } /// /// Static method to publish the exception information and any additional information. /// /// The exception object whose information should be published. /// A collection of additional data that should be published along with the exception information. public static void Publish(Exception exception, NameValueCollection additionalInfo) { try { #region Load the AdditionalInformation Collection with environment data. // Create the Additional Information collection if it does not exist. if (null == additionalInfo) additionalInfo = new NameValueCollection(); // Add environment information to the information collection. try { additionalInfo.Add(EXCEPTIONMANAGER_NAME + ".MachineName", Environment.MachineName); } catch(SecurityException) { additionalInfo.Add(EXCEPTIONMANAGER_NAME + ".MachineName", resourceManager.GetString("RES_EXCEPTIONMANAGEMENT_PERMISSION_DENIED")); } catch { additionalInfo.Add(EXCEPTIONMANAGER_NAME + ".MachineName", resourceManager.GetString("RES_EXCEPTIONMANAGEMENT_INFOACCESS_EXCEPTION")); } try { additionalInfo.Add(EXCEPTIONMANAGER_NAME + ".TimeStamp", DateTime.Now.ToString()); } catch(SecurityException) { additionalInfo.Add(EXCEPTIONMANAGER_NAME + ".TimeStamp",resourceManager.GetString("RES_EXCEPTIONMANAGEMENT_PERMISSION_DENIED")); } catch { additionalInfo.Add(EXCEPTIONMANAGER_NAME + ".TimeStamp", resourceManager.GetString("RES_EXCEPTIONMANAGEMENT_INFOACCESS_EXCEPTION")); } try { additionalInfo.Add(EXCEPTIONMANAGER_NAME + ".FullName", Assembly.GetExecutingAssembly().FullName); } catch(SecurityException) { additionalInfo.Add(EXCEPTIONMANAGER_NAME + ".FullName", resourceManager.GetString("RES_EXCEPTIONMANAGEMENT_PERMISSION_DENIED")); } catch { additionalInfo.Add(EXCEPTIONMANAGER_NAME + ".FullName", resourceManager.GetString("RES_EXCEPTIONMANAGEMENT_INFOACCESS_EXCEPTION")); } try { additionalInfo.Add(EXCEPTIONMANAGER_NAME + ".AppDomainName", AppDomain.CurrentDomain.FriendlyName); } catch(SecurityException) { additionalInfo.Add(EXCEPTIONMANAGER_NAME + ".AppDomainName", resourceManager.GetString("RES_EXCEPTIONMANAGEMENT_PERMISSION_DENIED")); } catch { additionalInfo.Add(EXCEPTIONMANAGER_NAME + ".AppDomainName", resourceManager.GetString("RES_EXCEPTIONMANAGEMENT_INFOACCESS_EXCEPTION")); } try { additionalInfo.Add(EXCEPTIONMANAGER_NAME + ".ThreadIdentity", Thread.CurrentPrincipal.Identity.Name); } catch(SecurityException) { additionalInfo.Add(EXCEPTIONMANAGER_NAME + ".ThreadIdentity", resourceManager.GetString("RES_EXCEPTIONMANAGEMENT_PERMISSION_DENIED")); } catch { additionalInfo.Add(EXCEPTIONMANAGER_NAME + ".ThreadIdentity", resourceManager.GetString("RES_EXCEPTIONMANAGEMENT_INFOACCESS_EXCEPTION")); } try { additionalInfo.Add(EXCEPTIONMANAGER_NAME + ".WindowsIdentity", WindowsIdentity.GetCurrent().Name); } catch(SecurityException) { additionalInfo.Add(EXCEPTIONMANAGER_NAME + ".WindowsIdentity", resourceManager.GetString("RES_EXCEPTIONMANAGEMENT_PERMISSION_DENIED")); } catch { additionalInfo.Add(EXCEPTIONMANAGER_NAME + ".WindowsIdentity", resourceManager.GetString("RES_EXCEPTIONMANAGEMENT_INFOACCESS_EXCEPTION")); } #endregion #region Publish the exception based on Configuration Settings // Check for any settings in config file. if (ConfigurationManager.GetSection(EXCEPTIONMANAGEMENT_CONFIG_SECTION) == null) { // Publish the exception and additional information to the default publisher if no settings are present. PublishToDefaultPublisher(exception, additionalInfo); } else { // Get settings from config file ExceptionManagementSettings config = (ExceptionManagementSettings)ConfigurationManager.GetSection(EXCEPTIONMANAGEMENT_CONFIG_SECTION); // If the mode is not equal to "off" call the Publishers, otherwise do nothing. if (config.Mode == ExceptionManagementMode.On) { // If no publishers are specified, use the default publisher. if (config.Publishers == null || config.Publishers.Count == 0) { // Publish the exception and additional information to the default publisher if no mode is specified. PublishToDefaultPublisher(exception, additionalInfo); } else { #region Iterate through the publishers // Loop through the publisher components specified in the config file. foreach(PublisherSettings Publisher in config.Publishers) { // Call the Publisher component specified in the config file. try { // Verify the publishers mode is not set to "OFF". // This publisher will be called even if the mode is not specified. // The mode must explicitly be set to OFF to not be called. if (Publisher.Mode == PublisherMode.On) { //if (exception == null || !Publisher.IsExceptionFiltered(exception.GetType())) if (exception != null) { // Publish the exception and any additional information PublishToCustomPublisher(exception, additionalInfo, Publisher); } } } // Catches any failure to call a custom publisher. catch(Exception e) { // Publish the exception thrown when trying to call the custom publisher. PublishInternalException(e,null); // Publish the original exception and additional information to the default publisher. PublishToDefaultPublisher(exception, additionalInfo); } // End Catch block. } // End foreach loop through publishers. #endregion } // End else statement when custom publishers are in the config settings. } // End else statement where config settings are not set to "OFF" } // End else statement when config settings are provided. #endregion } catch(Exception e) { // Publish the exception thrown within the ExceptionManager. PublishInternalException(e,null); // Publish the original exception and additional information to the default publisher. PublishToDefaultPublisher(exception, additionalInfo); } } // End Publish(Exception exception, NameValueCollection AdditionalInfo) /// /// Private static helper method to publish the exception information to a custom publisher. /// /// The exception object whose information should be published. /// A collection of additional data that should be published along with the exception information. /// The PublisherSettings that contains the values of the publishers configuration. private static void PublishToCustomPublisher(Exception exception, NameValueCollection additionalInfo, PublisherSettings publisher) { try { // Check if the exception format is "xml". if (publisher.ExceptionFormat == PublisherFormat.Xml) { // If it is load the IExceptionXmlPublisher interface on the custom publisher. // Instantiate the class IExceptionXmlPublisher XMLPublisher = (IExceptionXmlPublisher)Activate(publisher.AssemblyName, publisher.TypeName); // Publish the exception and any additional information //XMLPublisher.Publish(SerializeToXml(exception, additionalInfo),publisher.OtherAttributes); } // Otherwise load the IExceptionPublisher interface on the custom publisher. else { // Instantiate the class IExceptionPublisher Publisher = (IExceptionPublisher)Activate(publisher.AssemblyName, publisher.TypeName); // Publish the exception and any additional information Publisher.Publish(exception, additionalInfo, publisher.OtherAttributes); } } catch(Exception e) { CustomPublisherException publisherException = new CustomPublisherException(resourceManager.GetString("RES_CUSTOM_PUBLISHER_FAILURE_MESSAGE"), publisher.AssemblyName, publisher.TypeName, publisher.ExceptionFormat, e); publisherException.AdditionalInformation.Add(publisher.OtherAttributes); throw(publisherException); } } /// /// Private static helper method to publish the exception information to the default publisher. /// /// The exception object whose information should be published. /// A collection of additional data that should be published along with the exception information. private static void PublishToDefaultPublisher(Exception exception, NameValueCollection additionalInfo) { // Get the Default Publisher DefaultPublisher Publisher = new DefaultPublisher(); // Publish the exception and any additional information Publisher.Publish(exception, additionalInfo, null); } /// /// Private static helper method to publish the exception information to the default publisher. /// /// The exception object whose information should be published. /// A collection of additional data that should be published along with the exception information. internal static void PublishInternalException(Exception exception, NameValueCollection additionalInfo) { // Get the Default Publisher DefaultPublisher Publisher = new DefaultPublisher("Application", resourceManager.GetString("RES_EXCEPTIONMANAGER_INTERNAL_EXCEPTIONS")); // Publish the exception and any additional information Publisher.Publish(exception, additionalInfo, null); } /// /// Private helper function to assist in run-time activations. Returns /// an object from the specified assembly and type. /// /// Name of the assembly file (w/out extension) /// Name of the type to create /// Instance of the type specified in the input parameters. private static object Activate(string assembly, string typeName) { return AppDomain.CurrentDomain.CreateInstanceAndUnwrap(assembly, typeName); } /// /// Public static helper method to serialize the exception information into XML. /// /// The exception object whose information should be published. /// A collection of additional data that should be published along with the exception information. public static XmlDocument SerializeToXml(Exception exception, NameValueCollection additionalInfo) { try { // Variables representing the XmlElement names. string ROOT = resourceManager.GetString("RES_XML_ROOT"); string ADDITIONAL_INFORMATION = resourceManager.GetString("RES_XML_ADDITIONAL_INFORMATION"); string EXCEPTION = resourceManager.GetString("RES_XML_EXCEPTION"); string STACK_TRACE = resourceManager.GetString("RES_XML_STACK_TRACE"); // Create a new XmlDocument. XmlDocument xmlDoc = new XmlDocument(); // Create the root node. XmlElement root = xmlDoc.CreateElement(ROOT); xmlDoc.AppendChild(root); // Variables to hold values while looping through the exception chain. XmlElement element; XmlElement exceptionAddInfoElement; XmlElement stackTraceElement; XmlText stackTraceText; XmlAttribute attribute; #region Add values from AdditionalInfo to the XML Doc // Check if the collection has values. if (additionalInfo != null && additionalInfo.Count > 0) { // Create the element for the collection. element = xmlDoc.CreateElement(ADDITIONAL_INFORMATION); // Loop through the collection and add the values as attributes on the element. foreach (string i in additionalInfo) { attribute = xmlDoc.CreateAttribute(i.Replace(" ", "_")); attribute.Value = additionalInfo.Get(i); element.Attributes.Append(attribute); } // Add the element to the root. root.AppendChild(element); } #endregion if (exception == null) { // Create an empty exception element. element = xmlDoc.CreateElement(EXCEPTION); // Append to the root node. root.AppendChild(element); } else { #region Loop through each exception class in the chain of exception objects and record its information // Loop through each exception class in the chain of exception objects. Exception currentException = exception; // Temp variable to hold InnerException object during the loop. XmlElement parentElement = null; // Temp variable to hold the parent exception node during the loop. do { // Create the exception element. element = xmlDoc.CreateElement(EXCEPTION); // Add the exceptionType as an attribute. attribute = xmlDoc.CreateAttribute("ExceptionType"); attribute.Value = currentException.GetType().FullName; element.Attributes.Append(attribute); #region Loop through the public properties of the exception object and record their value // Loop through the public properties of the exception object and record their value. PropertyInfo[] aryPublicProperties = currentException.GetType().GetProperties(); NameValueCollection currentAdditionalInfo; foreach (PropertyInfo p in aryPublicProperties) { // Do not log information for the InnerException or StackTrace. This information is // captured later in the process. if (p.Name != "InnerException" && p.Name != "StackTrace") { // Only record properties whose value is not null. if (p.GetValue(currentException,null) != null) { // Check if the property is AdditionalInformation and the exception type is a BaseApplicationException. if (p.Name == "AdditionalInformation" && currentException is BaseApplicationException) { // Verify the collection is not null. if (p.GetValue(currentException,null) != null) { // Cast the collection into a local variable. currentAdditionalInfo = (NameValueCollection)p.GetValue(currentException,null); // Verify the collection has values. if (currentAdditionalInfo.Count > 0) { // Create element. exceptionAddInfoElement = xmlDoc.CreateElement(ADDITIONAL_INFORMATION); // Loop through the collection and add values as attributes. foreach (string i in currentAdditionalInfo) { attribute = xmlDoc.CreateAttribute(i.Replace(" ", "_")); attribute.Value = currentAdditionalInfo.Get(i); exceptionAddInfoElement.Attributes.Append(attribute); } element.AppendChild(exceptionAddInfoElement); } } } // Otherwise just add the ToString() value of the property as an attribute. else { attribute = xmlDoc.CreateAttribute(p.Name); attribute.Value = p.GetValue(currentException,null).ToString(); element.Attributes.Append(attribute); } } } } #endregion #region Record the exception StackTrace // Record the StackTrace within a separate element. if (currentException.StackTrace != null) { // Create Stack Trace Element. stackTraceElement = xmlDoc.CreateElement(STACK_TRACE); stackTraceText = xmlDoc.CreateTextNode(currentException.StackTrace.ToString()); stackTraceElement.AppendChild(stackTraceText); element.AppendChild(stackTraceElement); } #endregion // Check if this is the first exception in the chain. if (parentElement == null) { // Append to the root node. root.AppendChild(element); } else { // Append to the parent exception object in the exception chain. parentElement.AppendChild(element); } // Reset the temp variables. parentElement = element; currentException = currentException.InnerException; // Continue looping until we reach the end of the exception chain. } while (currentException != null); #endregion } // Return the XmlDocument. return xmlDoc; } catch(Exception e) { throw new SerializationException(resourceManager.GetString("RES_EXCEPTIONMANAGEMENT_XMLSERIALIZATION_EXCEPTION"),e); } } } #endregion #region DefaultPublisher Class /// /// Component used as the default publishing component if one is not specified in the config file. /// public sealed class DefaultPublisher : IExceptionPublisher { /// /// Default Constructor. /// public DefaultPublisher() { } /// /// Constructor allowing the log name and application names to be set. /// /// The name of the log for the DefaultPublisher to use. /// The name of the application. This is used as the Source name in the event log. public DefaultPublisher(string logName, string applicationName) { //this.logName = logName; this.applicationName = applicationName; } // Resource Manager for localized text. // Stuff fixed when access is denied to write and when there is not way to send email private static ResourceManager resourceManager = new ResourceManager("AyAEM.ExceptionManagerText", Assembly.GetExecutingAssembly()); // Member variable declarations //private string logName = "Application"; private string applicationName = resourceManager.GetString("RES_EXCEPTIONMANAGER_PUBLISHED_EXCEPTIONS"); private const string TEXT_SEPARATOR = "*********************************************"; /// /// Method used to publish exception information and additional information. /// /// The exception object whose information should be published. /// A collection of additional data that should be published along with the exception information. /// A collection of any additional attributes provided in the config settings for the custom publisher. public void Publish(Exception exception, NameValueCollection additionalInfo, NameValueCollection configSettings) { // Load Config values if they are provided. if (configSettings != null) { if (configSettings["applicationName"] != null && configSettings["applicationName"].Length > 0) applicationName = configSettings["applicationName"]; //if (configSettings["logName"] != null && configSettings["logName"].Length > 0) logName = configSettings["logName"]; } // Verify that the Source exists before gathering exception information. VerifyValidSource(); // Create StringBuilder to maintain publishing information. StringBuilder strInfo = new StringBuilder(); #region Record the contents of the AdditionalInfo collection // Record the contents of the AdditionalInfo collection. if (additionalInfo != null) { // Record General information. strInfo.AppendFormat("{0}General Information {0}{1}{0}Additional Info:", Environment.NewLine, TEXT_SEPARATOR); foreach (string i in additionalInfo) { strInfo.AppendFormat("{0}{1}: {2}", Environment.NewLine, i, additionalInfo.Get(i)); } } #endregion if (exception == null) { strInfo.AppendFormat("{0}{0}No Exception object has been provided.{0}", Environment.NewLine); } else { #region Loop through each exception class in the chain of exception objects // Loop through each exception class in the chain of exception objects. Exception currentException = exception; // Temp variable to hold InnerException object during the loop. int intExceptionCount = 1; // Count variable to track the number of exceptions in the chain. do { // Write title information for the exception object. strInfo.AppendFormat("{0}{0}{1}) Exception Information{0}{2}", Environment.NewLine, intExceptionCount.ToString(), TEXT_SEPARATOR); strInfo.AppendFormat("{0}Exception Type: {1}", Environment.NewLine, currentException.GetType().FullName); strInfo.AppendFormat("{0}{0}---------------------{0}{1}", Environment.NewLine, " "); #region Loop through the public properties of the exception object and record their value // Loop through the public properties of the exception object and record their value. PropertyInfo[] aryPublicProperties = currentException.GetType().GetProperties(); NameValueCollection currentAdditionalInfo; foreach (PropertyInfo p in aryPublicProperties) { // Do not log information for the InnerException or StackTrace. This information is // captured later in the process. if (p.Name != "InnerException" && p.Name != "StackTrace") { if (p.GetValue(currentException,null) == null) { strInfo.AppendFormat("{0}{1}: NULL", Environment.NewLine, p.Name); } else { // Loop through the collection of AdditionalInformation if the exception type is a BaseApplicationException. if (p.Name == "AdditionalInformation" && currentException is BaseApplicationException) { // Verify the collection is not null. if (p.GetValue(currentException,null) != null) { // Cast the collection into a local variable. currentAdditionalInfo = (NameValueCollection)p.GetValue(currentException,null); // Check if the collection contains values. if (currentAdditionalInfo.Count > 0) { strInfo.AppendFormat("{0}AdditionalInformation:", Environment.NewLine); // Loop through the collection adding the information to the string builder. for (int i = 0; i < currentAdditionalInfo.Count; i++) { strInfo.AppendFormat("{0}{1}: {2}", Environment.NewLine, currentAdditionalInfo.GetKey(i), currentAdditionalInfo[i]); } } } } // Otherwise just write the ToString() value of the property. else { strInfo.AppendFormat("{0}{1}: {2}", Environment.NewLine, p.Name, p.GetValue(currentException,null)); } } } } #endregion #region Record the Exception StackTrace // Record the StackTrace with separate label. if (currentException.StackTrace != null) { strInfo.AppendFormat("{0}{0}StackTrace Information{0}{1}", Environment.NewLine, TEXT_SEPARATOR); strInfo.AppendFormat("{0}{1}", Environment.NewLine, currentException.StackTrace); } #endregion // Reset the temp exception object and iterate the counter. currentException = currentException.InnerException; intExceptionCount++; } while (currentException != null); #endregion } // Write the entry to the event log. //WriteToLog(strInfo.ToString(), EventLogEntryType.Error); } /// /// Helper function to write an entry to the Event Log. /// /// The entry to enter into the Event Log. /// The EventLogEntryType to be used when the entry is logged to the Event Log. //private void WriteToLog(string entry, EventLogEntryType type) //{ // try // { // // Write the entry to the Event Log. // //EventLog.WriteEntry(applicationName,entry,type); // } // catch(SecurityException e) // { // throw new SecurityException(String.Format(resourceManager.GetString("RES_DEFAULTPUBLISHER_EVENTLOG_DENIED"), applicationName),e); // } //} private void VerifyValidSource() { try { //Set permissions to ASPNET User in HKEY_LOCAL_MACHINE\System\CurrentControlSet\Services\EventLog //if (!EventLog.SourceExists(applicationName)) //{ // EventLog.CreateEventSource(applicationName, logName); //} } catch(SecurityException e) { throw new SecurityException(String.Format(resourceManager.GetString("RES_DEFAULTPUBLISHER_EVENTLOG_DENIED"), applicationName),e); } } } #endregion }