errorswithstack
はGoa v3 のプラグインで、オリジナルのサービスエラーにスタックトレースを追加します。このプラグインは github.com/cockroachdb/errors/withstack
に依存しています。
プラグインの有効化
プラグインを有効にするには、下記のように errorswithstack を import します。
import ( _ "github.com/tchssk/goaplugins/v3/errorswithstack" . "goa.design/goa/v3/dsl" )
コード生成への影響
プラグインを有効化すると goa
ツールの gen
コマンドの挙動が変わります。
gen
コマンドの出力は次のように変更されます:
すべてのエラー初期化用ヘルパー関数は、
WithStackDepth()
を使ってオリジナルのサービスエラーにスタックトレースを追加するよう変更されます。func MakeInternalError(err error) *goa.ServiceError { - return goa.NewServiceError(err, "internal_error", false, false, true) + return goa.NewServiceError(withstack.WithStackDepth(err, 1), "internal_error", false, false, true) }
ミドルウェア
Goa エンドポイントミドルウェアを使ってエラーをキャプチャーすることができます。 GetOneLineSource()
を使って呼び出し元を抽出することができます:
func ErrorLogger(logger *log.Logger) func(goa.Endpoint) goa.Endpoint { return func(e goa.Endpoint) goa.Endpoint { return goa.Endpoint(func(ctx context.Context, req any) (any, error) { res, err := e(ctx, req) if err != nil { file, line, _, ok := withstack.GetOneLineSource(err) if ok { logger.Printf("%s:%d: %v", file, line, err) // file.go:15 something went wrong } } return res, err }) } }
もしくは GetReportableStackTrace()
を使うこともできます;
func ErrorLogger(logger *log.Logger) func(goa.Endpoint) goa.Endpoint { return func(e goa.Endpoint) goa.Endpoint { return goa.Endpoint(func(ctx context.Context, req any) (any, error) { res, err := e(ctx, req) if err != nil { if st := withstack.GetReportableStackTrace(errors.Unwrap(err)); st != nil { if len(st.Frames) >= 1 { frame := st.Frames[len(st.Frames)-1] logger.Printf("%s:%d: %v", frame.AbsPath, frame.Lineno, err) // /path/to/file.go:15 something went wrong } } } return res, err }) } }
エラーの基になる具体的な値は ServiceError
です。型アサーションして条件を作ることもできます:
func ErrorLogger(logger *log.Logger) func(goa.Endpoint) goa.Endpoint { return func(e goa.Endpoint) goa.Endpoint { return goa.Endpoint(func(ctx context.Context, req any) (any, error) { res, err := e(ctx, req) if err != nil { if serviceError, ok := err.(*goa.ServiceError); ok { if serviceError.Fault { file, line, _, ok := withstack.GetOneLineSource(err) if ok { logger.Printf("%s:%d: %v", file, line, err) // file.go:15 something went wrong } } } } return res, err }) } }
また report.ReportError
を使って Sentry にエラーを送ることもできます:
func ErrorReporter() func(goa.Endpoint) goa.Endpoint { return func(e goa.Endpoint) goa.Endpoint { return goa.Endpoint(func(ctx context.Context, req any) (any, error) { res, err := e(ctx, req) if err != nil { report.ReportError(err) } return res, err }) } }