如何在 InvariantCulture 中记录异常?

How to log exception in InvariantCulture?

在我的 MVC 解决方案中,我有两个异常处理程序。 首先将异常记录到日志数据库:

public sealed class LogErrorAttribute : Attribute, IExceptionFilter
{
    public void OnException(ExceptionContext filterContext)
    {
        // some handling with filterContext.Exception
    }
}

第二次向用户显示异常:

public sealed class AsyncAwareHandleErrorAttribute : HandleErrorAttribute
{
    public override void OnException(ExceptionContext filterContext)
    {
         // some handling with filterContext.Exception    
    }
}

当任何未处理的异常发生时,它们都会被触发:

throw new ArgumentNullException("email", i18n.someErrorMessage);

i18n.someErrorMessage 是来自 resx 文件的翻译字符串。

filterContext.Exception.Message 的两个处理程序中,我得到的字符串已经被翻译。

如何仅在 CultureInfo.InvariantCulture 中记录异常消息,在我的解决方案中设置为 en-US

异常应该包含它们自己的人类可读消息的概念非常普遍,但也非常错误。

概念上,

The message conveyed by an exception is the type of the exception along with any member variables contained within the exception.

这也意味着:

You have to have a different exception class for every single conceptually different exception that you throw.

您不能使用通配符“MyAllPurposeException”并通过在抛出异常时构造不同的人类可读消息来区分概念上不同的异常情况。试图理解你的异常的实体不应该仅限于人类:从概念上讲,catch 块也有权理解你的代码抛出的异常,在最重要的时候至少在单元测试的上下文中,但也经常在生产代码中。

为了方便起见,我们通常只是将人类可读的消息填充到我们的异常中,但这只是一个快速而肮脏的解决方案。当您的应用程序具有实际的国际化需求时,您不能像那样做 hack。

因此,我的建议是从不向异常添加任何消息。让Message成员为空;我什至会说 string Message 成员包含在原始 Exception class 中代表语言运行时的设计者是一个错误。相反,知道如何处理异常的处理程序应该决定是否应该为异常生成人类可读的消息,如果是这样,它是否应该只能由程序员或用户读取,因此, 语言环境(文化)应该是什么。

从概念上讲,您将有一个二维的 table,其中 X 轴上有语言环境,Y 轴上有异常类型名称,table 中的每个单元格将包含一个字符串,例如“Parameter %d cannot be null”,使用正确的语言。第一列可能是中性语言环境,即不变文化,包含程序员可读的消息。

当然,实际上它需要比这稍微复杂一些,因为您将不得不以某种方式读取异常的特定类型成员并将它们传递给字符串格式化函数。对于您可以控制的异常,您可以添加一个可覆盖的并像这样实现它:

public ParameterCannotBeNullException extends MyException
{
    private final int parameterNumber;

    public ParameterCannotBeNullException( int parameterNumber )
    {
        this.parameterNumber = parameterNumber;
    }

    public override String FormatMessage( String localeSpecificMessage )
    {
        return String.Format( localeSpecificMessage, parameterNumber );
    }
}

对于您无法控制的异常,您可以有一个巨大的级联 if 语句,如下所示:

String formatString = (look it up by exception.GetType() and locale)
String message;
if( exception is SomeException )
{
     SomeException temp = (SomeException)exception;
     message = String.Format( formatString, temp.X );
}
else if( exception is SomeOtherException )
{
     SomeOtherException temp = (SomeOtherException)exception;
     message = String.Format( formatString, temp.A, temp.B );
}
else
{
     message = exception.GetType().Name;
}

最后一件事:有时在写代码的时候,我们意识到我们遇到了可能发生错误的情况,所以需要抛出一个新的异常,但我们不想打断我们正在做的事情在那一刻正在做并去声明一个新的异常class。对于这种情况,我发现有一个 GeneralPurposeException 很有用,它实际上在其构造函数中接受一个包含程序员可读消息的字符串。但是这个 class 包含一个巨大的评论,内容如下:

XXX FIXME TODO:

For development purposes only!

Thus class must not make it to production!