Go: 独自エラーを整形してSentryに送信する
はじめに
Goのアプリケーションで独自エラー情報を整形してSentryに送るTipsを紹介します。
前提としてhttps://github.com/getsentry/sentry-goを使うことを想定しています。
スタックトレースを送る
エラー発生元のスタックトレースを送るためには大きく分けて2つステップが必要です。
- エラーにスタックトレースを追加する
-
sentry-go
で決められたメソッドを実装するエラーを作成する
では、それぞれ説明します。
1. エラーにスタックトレースを追加する
Goではデフォルトでエラーにスタックトレースが付与されません。
そのため、独自エラーやエラーハンドリングライブラリを使ってスタックトレースを管理する必要があります。
スタックトレースを付与できる便利なライブラリはいくつかあります。
個人的にfailure
は使いやすくて好きです。
https://github.com/morikuni/failure
https://github.com/cockroachdb/errors
独自エラーでスタックトレースを持たせる場合は、runtime.Callers
を使ってプログラムカウンターを取得して、runtime.CallersFrames
を使って関数の情報を取得できます。
この関数の情報をエラーに保存しておき、スタックトレースとして出力します。
type Frame []uintptr
func caller(skip int) Frame {
f := [16]uintptr{}
n := runtime.Callers(skip+1, f[:])
return f[:n]
}
func (f Frame) location() (function, file string, line int) {
frames := runtime.CallersFrames(f[:])
if _, ok := frames.Next(); !ok {
return "", "", 0
}
fr, ok := frames.Next()
if !ok {
return "", "", 0
}
return fr.Function, fr.File, fr.Line
}
独自エラーの例
独自エラーにframeを追加して、customErrorが生成された位置をframeに埋め込んでおく
type customError struct {
message string
// 根本のエラー
cause error
// 埋め込んでおく
frame Frame
}
func newCustomError(msg string) *customError {
e := new(customError)
e.message = msg
e.frame = caller(1)
return e
}
sentry-go
で決められたメソッドを実装するエラーを作成する
2.sentry-go
では、スタックトレースを出力するために、sentry.ExtractStacktrace
を内部で使っています。
内部には特定のライブラリのエラーを前提として決め打ちでメソッドを呼び出す処理があります。
参考: https://github.com/getsentry/sentry-go/blob/master/stacktrace.go
// https://github.com/getsentry/sentry-go/blob/master/stacktrace.go#L83-L87
func extractReflectedStacktraceMethod(err error) reflect.Value {
errValue := reflect.ValueOf(err)
// https://github.com/go-errors/errors
methodStackFrames := errValue.MethodByName("StackFrames")
if methodStackFrames.IsValid() {
return methodStackFrames
}
// https://github.com/pkg/errors
methodStackTrace := errValue.MethodByName("StackTrace")
if methodStackTrace.IsValid() {
return methodStackTrace
}
// https://github.com/pingcap/errors
methodGetStackTracer := errValue.MethodByName("GetStackTracer")
if methodGetStackTracer.IsValid() {
stacktracer := methodGetStackTracer.Call(nil)[0]
stacktracerStackTrace := reflect.ValueOf(stacktracer).MethodByName("StackTrace")
if stacktracerStackTrace.IsValid() {
return stacktracerStackTrace
}
}
return reflect.Value{}
}
上記のコードは、決め打ちでメソッドを呼び出せるかチェックしているため、同じメソッドをエラーに実装する必要があります。
このためsentryに対応していないエラーハンドリングライブラリを使っている場合は、独自にエラーを作成し、メソッドを実装する必要があります。
今回は StackTrace
メソッドを実装して、エラーからスタックトレースを取得できるようにしてみます
type customError struct {
message string
frame Frame
}
func (c *customError) StackTrace() []uintptr {
return c.frame
}
イベントの形を整形する
エラーをSentryに送信する際は以下のメソッドを呼び出しますが、カスタムエラーの場合は、sentryのType(エラーのタイトル)が型名になってしまいます。
sentry.CaptureException(err)
グローバルでエラーを整形する
Sentryの初期化時に、BeforeSendを使って、エラーのTypeをエラーメッセージに変更することができます
err := sentry.Init(sentry.ClientOptions{
Dsn: "hogehoge"
Environment: "local",
BeforeSend: func(event *sentry.Event, hint *sentry.EventHint) *sentry.Event {
// エラー意外にも通常のメッセージもこの処理に入るので、エラーのみ処理する
if hint.OriginalException == nil {
return event
}
for i := range event.Exception {
exception := &event.Exception[i]
// tracker.Errorのタイトルを書き換える
if strings.Contains(exception.Type, "customError") {
// Typeがタイトルに当たる
exception.Type = hint.OriginalException.Error()
}
}
return event
},
})
ちなみに、BeforeSend
でnilを返すとSentryにイベントを送信しないようにできるので、特定の条件のみ送らないというのもここでもハンドリングが可能です
イベントをカスタムで設定する
CaptureException
を使わずに独自でイベントを作成して送信することもできます
event := sentry.NewEvent()
event.Type = "customError"
sentry.CaptureEvent(event)
作ってみた
Sentryにエラーを送信できる独自エラーを作ってみました。
エラーの初期化
var InvalidParameterError = serrs.New(serrs.DefaultCode("invalid_parameter"),"invalid parameter error")
var CustomError = serrs.Wrap(
err,
serrs.WithCode(serrs.DefaultCode("custom_error")),
serrs.WithMessage("custom error"),
)
例
タイトル
一番根本のエラーメッセージがTypeになるようにしてます
スタックトレース
発生したエラーのスタックトレースが出力されています
追加データ
エラーのツリーで追加したデータが一覧で表示されます
おまけ
-
fmt.Printf("%+v",err)
で、Wrapしたエラーのスタックトレースが出力されます
if err := DoSomething(); err != nil {
// This point is recorded
return serrs.Wrap(err)
}
fmt.Printf("%+v",err)
// Output Example:
// - file: ./serrs/format_test.go:22
// function: github.com/ryomak/serrs_test.TestSerrs_Format
// msg:
// - file: ./serrs/format_test.go:14
// function: github.com/ryomak/serrs_test.TestSerrs_Format
// code: demo
// data: {key1:value1,key2:value2}
// - error1
最後に
Goのアプリケーションで独自エラー情報を整形してSentryに送るTipsを紹介しました。
独自エラーを作成する方の参考になれば幸いです。
Discussion