【问题标题】:How to get logrus to print stack of pkg/errors如何让 logrus 打印 pkg/错误堆栈
【发布时间】:2019-01-25 16:31:44
【问题描述】:

我正在使用 github.com/sirupsen/logrus 和 github.com/pkg/errors。当我处理从 pkg/errors 包装或创建的错误时,我在注销中看到的只是错误消息。我想查看堆栈跟踪。

从这个问题,https://github.com/sirupsen/logrus/issues/506,我推断 logrus 有一些处理 pkg/errors 的本地方法。

我该怎么做?

【问题讨论】:

  • 被问到here,然后是here,然后是there。现在出现了这个关于相同问题的新问题,没有提及先前的讨论,并且基本上从there复制了一个自我提供的答案。嗯……我不知道,但这感觉……很奇怪。
  • 垃圾邮件不是解决办法。我注意到你来自 Java 背景......请理解 go 不是 Java,错误不是异常。 (也:logrus.Errorf("%+v", err)
  • 我曾试图帮助您处理您以前的一些几乎相同的帖子,但您变得充满敌意。我认为说你被“骚扰”并不准确。但撇开这些不谈,我对您的主要建议是阅读文档,因为我从个人经验中知道这是可以做到的。种种迹象表明,你拒绝这样做。我在下面的回答进一步证明您甚至没有阅读过 pkg/errors 文档,这将使您轻松完成这项任务。

标签: go


【解决方案1】:

对您的 Logrus 问题的评论不正确(顺便说一句,似乎来自与 Logrus 没有任何关系的人,也没有为 Logrus 做出任何贡献,因此实际上并非来自“Logrus 团队”)。

很容易提取pkg/errors错误中的堆栈跟踪,as documented

type stackTracer interface {
        StackTrace() errors.StackTrace
}

这意味着使用 logrus 记录堆栈跟踪的最简单方法是:

if stackErr, ok := err.(stackTracer); ok {
    log.WithField("stacktrace", fmt.Sprintf("%+v", stackErr.StackTrace()))
}

从今天开始,当 my a pull request of mine was merged with pkg/errors 时,如果您使用 JSON 日志记录,这将变得更加容易:

if stackErr, ok := err.(stackTracer); ok {
    log.WithField("stacktrace", stackErr.StackTrace())
}

这将生成类似于“%+v”的日志格式,但没有换行符或制表符,每个字符串有一个日志条目,以便于编组为 JSON 数组。

当然,这两个选项都会强制您使用pkg/errors 定义的格式,这并不总是理想的。因此,您可以迭代堆栈跟踪,并生成自己的格式,可能会生成可轻松编组为 JSON 的格式。

if err, ok := err.(stackTracer); ok {
        for _, f := range err.StackTrace() {
                fmt.Printf("%+s:%d\n", f, f) // Or your own formatting
        }
}

您可以将其强制转换为您喜欢的任何格式,而不是打印每一帧。

【讨论】:

  • 正如我之前所说,这个问题的目的是像使用其他所有语言一样无缝地获取堆栈跟踪。您的回答不支持该目标的解决方案。该请求想要添加错误 WithError,然后将堆栈自动添加到日志输出中。您的解决方案需要强制转换和格式化每个可能试图将错误添加到日志的位置。这不方便。您还可以在下面提供 Go-ish 响应,指出错误应该是跟踪其逻辑堆栈的域的逻辑组件。
  • 您是否声称,如果是,请展示您的工作,有某种方式,内置并在​​ Logrus 中激活,以便 entry.WithError(aWrappedError) 并让记录器创建一个完整的堆栈?这就是问题所在。
  • 没有“无缝”的方式来做到这一点,但你也从来没有要求过。但是有一种方法可以获取堆栈跟踪,并使用WithFields 添加它。如果你想要WithError,那么你要么需要修补 logrus(这应该是微不足道的——我不知道他们是否会接受 PR),或者提供你自己的包装器,它提供了这个功能(这是我一直采取的方法)。
  • 如果您只是希望 每个 错误在 error 字段中包含堆栈跟踪,您也可以使用自定义 error 类型来完成此操作。没有直接的答案,因为没有明显的方法可以做到这一点。而且您的问题还不够具体,无法规定所有可能的方法。这就是为什么“阅读文档”确实是您问题的最佳答案。
【解决方案2】:

推理是错误的。 Logrus 实际上并不知道如何处理该错误。 更新 Logrus 团队官员表示,这不是一个受支持的功能,https://github.com/sirupsen/logrus/issues/895#issuecomment-457656556

Java 风格的响应 为了以这种方式普遍使用错误处理程序,我编写了一个新版本的 Entry,它来自 Logrus。如示例所示,使用您想要的任何常见字段创建一个新条目(下面的示例是在处理程序中设置的记录器,用于跟踪调用者 ID。在您处理条目时通过您的层传递 PgkError。当您需要记录特定错误,例如调用变量遇到错误,从 PkgError.WithError(...) 开始,然后添加您的详细信息。

这是一个起点。如果您想普遍使用它,请在 PkgErrorEntry 上实现所有 Entity 接口。继续委托给内部条目,但返回一个新的 PkgErrorEntry。这样的更改会使值 true 下降以替代 Entry。

package main

import (
    "fmt"
    "github.com/sirupsen/logrus"
    "strings"

    unwrappedErrors "errors"
    "github.com/pkg/errors"
)

// PkgErrorEntry enables stack frame extraction directly into the log fields.
type PkgErrorEntry struct {
    *logrus.Entry

    // Depth defines how much of the stacktrace you want.
    Depth int
}

// This is dirty pkg/errors.
type stackTracer interface {
    StackTrace() errors.StackTrace
}

func (e *PkgErrorEntry) WithError(err error) *logrus.Entry {
    out := e.Entry

    common := func(pError stackTracer) {
        st := pError.StackTrace()
        depth := 3
        if e.Depth != 0 {
            depth = e.Depth
        }
        valued := fmt.Sprintf("%+v", st[0:depth])
        valued = strings.Replace(valued, "\t", "", -1)
        stack := strings.Split(valued, "\n")
        out = out.WithField("stack", stack[2:])
    }

    if err2, ok := err.(stackTracer); ok {
        common(err2)
    }

    if err2, ok := errors.Cause(err).(stackTracer); ok {
        common(err2)
    }

    return out.WithError(err)
}

func someWhereElse() error {
    return unwrappedErrors.New("Ouch")
}

func level1() error {
    return level2()
}

func level2() error {
    return errors.WithStack(unwrappedErrors.New("All wrapped up"))
}

func main() {
    baseLog := logrus.New()
    baseLog.SetFormatter(&logrus.JSONFormatter{})
    errorHandling := PkgErrorEntry{Entry: baseLog.WithField("callerid", "1000")}

    errorHandling.Info("Hello")

    err := errors.New("Hi")
    errorHandling.WithError(err).Error("That should have a stack.")

    err = someWhereElse()
    errorHandling.WithError(err).Info("Less painful error")

    err = level1()
    errorHandling.WithError(err).Warn("Should have multiple layers of stack")
}

类似 Gopher 的方式 详情请参阅https://www.reddit.com/r/golang/comments/ajby88/how_to_get_stack_traces_in_logrus/

Ben Johnson wrote 关于让错误成为您域的一部分。一个缩写版本是您应该将跟踪器属性放在自定义错误上。当您直接控制的代码发生错误或发生来自 3rd 方库的错误时,立即处理错误的代码应将唯一值放入自定义错误中。该值将作为自定义错误的Error() string 实现的一部分打印出来。

当开发人员获得日志文件时,他们将能够 grep 代码库以获取该唯一值。 Ben 说:“最后,我们需要能够向我们的操作员提供所有这些信息以及逻辑堆栈跟踪,以便他们可以调试问题。Go 已经提供了一个简单的方法 error.Error() 来打印错误信息,以便我们可以利用那个。”

这是本的例子

// attachRole inserts a role record for a user in the database
func (s *UserService) attachRole(ctx context.Context, id int, role string) error {
    const op = "attachRole"
    if _, err := s.db.Exec(`INSERT roles...`); err != nil {
        return &myapp.Error{Op: op, Err: err}
    }
    return nil
}

我对 grep-able 代码的一个问题是值很容易偏离原始上下文。例如,假设函数的名称从 attachRole 更改为其他名称,并且函数更长。操作值可能与函数名称不同。无论如何,这似乎满足了追踪问题的一般需求,同时将错误视为一等公民。

Go2 可能会在这方面引发更多的 Java 风格响应。敬请关注。 https://go.googlesource.com/proposal/+/refs/changes/97/159497/3/design/XXXXX-error-values.md

【讨论】:

    猜你喜欢
    • 2017-08-15
    • 1970-01-01
    • 1970-01-01
    • 2011-12-29
    • 2011-05-25
    • 2016-04-11
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多