Go包是否应该使用log.Fatal以及何时使用?

mil*_*onb 20 go fatal-error

到目前为止我一直避免使用log.Fatal,但我最近偶然发现了这些问题; 代码覆盖测试使用对数致命.

100个代码覆盖问题中的一条评论说:

...在绝大多数情况下,log.Fatal应该只在main或init函数中使用(或者可能只是某些东西只能直接从它们调用)"

它让我思考,所以我开始查看Go提供的标准库代码.有很多例子,库中的测试代码使用log.Fatal它似乎很好.测试代码之外还有一些例子net/http,如下图所示:

// net/http/transport.go 
func (t *Transport) putIdleConn(pconn *persistConn) bool {
    ...
    for _, exist := range t.idleConn[key] {
        if exist == pconn {
            log.Fatalf("dup idle pconn %p in freelist", pconn)
        }
    }
    ...
}
Run Code Online (Sandbox Code Playgroud)

如果最好的做法是避免使用log.Fatal,为什么在标准库中使用它,我本来希望只返回一个错误.对库的用户来说,os.Exit调用它并不提供应用程序清理的任何机会似乎是不公平的.

我可能是天真的,所以我的问题似乎是一个更好的实践似乎是呼唤log.Panic哪些可以恢复,我理论上长期稳定的应用可能有可能从灰烬中升起.

那么什么是最佳实践说Go应该什么时候应该使用log.Fatal?

Mar*_*erg 27

它可能只是我,但这是我如何使用log.Fatal.根据UNIX约定,遇到错误的进程应尽早使用非零退出代码失败.这导致我在以下指导时使用log.Fatal......

  1. ...在我的任何一个中都会发生错误func init(),因为这些错误分别在处理导入或调用主函数之前发生.相反,我只做不直接影响库或cmd应该做的工作单元的东西.例如,我设置了日志记录并检查我们是否有一个理智的环境和参数.如果我们有无效标志,则无需运行main,对吧?如果我们无法提供适当的反馈,我们应该尽早告诉我们.
  2. ......发生的错误我不知道是不可恢复的.假设我们有一个程序可以创建命令行上给出的图像文件的缩略图.如果此文件由于权限不足而不存在或不可读,则没有理由继续,并且无法从中恢复此错误.所以我们遵守惯例而失败.
  3. ...在一个可能不可逆的过程中发生错误.我知道,这是一种软性定义.让我来说明一下.假设我们有一个实现 cp,它开始是非交互式的,并且递归地复制一个目录.现在,让我们假设我们在目标目录中遇到一个文件,该文件具有相同的名称(但内容不同)作为要复制的文件.由于我们不能要求用户决定做什么而我们无法复制此文件,因此我们遇到了问题.因为当我们完成退出代码零时,用户将假定源目录和目标目录是精确副本,我们不能简单地跳过相关文件.但是,我们不能简单地覆盖它,因为这可能会破坏信息.这是我们无法从用户的明确请求中恢复log.Fatal的情况,因此我将用于解释这种情况,从而尽快遵守原则.

  • 我喜欢您的回答,因为它很好地解释了要点(并在这个主题上引起了我自己的想法),但是我担心它会遗漏关键的一点:OP明确询问有关在包装中*使用log.Fatal`的问题-也就是说,在一段不受“ main()”编写者控制的代码中。如您所见,这实际上将问题从“行为良好的过程”域转移到“行为良好的程序包”域-这个问题的说法截然不同:是否可以无可挽回地使别人的程序失败? (3认同)
  • 它被涵盖,尽管有点隐含:无论我是编写包还是 cmd,我都会应用这些规则:不可恢复的错误是不可恢复的错误。提供经过消毒的包输入是程序的责任。如果这是不可能的,则应返回错误,但前提是输入无法事先清理。所以在适当的地方致命实际上可以帮助包的用户编写更好的代码。 (2认同)
  • 事实是,无论是否不可恢复,这都不是软件包维护者的要求。当然,您无法生成缩略图,但是我将其用作Web服务器的一小部分。如果缩略图程序包因为无法加载文件而调用os.Exit,我会很生气。 (2认同)

小智 10

马库斯,我看到了你的回应,我认为它非常好而且非常有洞察力,我倾向于同意你的分析。很难一概而论,尽管作为 Go 的新手,我一直在思考这一点。我认为在理论层面上,如果我们正在寻找计算方面的最佳实践,无论操作系统、包框架或库如何,记录器的职责就是简单地记录。在任何级别,记录员的职责:

  • 与我选择的渠道一致地格式化和打印信息。
  • 分类、过滤、显示不同的日志级别[debug、info、warn、error]
  • 跨异步和并行作业处理和聚合日志条目

如果程序运行正常,日志记录包或任何包都没有也不应该有权使程序崩溃。任何中间件或库都应该遵循抛出/捕获模式,以便调用者有机会捕获所有抛出的异常。这也是在应用程序中遵循的一个很好的模式,当您构建为应用程序的各个部分以及可能的其他应用程序提供支持的基础和包时,它们永远不应该直接使应用程序崩溃。相反,它们应该抛出致命异常,允许程序处理。我认为这也解决了马库斯你的一些观点,因为它可以在未捕获时立即提醒呼叫者,作为致命的崩溃。

在大多数情况下,我可以直接利用 Go 中的 log.Fatal 来实现直接面向用户的 cli 工具,我认为这才是它真正的目的所在。我认为作为处理跨包致命错误的长期方法没有很好的意义。