golang-airbrake-go-0.0~git20150109/0000755000175000000620000000000012523031351016041 5ustar jenkinsstaffgolang-airbrake-go-0.0~git20150109/airbrake.go0000644000175000000620000001364612523023460020164 0ustar jenkinsstaffpackage airbrake import ( "bytes" "errors" "io/ioutil" "log" "net/http" "os" "reflect" "regexp" "runtime" "sync" "text/template" ) var ( ApiKey = "" Endpoint = "https://api.airbrake.io/notifier_api/v2/notices" Environment = "development" Verbose = false // PrettyParams allows including request query/form parameters on the Environment tab // which is more readable than the raw text of the Parameters tab (in Errbit). // The param keys will be rendered as "?" so they will sort together at the top of the tab. PrettyParams = false sensitive = regexp.MustCompile(`password|token|secret|key`) badResponse = errors.New("Bad response") apiKeyMissing = errors.New("Please set the airbrake.ApiKey before doing calls") dunno = []byte("???") centerDot = []byte("·") dot = []byte(".") tmpl = template.Must(template.New("error").Parse(source)) ) type Line struct { Function string File string Line int } // stack implements Stack, skipping N frames func stacktrace(skip int) (lines []Line) { for i := skip; ; i++ { pc, file, line, ok := runtime.Caller(i) if !ok { break } item := Line{string(function(pc)), string(file), line} // ignore panic method if item.Function != "panic" { lines = append(lines, item) } } return } var channel chan map[string]interface{} var once sync.Once // function returns, if possible, the name of the function containing the PC. func function(pc uintptr) []byte { fn := runtime.FuncForPC(pc) if fn == nil { return dunno } name := []byte(fn.Name()) // The name includes the path name to the package, which is unnecessary // since the file name is already included. Plus, it has center dots. // That is, we see // runtime/debug.*T·ptrmethod // and want // *T.ptrmethod if period := bytes.Index(name, dot); period >= 0 { name = name[period+1:] } name = bytes.Replace(name, centerDot, dot, -1) return name } func initChannel() { channel = make(chan map[string]interface{}, 100) go func() { for params := range channel { post(params) } }() } func post(params map[string]interface{}) error { buffer := bytes.NewBufferString("") if err := tmpl.Execute(buffer, params); err != nil { log.Printf("Airbrake error: %s", err) return err } if Verbose { log.Printf("Airbrake payload for endpoint %s: %s", Endpoint, buffer) } response, err := http.Post(Endpoint, "text/xml", buffer) if err != nil { log.Printf("Airbrake error: %s", err) return err } if Verbose { body, _ := ioutil.ReadAll(response.Body) log.Printf("response: %s", body) } response.Body.Close() if Verbose { log.Printf("Airbrake post: %s status code: %d", params["Error"], response.StatusCode) } return nil } func Error(e error, request *http.Request) error { once.Do(initChannel) if ApiKey == "" { return apiKeyMissing } return post(params(e, request)) } func Notify(e error) error { once.Do(initChannel) if ApiKey == "" { return apiKeyMissing } return post(params(e, nil)) } func params(e error, request *http.Request) map[string]interface{} { params := map[string]interface{}{ "Class": reflect.TypeOf(e).String(), "Error": e, "ApiKey": ApiKey, "ErrorName": e.Error(), "Environment": Environment, } if params["Class"] == "" { params["Class"] = "Panic" } pwd, err := os.Getwd() if err == nil { params["Pwd"] = pwd } hostname, err := os.Hostname() if err == nil { params["Hostname"] = hostname } params["Backtrace"] = stacktrace(3) if request == nil || request.ParseForm() != nil { return params } // Compile relevant request parameters into a map. req := make(map[string]interface{}) params["Request"] = req req["Component"] = "" req["Action"] = "" // Nested http Muxes muck with the URL, prefer RequestURI. if request.RequestURI != "" { req["URL"] = request.RequestURI } else { req["URL"] = request.URL } // Compile header parameters. header := make(map[string]string) req["Header"] = header header["Method"] = request.Method header["Protocol"] = request.Proto for k, v := range request.Header { if !omit(k, v) { header[k] = v[0] } } // Compile query/form parameters. form := make(map[string]string) req["Form"] = form for k, v := range request.Form { if !omit(k, v) { form[k] = v[0] if PrettyParams { header["?"+k] = v[0] } } } return params } // omit checks the key, values for emptiness or sensitivity. func omit(key string, values []string) bool { return len(key) == 0 || len(values) == 0 || len(values[0]) == 0 || sensitive.FindString(key) != "" } func CapturePanic(r *http.Request) { if rec := recover(); rec != nil { if err, ok := rec.(error); ok { log.Printf("Recording err %s", err) Error(err, r) } else if err, ok := rec.(string); ok { log.Printf("Recording string %s", err) Error(errors.New(err), r) } panic(rec) } } const source = ` {{ .ApiKey }} Airbrake Golang 0.0.1 http://airbrake.io {{ html .Class }} {{ html .ErrorName }} {{ range .Backtrace }} {{ end }} {{ with .Request }} {{html .URL}} {{ .Component }} {{ .Action }} {{ range $key, $value := .Form }} {{ $value }}{{ end }} {{ range $key, $value := .Header }} {{ $value }}{{ end }} {{ end }} {{ html .Pwd }} {{ .Environment }} {{ html .Hostname }} ` golang-airbrake-go-0.0~git20150109/airbrake_test.go0000644000175000000620000000521412523023460021213 0ustar jenkinsstaffpackage airbrake import ( "bytes" "errors" "net/http" "regexp" "testing" "time" ) const API_KEY = "" func TestError(t *testing.T) { Verbose = true ApiKey = API_KEY Endpoint = "https://api.airbrake.io/notifier_api/v2/notices" err := Error(errors.New("Test Error"), nil) if err != nil { t.Error(err) } time.Sleep(1e9) } func TestRequest(t *testing.T) { Verbose = true ApiKey = API_KEY Endpoint = "https://api.airbrake.io/notifier_api/v2/notices" request, _ := http.NewRequest("GET", "/some/path?a=1", bytes.NewBufferString("")) err := Error(errors.New("Test Error"), request) if err != nil { t.Error(err) } time.Sleep(1e9) } func TestNotify(t *testing.T) { Verbose = true ApiKey = API_KEY Endpoint = "https://api.airbrake.io/notifier_api/v2/notices" err := Notify(errors.New("Test Error")) if err != nil { t.Error(err) } time.Sleep(1e9) } // Make sure we match https://help.airbrake.io/kb/api-2/notifier-api-version-23 func TestTemplateV2(t *testing.T) { var p map[string]interface{} request, _ := http.NewRequest("GET", "/query?t=xxx&q=SHOW+x+BY+y+FROM+z&key=sesame&timezone=", nil) request.Header.Set("Host", "Zulu") request.Header.Set("Keep_Secret", "Sesame") PrettyParams = true defer func() { PrettyParams = false }() // Trigger and recover a panic, so that we have something to render. func() { defer func() { if r := recover(); r != nil { p = params(r.(error), request) } }() panic(errors.New("Boom!")) }() // Did that work? if p == nil { t.Fail() } // Crude backtrace check. if len(p["Backtrace"].([]Line)) < 3 { t.Fail() } // It's messy to generically test rendered backtrace, drop it. delete(p, "Backtrace") // Render the params. var b bytes.Buffer if err := tmpl.Execute(&b, p); err != nil { t.Fatalf("Template error: %s", err) } // Validate the node. chunk := regexp.MustCompile(`(?s).*`).FindString(b.String()) if chunk != ` *errors.errorString Boom! ` { t.Fatal(chunk) } // Validate the node. chunk = regexp.MustCompile(`(?s).*`).FindString(b.String()) if chunk != ` /query?t=xxx&q=SHOW+x+BY+y+FROM+z&key=sesame&timezone= SHOW x BY y FROM z xxx SHOW x BY y FROM z xxx Zulu GET HTTP/1.1 ` { t.Fatal(chunk) } } golang-airbrake-go-0.0~git20150109/handler.go0000644000175000000620000000063212523023460020010 0ustar jenkinsstaffpackage airbrake import ( "net/http" ) // CapturePanicHandler "middleware". // Wraps the http handler so that all panics will be dutifully reported to airbrake // // Example: // http.HandleFunc("/", airbrake.CapturePanicHandler(MyServerFunc)) func CapturePanicHandler(app http.HandlerFunc) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { defer CapturePanic(r) app(w, r) } } golang-airbrake-go-0.0~git20150109/LICENSE0000644000175000000620000000204112523023460017045 0ustar jenkinsstaffCopyright (c) 2015 Tobias Lütke Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. golang-airbrake-go-0.0~git20150109/README0000644000175000000620000000101712523023460016722 0ustar jenkinsstaffConfig ====== set airbrake.Endpoint and airbrake.ApiKey globals Methods ======= airbrake.Error(err) reports an error airbrake.RequestError(err, *http.Request) can be used to add more context if you are in a http context You can also automatically have this library report panics, use this method: airbrake.CapturePanic(*http.Request) example: func serve(w http.ResponseWriter, r *http.Request) { defer airbrake.CapturePanic(r) [...] panic("Oh no :-(") // will be recorded by airbrake }