github.com/seeker-insurance/kit@v0.0.13/brake/brake.go (about) 1 //Package brake contains tools for setting up and working with the [airbrake](https://airbrake.io/) error monitoring software. 2 package brake 3 4 import ( 5 "encoding/json" 6 "fmt" 7 "io/ioutil" 8 "net/http" 9 "strings" 10 "sync" 11 12 "github.com/airbrake/gobrake" 13 "github.com/seeker-insurance/kit/functools" 14 "github.com/seeker-insurance/kit/log" 15 "github.com/spf13/cobra" 16 "github.com/spf13/viper" 17 ) 18 19 const ( 20 traceDepth = 5 21 22 SeverityError severity = "error" 23 SeverityWarn severity = "warning" 24 SeverityCritical severity = "critical" 25 ) 26 27 type severity string 28 29 var ( 30 Airbrake *gobrake.Notifier 31 Env string 32 ) 33 34 func init() { 35 cobra.OnInitialize(setup) 36 } 37 38 func setup() { 39 key := viper.GetString("airbrake_key") 40 project := viper.GetInt64("airbrake_project") 41 Env = viper.GetString("airbrake_env") 42 43 if len(key) != 0 && project != 0 { 44 Airbrake = gobrake.NewNotifier(project, key) 45 } 46 } 47 48 func IsSetup() bool { 49 return Airbrake != nil 50 } 51 52 func Notify(e error, req *http.Request, sev severity) { 53 if IsSetup() { 54 notice := gobrake.NewNotice(e, req, traceDepth) 55 setNoticeVars(notice, req, sev) 56 Airbrake.SendNotice(notice) 57 return 58 } 59 log.Warnf("Error (not reported): %v", e) 60 } 61 62 func NotifyFromChan(errs chan error, wg *sync.WaitGroup) { 63 wg.Add(1) 64 go func() { 65 for e := range errs { 66 Notify(e, nil, SeverityError) 67 } 68 wg.Done() 69 }() 70 } 71 72 func setNoticeVars(n *gobrake.Notice, req *http.Request, sev severity) { 73 n.Context["environment"] = Env 74 n.Context["severity"] = sev 75 if expectBody(req) { 76 n.Params["body"] = body(req) 77 } 78 } 79 80 func body(req *http.Request) interface{} { 81 b, err := ioutil.ReadAll(req.Body) 82 if err != nil { 83 return fmt.Sprintf("error reading body: %v", err) 84 } 85 86 if len(b) == 0 { 87 return "no body" 88 } 89 90 if !isJSONContentType(req) { 91 return string(b) 92 } 93 94 // if !json.Valid(b) { 95 // return fmt.Sprintf("body is not valid JSON: %s", b) 96 // } 97 98 formatted := make(map[string]interface{}) 99 json.Unmarshal(b, &formatted) 100 101 return formatted 102 } 103 104 func isJSONContentType(req *http.Request) bool { 105 cType := req.Header.Get("Content-Type") 106 cType = strings.ToLower(cType) 107 return strings.Contains(cType, "json") 108 } 109 110 func expectBody(req *http.Request) bool { 111 if req == nil { 112 return false 113 } 114 requestsWithBody := []string{http.MethodPost, http.MethodPatch, http.MethodPut} 115 return functools.StringSliceContains(requestsWithBody, req.Method) 116 }