Serilog异常处理最佳实践

3
我在我的.NET Core 5控制台应用程序中使用Serilog进行诊断。我无法确定与异常处理相结合的最佳日志记录语义。例如,假设我执行需要文件存在的操作。如果该文件不存在,出于流程控制原因(我需要解开以正确退出应用程序),我会抛出异常。但是在哪里以及如何记录这个问题呢?我可以想到几个选项:
第一种选择-在我抛出异常之前记录:
if (!File.Exists(myFilePath)) {
  _logger.Error("The file does not exist: {File}", myFilePath);
  throw new ArgumentException("File does not exist", nameof(myFilePath));
}

第二个选项 - 在捕获异常时记录日志:
try {
  if (!File.Exists(myFilePath)) {
    throw new ArgumentException("File does not exist", nameof(myFilePath));
  }
}
catch (Exception e) {
  _logger.Error(e, "An Exception");
}

从代码角度来看,我最喜欢第二个选项。至于描述字符串本身,我不会重复自己。但是,我觉得信息将以不同的方式进行传达。我不确定第二个选项的输出是什么样子的:它是否有结构?它缺少第一个选项中的原始日志字符串。

哪种方法最好?还有其他选项应该考虑吗?


Serilog(和结构化日志记录)具有异常的特殊属性,这也是为什么有一种重载的日志记录方法,它将异常作为第一个参数。它将被结构化并包含所有消息、堆栈跟踪、异常属性以及您在同一调用中附加的任何日志记录内容。在我看来(基于我的个人和专业使用),第二个选择是“正确”的,但这并不意味着您不能在两个位置记录它(尽管在第一种情况下可能会很冗长/调试,具体取决于情况)。 - pinkfloydx33
1
第二种形式(在捕获时记录)可能更多地涉及操作的上下文。假设抛出异常的位置是一个抽象的、通用的代码片段(例如,一些工具只是为了任何原因读取文件),它可能没有足够的信息使日志消息有意义。通过在捕获时进行记录,您可以在记录的消息中添加其他信息/属性。 - pinkfloydx33
额外的上下文:上下文是负责加载配置的代码。因此,它至少知道我正在加载的文件是一个 YAML 配置文件,并且该文件不存在。我不明白当抛出异常时,结构化变量 {File} 是如何被放入其中的。当我在第二个表单中记录异常时,它不会知道“File”是什么,对吗? - void.pointer
好的,我错过了那个。你是正确的。但是如果你使用一个更有意义的异常(例如FileNotFound或自定义类型),它将成为异常中的属性,当捕获时会被记录下来。或者,既然你在捕获时知道文件名,你可以将其添加到日志消息中,并与异常一起记录 logger.Error(e, "文件 {File} 不存在", myFilePath) - pinkfloydx33
Serilog是否鼓励使用正确的异常类型,并使用属性将数据输出到日志行本身?那么我可以说,例如,MyFileException.Filename属性映射到结构化输出字符串中的{File}吗?那么我就可以这样写... _logger.Error(ex, "无法加载:{File}", ex.Filename) - void.pointer
1个回答

3
我曾经也思考过这个问题,我的结论是:在实践中重复可能并不是一个问题。因为有两个事件被捕获了,1)配置文件出现问题,2)未处理的异常/应用退出。
这两个事件之间有关联,有一些重叠,但它们并不相同。
可以这样阅读这些事件:
[ERR] Could not open configuration, the file ./foo.config does not exist
[FTL] An unhandled exception occurred, exiting the application
System.ArgumentException: The file ./foo.config does not exist
 at YourApp.OpenConfig(string filename)
 at ...

第一个事件:出了什么问题?为什么首先要打开文件?这是捕捉所有详细上下文的地方。

第二个事件:为什么应用程序退出?由于没有更高级别的处理程序能够处理异常-嘭,这就是所有日志中的内容。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接