14

我在 MVC4 (RC) 应用程序中遇到了一个奇怪的问题。(在 .NET 4.0 上运行)

我刚刚设置 Elmah 来记录异常/错误。

我基本上安装了Elmah.MVCelmah.sqlserver NuGet 包。(分别为 2.0.0 和 1.2 版本)

它似乎开箱即用 - 我可以转到 elmah 页面并查看错误:

http://myserver/elmah

例如,如果我创建了一些 404 错误,它们会出现在此日志中。

不工作的是:我有一个带有[HttpPost]动作的标准 MVC 控制器。我已经设置了它,所以它总是会抛出异常:

public class TestController : Controller
{
    [HttpPost]
    [ValidateInput(false)]
    public void Testing()
    {
        throw new Exception("uh oh");
    }
}

然后我尝试通过 jQuery 将数据发布到这个控制器:

$.post('/Test/Testing', {test_data: 'This is some test data'});

好的,这行得通。响应返回典型的黄屏死机,错误被捕获并记录在 Elmah 中。

但是,如果我尝试发布类似 XML/HTML 的内容,则错误不会记录在 Elmah 中。我仍然从服务器返回相同的响应(黄屏死机),但在 Elmah 中什么也没有。

$.post('/Test/Testing', {test_data: '<test><test1>This is some test data</test1></test>'});

为什么?这没有意义。

请注意,我已经关闭了操作的请求验证。如果我不这样做,那么发布 XML/HTML 数据会导致此异常:

从客户端检测到潜在危险的 Request.Form 值

NuGet 也会拒绝记录该异常——我认为这是一个错误:

http://code.google.com/p/elmah/issues/detail?id=217

那么我遇到的这个问题的原因是什么?它是与我在上面发现的问题相关的错误吗?

仅仅因为请求包含 XML/HTML,我无法记录异常,这似乎是一种非常不幸的情况。

当然有办法解决这个问题吗?

4

7 回答 7

4

默认情况下,ELMAH 不会捕获 HttpRequestValidationException,如果用户发送无效请求,ELMAH 的报告中将忽略该请求。所以也有必要定义和使用这个全局过滤器:

public class ElmahRequestValidationErrorFilter : IExceptionFilter
{
    public void OnException(ExceptionContext context)
    {
        if (context.Exception is HttpRequestValidationException)
           ErrorLog.GetDefault(HttpContext.Current).Log(new Error(context.Exception));
    }
}
于 2014-01-01T10:48:46.210 回答
2

我现在有一个解决方法,有人在http://code.google.com/p/elmah/issues/detail?id=217上提出了建议

您可以强制 ASP 使用较旧的请求验证逻辑,方法是将其添加到<system.web>您的部分Web.config

<httpRuntime requestValidationMode="2.0" />

这将在全球范围内影响应用程序,这并不是一个很好的情况。

如果有人有更好的东西,请告诉我。

于 2012-07-24T12:24:32.383 回答
1

看起来这是在当前版本(撰写本文时)1.2.2 之后修复的。我最终在日志中明确记录了错误。这不会作为未处理的异常(电子邮件通知等)进行正常处理,但会将其记录到错误日志中。

catch (Exception ex)
{
    var newEx = new HttpException(500, "Kaboom!!!", ex);

    // Adding this explicit ELMAH logging because of ELMAH bug:
    // https://code.google.com/p/elmah/issues/detail?id=217
    // Waiting for a new ELMAH release with the fix
    var elmahError = new Elmah.Error(newEx);
    var elmahLog = Elmah.ErrorLog.GetDefault(HttpContext.ApplicationInstance.Context);
    elmahLog.Log(elmahError);

    throw newEx;
}
于 2014-08-07T15:09:49.943 回答
1

Elmah 中有一个 bug 不久前已修复,但我认为 google 代码网站上的任何构建都不足以包含它。

正如我在评论中提到的,问题是如果您通过模型(MVC)或控件(Webforms)访问数据,没问题;但是如果你访问 request.Form["inputId"] (Elmah 在构建一个对象时所做的),它每次都会抛出一个异常。

我做了什么:

  • 将google代码项目导出到我的github账号
  • 使用 VS2013 将新的 repo 克隆到我的工作站
  • 选择 NETFX 4.5 构建配置
  • 构建解决方案并运行演示
  • 通过将以下内容添加到 default.aspx,在演示中测试我的案例:

在此处输入图像描述

  • 运行一些测试

在我对测试感到满意后,我通过以下方式将新的 dll 添加到我的解决方案中:

  • 删除当前 Elmah.dll 引用
  • 将 Elmah.dll、Elmah.AspNet.dll 和 AntiXssLibrary.dll 复制/粘贴到我的解决方案中的“lib”文件夹中
  • 添加对三个新 dll 的引用
  • 更新 web.config 使其看起来像演示的 web.config(事情非常相似,但不同 (Elmah = Elmah.AspNet))
  • 删除 Elmah.mvc 参考(它给我带来了问题)
  • 从 FilterConfig 中删除filters.Add(new HandleErrorAttribute());(这允许自定义错误继续起作用)

