Golang: pkg/errorsのスタックトレースをきれい表示するやつとDatadogのエラートラッキング

三行で

  • pkg/errorsのスタックトレースが見にくい
  • DatadogのError Trackingが見にくい
  • まあまあなんとかするやつを作った

pkg/errors

Goのスタックトレース事情については、以下を参照のこと。

zenn.dev

現状では標準ライブラリにスタックトレースが実装されていないので、とりあえずpkg/errorsを使っている*1が、スタックトレースの表示がめちゃくちゃ見にくい。

package main

import (
    "fmt"
    "os"

    "github.com/pkg/errors"
)

func main() {
    err := f1()
    fmt.Printf("%+v", err)
}

func f1() error {
    return errors.Wrap(f2(), "from f1()")
}

func f2() error {
    return errors.Wrap(f3(), "from f2()")
}

func f3() error {
    _, err := os.Open("not_found")
    return errors.Wrap(err, "from f3()")
}

gist.github.com

なのでもう少しきれいに表示するライブラリを作った。

github.com

pperr.Print(err)

以下のように表示される。

gist.github.com

若干重い処理をしているが、エラー発生時は仕方ないということにした。

Datadogのエラートラッキング

DatadogはAPMに統合されたエラートラッキング機能を提供していて、Golangにも対応している。 APMの1リクエストに紐付いたエラーを表示できるが、スタックトレースの抽出が貧弱であんまりイケてない。

   router.HandleFunc("/hello", func(rw http.ResponseWriter, r *http.Request) {
        span, _ := tracer.SpanFromContext(r.Context())
        err := f1()

        if err != nil {
            rw.WriteHeader(http.StatusInternalServerError)
            span.SetTag(ext.Error, err)
        } else {
            fmt.Fprintln(rw, "hello")
        }
    })

上記のようにspan.SetTag(ext.Error, err)するとこのような表示になる。

f:id:winebarrel:20220220174212p:plain

ext.ErrorDetailsにpkg/errorsのスタックトレースをセットしているが、コンソールに表示されるのはtakeStacktrace()のほうらしい。 なので直接ext.ErrorStackを設定する。

           span.SetTag(ext.Error, err)
            span.SetTag(ext.ErrorStack, pperr.Sprint(err))

一応、スタックトレースが表示されるが、インデントが潰れてあんまり見やすくない。

f:id:winebarrel:20220220175441p:plain

なのでインデントを全角スペースするという微妙なことをする。 さらにErrorTypeとして*errors.withStackが表示されてもうれしくないので、直近のcause error typeを表示するようにする。

            span.SetTag(ext.Error, err)
            span.SetTag(ext.ErrorStack, pperr.SprintFunc(err, pperr.NewPrinterWithIndent("    ")))
            span.SetTag(ext.ErrorType, pperr.CauseType(err))

これでまあまあ見やすくなったと思う。

f:id:winebarrel:20220220175834p:plain

ちなみにext.ErrorTypeスタックトレースをセットするというさらによろしくなさそうなことをすると、全角スペースは不要になる。

           span.SetTag(ext.ErrorType, pperr.Sprint(err))

f:id:winebarrel:20220220180058p:plain

エラー表示の機能的にはSentryと大きな違いはなさそうだけれど、UXがだいぶ劣っているのでDatadogにはもう少し頑張って欲しい。 (フィードバックしたらなんとかなるだろうか)

*1:アーカイブ済みなのは把握しているが、代替を見つけられていないので…