65.9K
CodeProject 正在变化。 阅读更多。
Home

使用 ELMAH 将错误记录到 Windows 事件日志

starIconstarIconstarIconstarIconstarIcon

5.00/5 (5投票s)

2012 年 3 月 5 日

Apache

5分钟阅读

viewsIcon

26423

downloadIcon

575

使用 ELMAH 将错误记录到 Windows 事件日志

目录

引言

在 ASP.NET 应用程序中记录未处理错误的流行库之一是 ELMAH。它有很多优点,包括使用方便且易于集成到项目中。而且有很多关于如何使用 ELMAH 的文章。

但 ELMAH 有一个缺点。它无法将错误保存到 Windows 事件日志。ELMAH 可以将错误保存到文本文件、数据库,或者存储在内存中,或者通过电子邮件发送错误。它还带有一个用于查看错误的内置 Web 界面。但您会同意,保存到事件日志是一种更通用的方法。尤其是当出现与写入数据库或文件相关的错误时。而且,如果您选择 ELMAH 的内存存储,这种存储是临时的,并且缓冲区是有限的。

在我们的项目中,我们希望 ELMAH 同时将错误存储在数据库和 EventLog 中。

选择解决方案

有两种方法可以扩展 ELMAH 库以将错误保存到 EventLog

选项 1

您可以实现一个自定义日志提供程序,继承自 Elmah.ErrorLog 类并重写 GetError()GetErrors()Log() 方法。在这种情况下,它将像其他 ELMAH 日志提供程序一样工作,这些提供程序将错误保存到数据库、文件或内存中。但存在两个问题。

第一个问题是 ELMAH 一次只能运行一个日志提供程序。ELMAH 日志提供程序不仅写入错误,还读取错误以通过其 Web 界面显示。因此,您需要指定一个存储和读取的源。

第二个问题是 ELMAH 以自己的 XML 格式存储错误信息。这需要存储尽可能多的结构化信息,以便将来在 Web 界面中显示。因此,如果您要实现自定义日志提供程序,还需要以 ELMAH XML 格式将错误信息存储到 EventLog 中。但这不方便人类阅读。而且,在我看来,您将失去解决方案的意义,因为通常查看 EventLog 的是人(程序员、管理员等)。

但是,存在这样一个解决方案。它在 Deba Khadanga 的文章 “EventLog based Error Logging using ELMAH” 中进行了描述。

选项 2

值得庆幸的是,ELMAH 库中有一个 ErrorMailModule。此模块不将错误写入或读取到任何地方,而是通过电子邮件发送错误信息。并且它可以与日志提供程序同时工作。因此,我们将使用它作为我们解决方案的基础。

实现

ELMAH 库的源代码在其下载页面上可用。您可以熟悉 Elmah.ErrorMailModule 类。但我们将在此基础上实现我们自己的 ElmahErrorEventLogModule 类。