在 Elmah 源码中,关键部分是request.Unvalidated在 HttpRequestValidation.cs中的使用

于 2015-09-27T23:41:54.680 回答
0

您可以通过抛出HttpException异常而不是正常异常来触发 Elmah。

我们的解决方案是用try/catch块包装我们的控制器操作代码,并在catch块中将代码抛出的异常包装在 an 中HttpException,然后将其抛出。像这样:

[HttpPost]
[ValidateInput(false)]
public ActionResult MyAction(FormCollection riskyData)
{
    try
    {
        //... your code here ...
    }
    catch (Exception ex)
    {
        throw new HttpException(500, "Internal Server Error", ex);
    }
}
于 2013-07-11T09:48:13.597 回答
0

将以下类添加到您的项目中:

public class ElmahErrorLogModuleFix : ErrorLogModule
{
    protected override void LogException(Exception e, HttpContext context)
    {
        if (e == null)
            throw new ArgumentNullException("e");
        ExceptionFilterEventArgs args = new ExceptionFilterEventArgs(e, (object)context);
        this.OnFiltering(args);
        if (args.Dismissed)
            return;
        ErrorLogEntry entry = (ErrorLogEntry)null;
        try
        {
            //FIX STARTS
            //Error error = new Error(e, context);
            Error error = CreateErrorSafe(e, context);
            //FIX ENDS
            ErrorLog errorLog = this.GetErrorLog(context);
            error.ApplicationName = errorLog.ApplicationName;
            string id = errorLog.Log(error);
            entry = new ErrorLogEntry(errorLog, id, error);
        }
        catch (Exception ex)
        {
            Trace.WriteLine((object)ex);
        }
        if (entry == null)
            return;
        this.OnLogged(new ErrorLoggedEventArgs(entry));
    }

    public static Error CreateErrorSafe(Exception e, HttpContext context)
    {
        try
        {
            var safeFormCollection = new NameValueCollection();
            var form = context.Request.Form;
            var additionalMessage = string.Empty;
            foreach (var key in form.AllKeys)
            {
                try
                {
                    safeFormCollection.Add(key, form[key]);
                }
                catch (Exception)
                {
                    safeFormCollection.Add(key, "_invalid input data_");
                    additionalMessage += "Form parameter with name=" + key + " has dangerous value. " + Environment.NewLine;
                }
            }

            //if no invalid values in form then do as elmah does
            if (string.IsNullOrEmpty(additionalMessage))
            {
                return new Error(e, context);
            }

            var exception = new Exception(additionalMessage, e);
            var error = new Error(exception);
            error.HostName = TryGetMachineName(context, null);

            IPrincipal user = context.User;
            if (user != null && NullString(user.Identity.Name).Length > 0)
                error.User = user.Identity.Name;
            HttpRequest request = context.Request;
            //this._serverVariables = Error.CopyCollection(request.ServerVariables);
            error.ServerVariables.Add(CopyCollection(request.ServerVariables));
            if (error.ServerVariables != null && error.ServerVariables["AUTH_PASSWORD"] != null)
                error.ServerVariables["AUTH_PASSWORD"] = "*****";
            error.QueryString.Add(CopyCollection(request.QueryString));
            error.Form.Add(CopyCollection(safeFormCollection));
            error.Cookies.Add(CopyCollection(request.Cookies));
            return error;
        }
        catch (Exception logEx)
        {
            return new Error(new Exception("Error when trying to process error catched by elmah", logEx));
        }
    }

    /// <summary>
    /// Elmah dll method in Environment.cs
    /// </summary>
    /// <param name="context"></param>
    /// <param name="unknownName"></param>
    /// <returns></returns>
    public static string TryGetMachineName(HttpContext context, string unknownName)
    {
        if (context != null)
        {
            try
            {
                return context.Server.MachineName;
            }
            catch (HttpException ex)
            {
            }
            catch (SecurityException ex)
            {
            }
        }
        try
        {
            return System.Environment.MachineName;
        }
        catch (SecurityException ex)
        {
        }
        return NullString(unknownName);
    }

    /// <summary>
    /// Elmah method in Mask.cs
    /// </summary>
    /// <param name="s"></param>
    /// <returns></returns>
    public static string NullString(string s)
    {
        if (s != null)
            return s;
        else
            return string.Empty;
    }

