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  }