github.com/zak-blake/goa@v1.4.1/middleware/error_handler.go (about) 1 package middleware 2 3 import ( 4 "fmt" 5 "net/http" 6 7 "context" 8 9 "github.com/goadesign/goa" 10 ) 11 12 // ErrorHandler turns a Go error into an HTTP response. It should be placed in the middleware chain 13 // below the logger middleware so the logger properly logs the HTTP response. ErrorHandler 14 // understands instances of goa.ServiceError and returns the status and response body embodied in 15 // them, it turns other Go error types into a 500 internal error response. 16 // If verbose is false the details of internal errors is not included in HTTP responses. 17 // If you use github.com/pkg/errors then wrapping the error will allow a trace to be printed to the logs 18 func ErrorHandler(service *goa.Service, verbose bool) goa.Middleware { 19 return func(h goa.Handler) goa.Handler { 20 return func(ctx context.Context, rw http.ResponseWriter, req *http.Request) error { 21 e := h(ctx, rw, req) 22 if e == nil { 23 return nil 24 } 25 cause := cause(e) 26 status := http.StatusInternalServerError 27 var respBody interface{} 28 if err, ok := cause.(goa.ServiceError); ok { 29 status = err.ResponseStatus() 30 respBody = err 31 goa.ContextResponse(ctx).ErrorCode = err.Token() 32 rw.Header().Set("Content-Type", goa.ErrorMediaIdentifier) 33 } else { 34 respBody = e.Error() 35 rw.Header().Set("Content-Type", "text/plain") 36 } 37 if status == http.StatusInternalServerError { 38 reqID := ctx.Value(reqIDKey) 39 if reqID == nil { 40 reqID = shortID() 41 ctx = context.WithValue(ctx, reqIDKey, reqID) 42 } 43 goa.LogError(ctx, "uncaught error", "err", fmt.Sprintf("%+v", e), "id", reqID, "msg", respBody) 44 if !verbose { 45 rw.Header().Set("Content-Type", goa.ErrorMediaIdentifier) 46 msg := fmt.Sprintf("%s [%s]", http.StatusText(http.StatusInternalServerError), reqID) 47 respBody = goa.ErrInternal(msg) 48 // Preserve the ID of the original error as that's what gets logged, the client 49 // received error ID must match the original 50 if origErrID := goa.ContextResponse(ctx).ErrorCode; origErrID != "" { 51 respBody.(*goa.ErrorResponse).ID = origErrID 52 } 53 } 54 } 55 return service.Send(ctx, status, respBody) 56 } 57 } 58 } 59 60 // Cause returns the underlying cause of the error, if possible. 61 // An error value has a cause if it implements the following 62 // interface: 63 // 64 // type causer interface { 65 // Cause() error 66 // } 67 // 68 // If the error does not implement Cause, the original error will 69 // be returned. If the error is nil, nil will be returned without further 70 // investigation. 71 func cause(e error) error { 72 type causer interface { 73 Cause() error 74 } 75 for { 76 cause, ok := e.(causer) 77 if !ok { 78 break 79 } 80 c := cause.Cause() 81 if c == nil { 82 break 83 } 84 e = c 85 } 86 return e 87 }