recovery.go 1.9 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768
  1. package logging
  2. import (
  3. "fmt"
  4. "io/ioutil"
  5. "net/http"
  6. "os"
  7. "runtime/debug"
  8. "github.com/davecgh/go-spew/spew"
  9. "github.com/pkg/errors"
  10. )
  11. // RecoveryHandler recovers handler panics and logs them using LogPanicWithStack
  12. func RecoveryHandler() func(http.Handler) http.Handler {
  13. return func(next http.Handler) http.Handler {
  14. return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
  15. defer func() {
  16. if r := recover(); r != nil {
  17. log := FromContext(req.Context())
  18. if log == nil {
  19. log = Logger("RecoveryHandler")
  20. }
  21. if err := LogPanicWithStack(log, "httpRecovery", r); err != nil {
  22. fmt.Fprintf(os.Stderr, "PanicLog failed! %q", err)
  23. panic(err)
  24. }
  25. http.Error(w, "internal processing error - please try again", http.StatusInternalServerError)
  26. }
  27. }()
  28. next.ServeHTTP(w, req)
  29. })
  30. }
  31. }
  32. // LogPanicWithStack writes the passed value r, together with a debug.Stack to a tmpfile and logs its location
  33. func LogPanicWithStack(log Interface, location string, r interface{}, vals ...interface{}) error {
  34. if log == nil {
  35. log = internal
  36. }
  37. var err error
  38. switch t := r.(type) {
  39. case string:
  40. err = errors.New(t)
  41. case error:
  42. err = t
  43. default:
  44. err = errors.Errorf("unkown type(%T) error: %v", r, r)
  45. }
  46. os.Mkdir("panics", os.ModePerm)
  47. b, tmpErr := ioutil.TempFile("panics", location)
  48. if tmpErr != nil {
  49. log.Log("event", "panic", "location", location, "err", err, "warning", "no temp file", "tmperr", tmpErr)
  50. return errors.Wrapf(tmpErr, "LogPanic: failed to create httpRecovery log")
  51. }
  52. fmt.Fprintf(b, "warning! %s!\nError:\n%+v\n", location, err)
  53. for i, v := range vals {
  54. spew.Fdump(b, "val(%d): %#v\n", i, v)
  55. }
  56. fmt.Fprintf(b, "\n\nCall Stack:\n%s", debug.Stack())
  57. log.Log("event", "panic", "location", location, "panicLog", b.Name())
  58. fmt.Fprintf(os.Stderr, "panicWithStack: wrote %s\n", b.Name())
  59. return errors.Wrap(b.Close(), "LogPanic: failed to close dump file")
  60. }