首先,我们将实现一些附加类,用于读取配置参数。ELMAH 库中已经有一个 Elmah.Configuration 类。但它被修改为 internal sealed,我们无法使用它。因此,我们将实现我们自己的 ElmahConfiguration 类,它是 Elmah.Configuration 的一个副本。并将添加来自 Elmah.ErrorMailModule 类的一些其他与读取配置相关的​​方法(例如 GetSetting() 等)。

    /// <summary> 
    /// Get the configuration from the "elmah" section of the configuration file.
    /// </summary>
    public class ElmahConfiguration
    {
        internal const string GroupName = "elmah";
        internal const string GroupSlash = GroupName + "/";

        public ElmahConfiguration() { }

        public static NameValueCollection AppSettings
        {
            get
            {
                return ConfigurationManager.AppSettings;
            }
        }

        public static object GetSubsection(string name)
        {
            return GetSection(GroupSlash + name);
        }

        public static object GetSection(string name)
        {
            return ConfigurationManager.GetSection(name);
        }

        public static string GetSetting(IDictionary config, string name)
        {
            return GetSetting(config, name, null);
        }

        public static string GetSetting(IDictionary config, string name, string defaultValue)
        {
            string value = NullString((string)config[name]);

            if (value.Length == 0)
            {
                if (defaultValue == null)
                {
                    throw new Elmah.ApplicationException(string.Format(
                        "The required configuration setting '{0}' 
                        is missing for the error eventlog module.", name));
                }

                value = defaultValue;
            }

            return value;
        }

        public static string NullString(string s)
        {
            return s == null ? string.Empty : s;
        }
    }

我们还需要一个 SectionHandler 类来从配置文件中读取 <elmah> 部分。同样,我们将根据 Elmah.ErrorMailSectionHandler 的类比来实现我们自己的 ElmahErrorEventLogSectionHandler 类。幸运的是,它非常小,只有一行。

    /// <summary> 
    /// Handler for the "elmah/errorEventLog" section of the configuration file.
    /// </summary>
    public class ElmahErrorEventLogSectionHandler : SingleTagSectionHandler { }

现在开始实现主要的 ElmahErrorEventLogModule 类。

让我们声明一个 eventLogSource 变量,它将存储 EventLog 中事件源的名称。我们将从配置文件中读取此名称。让我们也声明一个 Elmah.ExceptionFilterEventHandler 类型的委托,可用于额外的过滤。

    private string eventLogSource;
    public event Elmah.ExceptionFilterEventHandler Filtering;

现在让我们实现 OnInit() 方法来进行模块初始化。它使用 ElmahConfiguration 类来读取配置。然后它检查事件源是否已在 EventLog 中注册。如果未注册,它会尝试注册该源。不幸的是,这需要应用程序的管理员权限。但 ASP.NET 应用程序通常没有这些权限。因此,您需要提前手动注册您的事件源。您需要以管理员身份在 Windows 中执行以下命令

eventcreate /ID 1 /L APPLICATION /T INFORMATION 
/SO "your_eventLog_source_name" /D "Registering"

并且,在所有检查完成后,它会注册模块的事件处理程序。

    /// <summary> 
    /// Initializes the module and prepares it to handle requests.
    /// </summary>
    protected override void OnInit(HttpApplication application)
        {
            if (application == null)
                throw new ArgumentNullException("application");

            // Get the configuration section of this module.
            // If it's not there then there is nothing to initialize or do.
            // In this case, the module is as good as mute.
            IDictionary config = 
            (IDictionary)ElmahConfiguration.GetSubsection("errorEventLog");
            if (config == null)
                return;

            // Get settings.
            eventLogSource = ElmahConfiguration.GetSetting
            (config, "eventLogSource", string.Empty);
            if (string.IsNullOrEmpty(eventLogSource))
                return;

            // Register an event source in the Application log.
            try
            {
                if (!EventLog.SourceExists(eventLogSource))
                    EventLog.CreateEventSource(eventLogSource, "Application");
            }
            catch
            {
                // Don't register event handlers if it's 
                // not possible to register an EventLog source.
                // Most likely an application hasn't rights 
                // to register a new source in the EventLog.
                // Administration rights are required for this. 
                // Please register a new source manually.
                // Or maybe eventLogSource is not valid.
                return;
            }

            // Hook into the Error event of the application.
            application.Error += new EventHandler(OnError);
            Elmah.ErrorSignal.Get(application).Raised += 
            new Elmah.ErrorSignalEventHandler(OnErrorSignaled);
        }

接下来,让我们实现更多用于处理和过滤传入错误的方法。此外,我们将重写 SupportDiscoverability() 方法。

        /// <summary> 
        /// Determines whether the module will be registered 
        /// for discovery in partial trust environments or not.
        /// </summary>
        protected override bool SupportDiscoverability
        {
            get { return true; }
        }

        /// <summary> 
        /// The handler called when an unhandled exception bubbles up to the module.
        /// </summary>
        protected virtual void OnError(object sender, EventArgs e)
        {
            HttpContext context = ((HttpApplication)sender).Context;
            OnError(context.Server.GetLastError(), context);
        }

        /// <summary> 
        /// The handler called when an exception is explicitly signaled.
        /// </summary>
        protected virtual void OnErrorSignaled(object sender, Elmah.ErrorSignalEventArgs args)
        {
            OnError(args.Exception, args.Context);
        }

        /// <summary> 
        /// Reports the exception.
        /// </summary>
        protected virtual void OnError(Exception e, HttpContext context)
        {
            if (e == null)
                throw new ArgumentNullException("e");

            // Fire an event to check if listeners 
            // want to filter out reporting of the uncaught exception.
            Elmah.ExceptionFilterEventArgs args = new Elmah.ExceptionFilterEventArgs(e, context);
            OnFiltering(args);

            if (args.Dismissed)
                return;

            // Get the last error and then write it to the EventLog.
            Elmah.Error error = new Elmah.Error(e, context);
            ReportError(error);
        }

        /// <summary> 
        /// Raises the event.
        /// </summary>
        protected virtual void OnFiltering(Elmah.ExceptionFilterEventArgs args)
        {
            Elmah.ExceptionFilterEventHandler handler = Filtering;

            if (handler != null)
                handler(this, args);
        }

最后,让我们实现 ReportError() 方法。它将错误条目写入 EventLog。我们将在写入之前组合和格式化错误消息 string。在我们的项目中,我实现了这样的组合。但您可以重用此组合或创建自己的。

        /// <summary> 
        /// Writes the error to the EventLog.
        /// </summary>
        protected virtual void ReportError(Elmah.Error error)
        {
            // Compose an error message.
            StringBuilder sb = new StringBuilder();
            sb.Append(error.Message);
            sb.AppendLine();
            sb.AppendLine();
            sb.Append("Date and Time: " + error.Time.ToString("dd.MM.yyyy HH.mm.ss"));
            sb.AppendLine();
            sb.Append("Host Name: " + error.HostName);
            sb.AppendLine();
            sb.Append("Error Type: " + error.Type);
            sb.AppendLine();
            sb.Append("Error Source: " + error.Source);
            sb.AppendLine();
            sb.Append("Error Status Code: " + error.StatusCode.ToString());
            sb.AppendLine();
            sb.Append("Error Request Url: " + HttpContext.Current.Request.Url.AbsoluteUri);
            sb.AppendLine();
            sb.AppendLine();
            sb.Append("Error Details:");
            sb.AppendLine();
            sb.Append(error.Detail);
            sb.AppendLine();

            string messageString = sb.ToString();
            if (messageString.Length > 32765)
            {
                // Max limit of characters that EventLog allows for an event is 32766.
                messageString = messageString.Substring(0, 32765);
            }

            // Write the error entry to the event log.
            try
            {
                EventLog.WriteEntry(eventLogSource, 
                messageString, EventLogEntryType.Error, error.StatusCode);
            }
            catch
            {
                // Nothing to do if it is not possible to write an error message to the EventLog.
                // Most likely an application hasn't rights to write to the EventLog.
                // Or maybe eventLogSource is not valid.
            }
        }

完整源代码

最终,我们可以将所有这些代码放在一个文件中。

using System;
using System.Collections;
using System.Collections.Specialized;
using System.Configuration;
using System.Diagnostics;
using System.Text;
using System.Web;
using Elmah;

namespace MyNamespace
{
    /// <summary>
    /// Handler for the "elmah/errorEventLog" section of the configuration file.
    /// </summary>
    public class ElmahErrorEventLogSectionHandler : SingleTagSectionHandler { }

    /// <summary>
    /// HTTP module that writes Elmah logged error to the 
    /// Windows Application EventLog whenever an unhandled exception occurs 
    /// in an ASP.NET web application.
    /// </summary>
    public class ElmahErrorEventLogModule : HttpModuleBase, IExceptionFiltering
    {
        private string eventLogSource;

        public event Elmah.ExceptionFilterEventHandler Filtering;

        /// <summary>
        /// Initializes the module and prepares it to handle requests.
        /// </summary>
        protected override void OnInit(HttpApplication application)
        {
            if (application == null)
                throw new ArgumentNullException("application");

            // Get the configuration section of this module.
            // If it's not there then there is nothing to initialize or do.
            // In this case, the module is as good as mute.
            IDictionary config = 
            (IDictionary)ElmahConfiguration.GetSubsection("errorEventLog");
            if (config == null)
                return;

            // Get settings.
            eventLogSource = 
            ElmahConfiguration.GetSetting(config, "eventLogSource", string.Empty);
            if (string.IsNullOrEmpty(eventLogSource))
                return;

            // Register an event source in the Application log.
            try
            {
                if (!EventLog.SourceExists(eventLogSource))
                    EventLog.CreateEventSource(eventLogSource, "Application");
            }
            catch
            {
                // Don't register event handlers 
                // if it's not possible to register an EventLog source.
                // Most likely an application hasn't rights 
                // to register a new source in the EventLog.
                // Administration rights are required for this. 
                // Please register a new source manually.
                // Or maybe eventLogSource is not valid.
                return;
            }

            // Hook into the Error event of the application.
            application.Error += new EventHandler(OnError);
            Elmah.ErrorSignal.Get(application).Raised += 
                new Elmah.ErrorSignalEventHandler(OnErrorSignaled);
        }

        /// <summary>
        /// Determines whether the module will be registered 
        /// for discovery in partial trust environments or not.
        /// </summary>
        protected override bool SupportDiscoverability
        {
            get { return true; }
        }

        /// <summary>
        /// The handler called when an unhandled exception bubbles up to the module.
        /// </summary>
        protected virtual void OnError(object sender, EventArgs e)
        {
            HttpContext context = ((HttpApplication)sender).Context;
            OnError(context.Server.GetLastError(), context);
        }

        /// <summary>
        /// The handler called when an exception is explicitly signaled.
        /// </summary>
        protected virtual void OnErrorSignaled(object sender, Elmah.ErrorSignalEventArgs args)
        {
            OnError(args.Exception, args.Context);
        }

        /// <summary>
        /// Reports the exception.
        /// </summary>
        protected virtual void OnError(Exception e, HttpContext context)
        {
            if (e == null)
                throw new ArgumentNullException("e");

            // Fire an event to check if listeners want to filter out 
            // reporting of the uncaught exception.
            Elmah.ExceptionFilterEventArgs args = new Elmah.ExceptionFilterEventArgs(e, context);
            OnFiltering(args);

            if (args.Dismissed)
                return;

            // Get the last error and then write it to the EventLog.
            Elmah.Error error = new Elmah.Error(e, context);
            ReportError(error);
        }

        /// <summary>
        /// Raises the <see cref="Filtering"/> event.
        /// </summary>
        protected virtual void OnFiltering(Elmah.ExceptionFilterEventArgs args)
        {
            Elmah.ExceptionFilterEventHandler handler = Filtering;

            if (handler != null)
                handler(this, args);
        }

        /// <summary>
        /// Writes the error to the EventLog.
        /// </summary>
        protected virtual void ReportError(Elmah.Error error)
        {
            // Compose an error message.
            StringBuilder sb = new StringBuilder();
            sb.Append(error.Message);
            sb.AppendLine();
            sb.AppendLine();
            sb.Append("Date and Time: " + 
            error.Time.ToString("dd.MM.yyyy HH.mm.ss"));
            sb.AppendLine();
            sb.Append("Host Name: " + error.HostName);
            sb.AppendLine();
            sb.Append("Error Type: " + error.Type);
            sb.AppendLine();
            sb.Append("Error Source: " + error.Source);
            sb.AppendLine();
            sb.Append("Error Status Code: " + error.StatusCode.ToString());
            sb.AppendLine();
            sb.Append("Error Request Url: " + 
            HttpContext.Current.Request.Url.AbsoluteUri);
            sb.AppendLine();
            sb.AppendLine();
            sb.Append("Error Details:");
            sb.AppendLine();
            sb.Append(error.Detail);
            sb.AppendLine();

            string messageString = sb.ToString();
            if (messageString.Length > 32765)
            {
                // Max limit of characters that EventLog allows for an event is 32766.
                messageString = messageString.Substring(0, 32765);
            }

            // Write the error entry to the event log.
            try
            {
                EventLog.WriteEntry(eventLogSource, 
                messageString, EventLogEntryType.Error, error.StatusCode);
            }
            catch
            {
                // Nothing to do if it is not possible to write an error message to the EventLog.
                // Most likely an application hasn't rights to write to the EventLog.
                // Or maybe eventLogSource is not valid.
            }
        }
    }

    /// <summary>
    /// Get the configuration from the "elmah" section of the configuration file.
    /// </summary>
    public class ElmahConfiguration
    {
        internal const string GroupName = "elmah";
        internal const string GroupSlash = GroupName + "/";

        public ElmahConfiguration() { }

        public static NameValueCollection AppSettings
        {
            get
            {
                return ConfigurationManager.AppSettings;
            }
        }

        public static object GetSubsection(string name)
        {
            return GetSection(GroupSlash + name);
        }

        public static object GetSection(string name)
        {
            return ConfigurationManager.GetSection(name);
        }

        public static string GetSetting(IDictionary config, string name)
        {
            return GetSetting(config, name, null);
        }

        public static string GetSetting(IDictionary config, string name, string defaultValue)
        {
            string value = NullString((string)config[name]);

            if (value.Length == 0)
            {
                if (defaultValue == null)
                {
                    throw new Elmah.ApplicationException(string.Format(
                        "The required configuration setting '{0}' 
                        is missing for the error eventlog module.", name));
                }

                value = defaultValue;
            }

            return value;
        }

        public static string NullString(string s)
        {
            return s == null ? string.Empty : s;
        }
    }
}

配置文件

现在让我们看一下配置文件。

在 ELMAH 处理程序之后,在 <configSections><sectionGroup name=”elmah”> 部分注册我们的 SectionHandler。当然,在您的情况下,命名空间和 DLL 名称会不同。

同样,需要在 ELMAH 模块之后在 <system.web><system.webServer> 部分注册我们的模块。

最后,为我们的模块在 <elmah> 部分添加配置参数 errorEventLog

<?xml version="1.0" encoding="utf-8"?>
<configuration>
  <configSections>
    <sectionGroup name="elmah">
      <section name="security" requirePermission="false" 
      type="Elmah.SecuritySectionHandler, Elmah" />
      <section name="errorLog" requirePermission="false" 
      type="Elmah.ErrorLogSectionHandler, Elmah" />
      <section name="errorMail" requirePermission="false" 
      type="Elmah.ErrorMailSectionHandler, Elmah" />
      <section name="errorFilter" requirePermission="false" 
      type="Elmah.ErrorFilterSectionHandler, Elmah" />
      <section name="errorEventLog" requirePermission="false" 
      type="MyNamespace.ElmahErrorEventLogSectionHandler, MyApplicationOrLybraryDllName" />
    </sectionGroup>
  </configSections>
  
  <system.web>
    <httpModules>
      <add name="ErrorLog" type="Elmah.ErrorLogModule, Elmah" />
      <add name="ErrorFilter" 
      type="Elmah.ErrorFilterModule, Elmah" />
      <add name="ErrorEventLog" 
      type="MyNamespace.ElmahErrorEventLogModule, MyApplicationOrLybraryDllName" />
    </httpModules>
    <httpHandlers>
      <add verb="POST,GET,HEAD" path="elmah.axd" 
      type="Elmah.ErrorLogPageFactory, Elmah" />
    </httpHandlers>
  </system.web>
  
  <system.webServer>
    <modules runAllManagedModulesForAllRequests="true">
      <add name="ErrorLog" type="Elmah.ErrorLogModule, 
      Elmah" preCondition="managedHandler" />
      <add name="ErrorFilter" type="Elmah.ErrorFilterModule, 
      Elmah" preCondition="managedHandler" />
      <add name="ErrorEventLog" 
      type="MyNamespace.ElmahErrorEventLogModule, 
      MyApplicationOrLybraryDllName" preCondition="managedHandler" />
    </modules>
    <handlers>
      <add name="Elmah" path="elmah.axd" 
      verb="POST,GET,HEAD" type="Elmah.ErrorLogPageFactory, 
      Elmah" preCondition="integratedMode" />
    </handlers>
  </system.webServer>
  
  <elmah>
    <errorLog type="Elmah.MemoryErrorLog, Elmah" size="50" />
    <errorEventLog eventLogSource="MyEventLogSourceName" />
  </elmah>
</configuration>

下载

您可以从 此处 下载所有这些代码。

结论

我希望这篇文章能帮助到一些人。我在一个项目上工作时没有找到这样的解决方案。这就是写这篇文章的原因。

© . All rights reserved.