避免重复日志记录的策略

在软件开发中,异常处理是确保程序健壮性的关键环节。然而,如何正确地记录异常日志,以避免重复记录,是一个常见的挑战。本文将探讨在.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))
沪ICP备2024098111号-1
上海秋旦网络科技中心:上海市奉贤区金大公路8218号1幢 联系电话:17898875485