在软件开发中,异常处理是确保程序健壮性的关键环节。然而,如何正确地记录异常日志,以避免重复记录,是一个常见的挑战。本文将探讨在.NET环境中,如何通过使用自定义异常类和异常过滤来避免重复日志记录的问题。
根据MSDN的建议,应该避免在调用堆栈的较低层级进行异常报告或日志记录。如果在较低层级捕获并记录异常,然后再将其重新抛出,那么调用堆栈中更高层级的调用者可能会再次记录相同的异常,导致日志条目重复。
然而,通常希望在异常发生的位置尽可能接近地记录错误,因为这样可以访问到方法参数,并将它们记录在日志中,这将极大地帮助调试异常。但是,根据MSDN的建议,这将导致重复的日志条目。
让通过一段代码来说明这个问题:
public void CreateBookings(string[] people)
{
try
{
foreach (string person in people)
{
CreateBookingForPerson(person);
}
}
catch (Exception e)
{
_logger.LogError(e);
}
}
public void CreateBookingForPerson(string person)
{
_bookingRepo.CreateBooking(person);
}
在上面的代码中,只在第一个方法中捕获并记录错误:在CreateBookingForPerson方法中发生的错误会冒泡并被CreateBookings方法中的catch块捕获。
假设有一个包含10个人的数组需要处理;在处理第四个项目时,发生了错误:
_bookingRepo.CreateBooking(person);
错误会冒泡直到被CreateBookings方法中的catch块捕获。问题是:此时,已经无法访问导致错误的person项:可以记录发生了错误,但已经失去了为什么(详细信息)。如果异常是在CreateBookingForPerson方法中捕获的,仍然可以访问导致错误的项,这是调试问题的关键信息。
然而,如果决定在CreateBookingForPerson方法中添加一个日志记录的try/catch块,错误最终会被记录两次:这是试图避免的。
解决方案的核心概念是:当捕获到异常时,记录异常并将其包装在自定义异常类型LoggedException中。抛出这个新创建的LoggedException。忽略/不要记录LoggedException类型的异常。
让首先看看LoggedException类代码:
public class LoggedException : Exception
{
public LoggedException(string message) : base(message)
{
LogException(message, null);
}
public LoggedException(string message, Exception innerException) : base(message, innerException)
{
LogException(message, innerException);
}
private void LogException(string message, Exception innerException)
{
// 连接到选择的日志框架
}
}
这个类相当简单;它只是内置Exception类的包装器。在实例化过程中通过调用LogException记录错误详细信息。LogException是连接到所选日志框架的地方。作为一个额外的好处,现在已经将日志记录功能集中化了。如果想更换日志框架,唯一需要更改的代码就在LoggedException中。
让再次看看从介绍中得到的代码,但这次使用LoggedException。
public void CreateBookings(string[] people)
{
try
{
foreach (string person in people)
{
CreateBookingForPerson(person);
}
}
catch (Exception e) when (!(e is LoggedException))
{
string errorMsg = "在创建预订时发生错误。";
throw new LoggedException(errorMsg, e);
}
}
public void CreateBookingForPerson(string person)
{
try
{
_bookingRepo.CreateBooking(person);
}
catch (Exception e) when (!(e is LoggedException))
{
string errorMsg = $"在为{person}创建预订时发生错误。";
throw new LoggedException(errorMsg, e);
}
}
一旦错误被记录,两个catch块都会抛出LoggedException。LoggedException告诉调用堆栈中更高层级的catch块,异常已经被记录过了。有了这个机制,错误日志可以包含额外的细节,以便快速定位问题。
注意到每个catch块上的when关键字了吗?
catch (Exception e) when (!(e is LoggedException))