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

使用 ELMAH 自定义错误消息

emptyStarIconemptyStarIconemptyStarIconemptyStarIconemptyStarIcon

0/5 (0投票)

2018 年 8 月 19 日

CPOL

5分钟阅读

viewsIcon

9413

如何扩展 ELMAH 以在错误日志中包含额外详细信息。

引言

本文介绍了一种在 ELMAH 日志记录中添加自定义异常详细信息的方法。

背景

我正在为客户管理一个 ASP.NET MVC 网站的开发,并已包含 ELMAH 日志记录来跟踪发生的任何未处理异常。有关 ELMAH 的更多信息,请参阅 https://elmah.github.io/

ELMAH 工作得很好,但仅记录基于 System.Exception 的信息。它不会记录自定义异常中不存在于 System.Exception 中的字段的详细信息。一个典型的例子是 System.Data.Entity.Validation.DbEntityValidationException。如果抛出此异常,您会在 ELMAH 中看到以下消息:

  • 一个或多个实体验证失败。有关更多详细信息,请参阅“EntityValidationErrors”属性。

不幸的是,由于“EntityValidationErrors”属性是 DbEntityValidationException 特有的,ELMAH 对其一无所知,因此不会记录这些详细信息。

我发现了一些文章,其中您可以在错误发生的确切位置生成带有所需详细信息的自定义异常。我需要一种方法,可以在 ELMAH 中自动捕获此信息,而无需修改每个此类位置(无论是否需要)。

在此示例中,我提供了一种将此信息捕获到 ELMAH 日志中的方法,以便您可以确定发生的特定验证错误。这在网站内全局管理,因此您无需向可能发生此类错误的每个位置添加自定义代码。

您还可以修改此代码,以便捕获您想要跟踪的任何异常的任何自定义字段。

供您参考,代码是根据以下内容编写的:

  • .NET Framework 4.5.2
  • ASP.NET MVC 5.2.3
  • Elmah 1.2.2

此代码可能可以根据需要适用于其他版本。

Using the Code

在开始之前,假定您已经:

  1. 拥有一个 ASP.NET MVC 网站
  2. 已将 ELMAH 安装到您的网站中,并已测试其正常工作。

有几篇文章讨论了如何安装 ELMAH,因此我不会在此重复。

您需要找到已添加到项目中的 ELMAH 自定义过滤器。这应该位于 App_Start 文件夹中。在我的安装中,该文件名为 ElmahErrorAttribute.cs,并包含 ElmahHTTPErrorAttribute 类,该类派生自 System.Web.Http.Filters.ExceptionFilterAttribute

找到 OnException() 方法,它应该如下所示:

public override void OnException
(System.Web.Http.Filters.HttpActionExecutedContext actionExecutedContext)
{
    if (actionExecutedContext.Exception != null)
    {
        Elmah.ErrorSignal.FromCurrentContext().Raise(actionExecutedContext.Exception);
    }

    base.OnException(actionExecutedContext);
}

简而言之,任何未处理的异常都会路由到这里。异常会被提升到 ELMAH 中以捕获详细信息到 ELMAH 日志中,然后代码继续执行其默认处理。

我们要做的就是确定传入异常的基异常是否是我们想要提供额外详细信息的类型之一。

  1. 如果异常不匹配我们想要跟踪的特定异常类型之一,则按原样处理。
  2. 如果异常匹配我们想要跟踪的特定异常类型之一,那么我们希望创建一个新的异常,将扩展信息捕获到异常消息中,并将 InnerException 属性设置为传入的原始异常。

