golang-airbrake-go-0.0~git20150109/ 0000755 0001750 0000062 00000000000 12523031351 016041 5 ustar jenkins staff golang-airbrake-go-0.0~git20150109/airbrake.go 0000644 0001750 0000062 00000013646 12523023460 020164 0 ustar jenkins staff package 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.go 0000644 0001750 0000062 00000005214 12523023460 021213 0 ustar jenkins staff package 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.go 0000644 0001750 0000062 00000000632 12523023460 020010 0 ustar jenkins staff package 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/LICENSE 0000644 0001750 0000062 00000002041 12523023460 017045 0 ustar jenkins staff Copyright (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/README 0000644 0001750 0000062 00000001017 12523023460 016722 0 ustar jenkins staff Config
======
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
}