    /// <summary>
    /// Elmah method in Error.cs
    /// </summary>
    /// <param name="collection"></param>
    /// <returns></returns>
    private static NameValueCollection CopyCollection(NameValueCollection collection)
    {
        if (collection == null || collection.Count == 0)
            //FIX HERE: cannot allow reutrn null collection as elmah does, because of exception. fix as below
            //return (NameValueCollection)null;
            return new NameValueCollection();
        //FIX ENDS
        else
            return new NameValueCollection(collection);
    }

    /// <summary>
    /// Elmah method in Error.cs
    /// </summary>
    /// <param name="cookies"></param>
    /// <returns></returns>
    private static NameValueCollection CopyCollection(HttpCookieCollection cookies)
    {
        if (cookies == null || cookies.Count == 0)
            //FIX HERE: cannot allow reutrn null collection as elmah does, because of exception. fix as below
            //return (NameValueCollection)null;
            return new NameValueCollection();
        //FIX ENDS
        NameValueCollection nameValueCollection = new NameValueCollection(cookies.Count);
        for (int index = 0; index < cookies.Count; ++index)
        {
            HttpCookie httpCookie = cookies[index];
            nameValueCollection.Add(httpCookie.Name, httpCookie.Value);
        }
        return nameValueCollection;
    }
}

public class ElmahErrorMailModuleFix : ErrorMailModule
{
    private bool _reportAsynchronously2;

    protected override void OnInit(HttpApplication application)
    {
        base.OnInit(application);
        IDictionary config = (IDictionary)this.GetConfig();
        if (config == null)
            return;
        _reportAsynchronously2 = Convert.ToBoolean(GetSetting(config, "async", bool.TrueString));
    }

    protected override void OnError(Exception e, HttpContext context)
    {
        if (e == null)
            throw new ArgumentNullException("e");
        ExceptionFilterEventArgs args = new ExceptionFilterEventArgs(e, (object)context);
        this.OnFiltering(args);
        if (args.Dismissed)
            return;
        //FIX STARTS
        //Error error = new Error(e, context);
        Error error = ElmahErrorLogModuleFix.CreateErrorSafe(e, context);
        //FIX ENDS
        if (this._reportAsynchronously2)
            this.ReportErrorAsync(error);
        else
            this.ReportError(error);
    }

    /// <summary>
    /// Elmah method in ErrorMailModule.cs
    /// </summary>
    /// <param name="config"></param>
    /// <param name="name"></param>
    /// <param name="defaultValue"></param>
    /// <returns></returns>
    private static string GetSetting(IDictionary config, string name, string defaultValue)
    {
        string str = ElmahErrorLogModuleFix.NullString((string)config[(object)name]);
        if (str.Length == 0)
        {
            if (defaultValue == null)
                throw new global::Elmah.ApplicationException(string.Format("The required configuration setting '{0}' is missing for the error mailing module.", (object)name));
            str = defaultValue;
        }
        return str;
    }
}

这些类继承ErrorLogModuleErrorMailModule重写Error创建类的方法,因此HttpRequestValidationException不会引发异常。

然后将这些添加到您的 Web.config 中:

<add name="ErrorLog" type="YourProject.SomeFolder.ElmahErrorLogModuleFix, YourProject" preCondition="managedHandler" />
<!--and for email module-->

使用这些类而不是原始类。有点肮脏的黑客,但它的工作原理。

归功于此处找到的消息 #17 的海报。

于 2016-01-06T09:08:49.563 回答
0

感谢 Amarisi 提供的代码,我已将其重新编写到我的 Global.asax.cs 文件 Application_Error 方法中,以记录 Elmah 未能记录的所有异常。

它看起来像这样(已编辑但应该可以工作):

protected void Application_Error(object sender, EventArgs e)
    {
        Exception exception = Server.GetLastError();

        // ELMAH will not log exceptions arising from POST requests
        // when the form values contains 'potentially dangerous' chars
        // e.g. html tags

        // Adding this explicit ELMAH logging because of ELMAH bug:
        // https://code.google.com/p/elmah/issues/detail?id=217
        // Waiting for a new ELMAH release with the fix

        // get form object
        System.Collections.Specialized.NameValueCollection RequestForm = Request.Unvalidated.Form;

        // get combined text of form values
        string formValues = string.Join(",", RequestForm.AllKeys.Select(key => RequestForm[key])); 

        // is this the type of POST that Elmah fails on?
        if (formValues.Contains("<")) 
        {
            // log the exception manually
            var elmahError = new Elmah.Error(exception);
            var elmahLog = Elmah.ErrorLog.GetDefault(Context);
            elmahLog.Log(elmahError);
        }
    }
}

Contains("<") 不是最好的测试。我怀疑它涵盖了 Elmah 未能记录的所有内容。

很想看看是否有人可以进一步改善这一点。

于 2021-12-15T18:51:00.383 回答