在下面的示例中,我执行以下操作:

  • 创建一个新的自定义 Exception,名为 ElmahExtendedException,它派生自 System.Exception。您也可以只使用 System.Exception,但使用自定义 Exception 可以更轻松地在 ELMAH 日志文件中识别它。
    public class ElmahExtendedException
        : Exception
    {
        public ElmahExtendedException(string message, Exception e)
            : base(message, e) { }
    }
  • 在 ELMAH 过滤器(ElmahHTTPErrorAttribute)的同一类中创建一个名为 GetExtendedException()private 方法。它接收原始异常并确定它是否是我们感兴趣的异常类型之一。如果不是,我们只需按原样返回它。否则,我们处理异常以创建新的 ElmahExtendedException 并返回它。

    在这种情况下,我们正在处理 DbEntityValidationException 实例。在这里,我们将 ValidationError 列表解析为 StringBuilder 中的一组 string,并将它们附加到异常的原始 Message 中。我们附加最终消息和原始 Exception,并使用它们来创建新的 ElmahExtendedException 并返回它。

    private Exception GetExtendedException(Exception ex)
    {
        var baseException = ex.GetBaseException();
    
        switch (baseException.GetType().ToString())
        {
            case "System.Data.Entity.Validation.DbEntityValidationException":
                var dbException = baseException as 
                       System.Data.Entity.Validation.DbEntityValidationException;
                var sb = new System.Text.StringBuilder();
                sb.AppendLine(baseException.Message);
    
                foreach (var entity in dbException.EntityValidationErrors)
                {
                    foreach (var error in entity.ValidationErrors)
                    {
                        sb.AppendLine(string.Format(" [{0}: {1}]", 
                                  error.PropertyName, error.ErrorMessage));
                    }
                }
    
                return new ElmahExtendedException(sb.ToString(), ex);
    
            default:
                return ex;
        }
    }

    如果需要,您可以为要处理的其他异常添加自己的情况。您需要捕获任何其他详细信息,并根据需要添加 StringBuilder 进行格式化。在上面的示例中,我们将每个 ValidationError 添加到单独的行中,以便以后在日志中更容易阅读每个详细信息。

    您会注意到我们使用了原始异常的基 Exception。我们想引用原始异常并使用其错误消息。如果您有更复杂的嵌套异常,您可能需要修改此代码以找到您感兴趣的特定实例。

  • 最后,我们修改 OnException() 方法如下:
    public override void OnException
    (System.Web.Http.Filters.HttpActionExecutedContext actionExecutedContext)
    {
        if (actionExecutedContext.Exception != null)
        {
            var exception = GetExtendedException(actionExecutedContext.Exception);
            Elmah.ErrorSignal.FromCurrentContext().Raise(exception);
        }
    
        base.OnException(actionExecutedContext);
    }

    在这里,我们调用我们的 GetExtendedException() 方法,该方法将返回原始异常或我们的新 ElmahExtendedException。然后,我们将此异常提升到 ELMAH 进行日志记录。

如果我们查看 ELMAH 日志记录,日志详细信息现在显示如下:


System.Data.Entity.Validation.DbEntityValidationException一个或多个实体验证失败。有关更多详细信息,请参阅“EntityValidationErrors”属性。

SampleWeb.ElmahExtendedException: Validation failed for one or more entities. 
See 'EntityValidationErrors' property for more details.
 [FieldContents: The field FieldContents must be a string or array type with a maximum length of '100'.]
 ---> System.Data.Entity.Validation.DbEntityValidationException: Validation failed for 
   one or more entities. See 'EntityValidationErrors' property for more details.
   at System.Data.Entity.Internal.InternalContext.SaveChanges() ...

正如您所见,我们在日志标题中显示了原始异常 Message。然而,在异常的详细信息部分:

  • 我们看到了新的 ElmahExtendedException 异常,表明它包含我们的扩展异常详细信息。
  • 直接在其下方,我们看到了我们添加的详细信息 - 在这种情况下,是显示 Fieldname 后跟特定错误的验证错误。如果有多个验证错误,它们将每行一个。
  • 在此下方,我们有原始 Exception 的名称,后跟完整的堆栈跟踪。

摘要

希望这对您有所帮助。同样,根据需要,将其改编为其他类型的异常应该是很直接的。

如果您有任何反馈或改进建议,我们随时欢迎。

历史

  • 2018 年 8 月 18 日 - 首次发布
© . All rights reserved.