ErrorsWithStack Plugin
The errorswithstack
plugin is a Goa v3 plugin
that adds a stack trace to every original service error.
This plugin depends on github.com/cockroachdb/errors/withstack
.
Enabling the Plugin
To enable the plugin simply import as follows:
import (
_ "github.com/tchssk/goaplugins/v3/errorswithstack"
. "goa.design/goa/v3/dsl"
)
Effects on Code Generation
Enabling the plugin changes the behavior of the gen
command of the goa
tool.
The gen
command output is modified as follows:
-
All error initialization helper functions are modified to add a stack trace to the original service error using 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)
}
Middleware
You can capture errors using the Goa endpoint middleware. The topmost caller can be extracted using 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
})
}
}
or 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
})
}
}
The error's underlying concrete value is ServiceError
. You can also create conditions by making type assertion:
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
})
}
}
You can also report errors to Sentry using report.ReportError
:
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
})
}
}