77e69b9cf3
Signed-off-by: Olivier Gambier <olivier@docker.com>
461 lines
11 KiB
Go
461 lines
11 KiB
Go
package bugsnag
|
|
|
|
import (
|
|
"fmt"
|
|
"io/ioutil"
|
|
"log"
|
|
"net"
|
|
"net/http"
|
|
"strings"
|
|
"sync"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/bitly/go-simplejson"
|
|
)
|
|
|
|
func TestConfigure(t *testing.T) {
|
|
Configure(Configuration{
|
|
APIKey: testAPIKey,
|
|
})
|
|
|
|
if Config.APIKey != testAPIKey {
|
|
t.Errorf("Setting APIKey didn't work")
|
|
}
|
|
|
|
if New().Config.APIKey != testAPIKey {
|
|
t.Errorf("Setting APIKey didn't work for new notifiers")
|
|
}
|
|
}
|
|
|
|
var postedJSON = make(chan []byte, 10)
|
|
var testOnce sync.Once
|
|
var testEndpoint string
|
|
var testAPIKey = "166f5ad3590596f9aa8d601ea89af845"
|
|
|
|
func startTestServer() {
|
|
testOnce.Do(func() {
|
|
mux := http.NewServeMux()
|
|
mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
|
|
body, err := ioutil.ReadAll(r.Body)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
postedJSON <- body
|
|
})
|
|
|
|
l, err := net.Listen("tcp", "127.0.0.1:0")
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
testEndpoint = "http://" + l.Addr().String() + "/"
|
|
|
|
go http.Serve(l, mux)
|
|
})
|
|
}
|
|
|
|
type _recurse struct {
|
|
*_recurse
|
|
}
|
|
|
|
func TestNotify(t *testing.T) {
|
|
startTestServer()
|
|
|
|
recurse := _recurse{}
|
|
recurse._recurse = &recurse
|
|
|
|
OnBeforeNotify(func(event *Event, config *Configuration) error {
|
|
if event.Context == "testing" {
|
|
event.GroupingHash = "lol"
|
|
}
|
|
return nil
|
|
})
|
|
|
|
Notify(fmt.Errorf("hello world"),
|
|
Configuration{
|
|
APIKey: testAPIKey,
|
|
Endpoint: testEndpoint,
|
|
ReleaseStage: "test",
|
|
AppVersion: "1.2.3",
|
|
Hostname: "web1",
|
|
ProjectPackages: []string{"github.com/bugsnag/bugsnag-go"},
|
|
},
|
|
User{Id: "123", Name: "Conrad", Email: "me@cirw.in"},
|
|
Context{"testing"},
|
|
MetaData{"test": {
|
|
"password": "sneaky",
|
|
"value": "able",
|
|
"broken": complex(1, 2),
|
|
"recurse": recurse,
|
|
}},
|
|
)
|
|
|
|
json, err := simplejson.NewJson(<-postedJSON)
|
|
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
if json.Get("apiKey").MustString() != testAPIKey {
|
|
t.Errorf("Wrong api key in payload")
|
|
}
|
|
|
|
if json.GetPath("notifier", "name").MustString() != "Bugsnag Go" {
|
|
t.Errorf("Wrong notifier name in payload")
|
|
}
|
|
|
|
event := json.Get("events").GetIndex(0)
|
|
|
|
for k, value := range map[string]string{
|
|
"payloadVersion": "2",
|
|
"severity": "warning",
|
|
"context": "testing",
|
|
"groupingHash": "lol",
|
|
"app.releaseStage": "test",
|
|
"app.version": "1.2.3",
|
|
"device.hostname": "web1",
|
|
"user.id": "123",
|
|
"user.name": "Conrad",
|
|
"user.email": "me@cirw.in",
|
|
"metaData.test.password": "[REDACTED]",
|
|
"metaData.test.value": "able",
|
|
"metaData.test.broken": "[complex128]",
|
|
"metaData.test.recurse._recurse": "[RECURSION]",
|
|
} {
|
|
key := strings.Split(k, ".")
|
|
if event.GetPath(key...).MustString() != value {
|
|
t.Errorf("Wrong %v: %v != %v", key, event.GetPath(key...).MustString(), value)
|
|
}
|
|
}
|
|
|
|
exception := event.Get("exceptions").GetIndex(0)
|
|
|
|
if exception.Get("message").MustString() != "hello world" {
|
|
t.Errorf("Wrong message in payload")
|
|
}
|
|
|
|
if exception.Get("errorClass").MustString() != "*errors.errorString" {
|
|
t.Errorf("Wrong errorClass in payload: %v", exception.Get("errorClass").MustString())
|
|
}
|
|
|
|
frame0 := exception.Get("stacktrace").GetIndex(0)
|
|
if frame0.Get("file").MustString() != "bugsnag_test.go" ||
|
|
frame0.Get("method").MustString() != "TestNotify" ||
|
|
frame0.Get("inProject").MustBool() != true ||
|
|
frame0.Get("lineNumber").MustInt() == 0 {
|
|
t.Errorf("Wrong frame0")
|
|
}
|
|
|
|
frame1 := exception.Get("stacktrace").GetIndex(1)
|
|
|
|
if frame1.Get("file").MustString() != "testing/testing.go" ||
|
|
frame1.Get("method").MustString() != "tRunner" ||
|
|
frame1.Get("inProject").MustBool() != false ||
|
|
frame1.Get("lineNumber").MustInt() == 0 {
|
|
t.Errorf("Wrong frame1")
|
|
}
|
|
}
|
|
|
|
func crashyHandler(w http.ResponseWriter, r *http.Request) {
|
|
c := make(chan int)
|
|
close(c)
|
|
c <- 1
|
|
}
|
|
|
|
func runCrashyServer(rawData ...interface{}) (net.Listener, error) {
|
|
l, err := net.Listen("tcp", "127.0.0.1:0")
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
mux := http.NewServeMux()
|
|
mux.HandleFunc("/", crashyHandler)
|
|
srv := http.Server{
|
|
Addr: l.Addr().String(),
|
|
Handler: Handler(mux, rawData...),
|
|
ErrorLog: log.New(ioutil.Discard, log.Prefix(), 0),
|
|
}
|
|
|
|
go srv.Serve(l)
|
|
return l, err
|
|
}
|
|
|
|
func TestHandler(t *testing.T) {
|
|
startTestServer()
|
|
|
|
l, err := runCrashyServer(Configuration{
|
|
APIKey: testAPIKey,
|
|
Endpoint: testEndpoint,
|
|
ProjectPackages: []string{"github.com/bugsnag/bugsnag-go"},
|
|
Logger: log.New(ioutil.Discard, log.Prefix(), log.Flags()),
|
|
}, SeverityInfo)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
http.Get("http://" + l.Addr().String() + "/ok?foo=bar")
|
|
l.Close()
|
|
|
|
json, err := simplejson.NewJson(<-postedJSON)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
if json.Get("apiKey").MustString() != testAPIKey {
|
|
t.Errorf("Wrong api key in payload")
|
|
}
|
|
|
|
if json.GetPath("notifier", "name").MustString() != "Bugsnag Go" {
|
|
t.Errorf("Wrong notifier name in payload")
|
|
}
|
|
|
|
event := json.Get("events").GetIndex(0)
|
|
|
|
for k, value := range map[string]string{
|
|
"payloadVersion": "2",
|
|
"severity": "info",
|
|
"user.id": "127.0.0.1",
|
|
"metaData.Request.Url": "http://" + l.Addr().String() + "/ok?foo=bar",
|
|
"metaData.Request.Method": "GET",
|
|
} {
|
|
key := strings.Split(k, ".")
|
|
if event.GetPath(key...).MustString() != value {
|
|
t.Errorf("Wrong %v: %v != %v", key, event.GetPath(key...).MustString(), value)
|
|
}
|
|
}
|
|
|
|
if event.GetPath("metaData", "Request", "Params", "foo").GetIndex(0).MustString() != "bar" {
|
|
t.Errorf("missing GET params in request metadata")
|
|
}
|
|
|
|
if event.GetPath("metaData", "Headers", "Accept-Encoding").GetIndex(0).MustString() != "gzip" {
|
|
t.Errorf("missing GET params in request metadata: %v", event.GetPath("metaData", "Headers"))
|
|
}
|
|
|
|
exception := event.Get("exceptions").GetIndex(0)
|
|
|
|
if exception.Get("message").MustString() != "runtime error: send on closed channel" {
|
|
t.Errorf("Wrong message in payload: %v", exception.Get("message").MustString())
|
|
}
|
|
|
|
if exception.Get("errorClass").MustString() != "runtime.errorCString" {
|
|
t.Errorf("Wrong errorClass in payload: %v", exception.Get("errorClass").MustString())
|
|
}
|
|
|
|
// TODO:CI these are probably dependent on go version.
|
|
frame0 := exception.Get("stacktrace").GetIndex(0)
|
|
if frame0.Get("file").MustString() != "runtime/panic.c" ||
|
|
frame0.Get("method").MustString() != "panicstring" ||
|
|
frame0.Get("inProject").MustBool() != false ||
|
|
frame0.Get("lineNumber").MustInt() == 0 {
|
|
t.Errorf("Wrong frame0: %v", frame0)
|
|
}
|
|
|
|
frame3 := exception.Get("stacktrace").GetIndex(3)
|
|
|
|
if frame3.Get("file").MustString() != "bugsnag_test.go" ||
|
|
frame3.Get("method").MustString() != "crashyHandler" ||
|
|
frame3.Get("inProject").MustBool() != true ||
|
|
frame3.Get("lineNumber").MustInt() == 0 {
|
|
t.Errorf("Wrong frame3: %v", frame3)
|
|
}
|
|
}
|
|
|
|
func TestAutoNotify(t *testing.T) {
|
|
|
|
var panicked interface{}
|
|
|
|
func() {
|
|
defer func() {
|
|
panicked = recover()
|
|
}()
|
|
defer AutoNotify(Configuration{Endpoint: testEndpoint, APIKey: testAPIKey})
|
|
|
|
panic("eggs")
|
|
}()
|
|
|
|
if panicked.(string) != "eggs" {
|
|
t.Errorf("didn't re-panic")
|
|
}
|
|
|
|
json, err := simplejson.NewJson(<-postedJSON)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
event := json.Get("events").GetIndex(0)
|
|
|
|
if event.Get("severity").MustString() != "error" {
|
|
t.Errorf("severity should be error")
|
|
}
|
|
exception := event.Get("exceptions").GetIndex(0)
|
|
|
|
if exception.Get("message").MustString() != "eggs" {
|
|
t.Errorf("caught wrong panic")
|
|
}
|
|
}
|
|
|
|
func TestRecover(t *testing.T) {
|
|
var panicked interface{}
|
|
|
|
func() {
|
|
defer func() {
|
|
panicked = recover()
|
|
}()
|
|
defer Recover(Configuration{Endpoint: testEndpoint, APIKey: testAPIKey})
|
|
|
|
panic("ham")
|
|
}()
|
|
|
|
if panicked != nil {
|
|
t.Errorf("re-panick'd")
|
|
}
|
|
|
|
json, err := simplejson.NewJson(<-postedJSON)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
event := json.Get("events").GetIndex(0)
|
|
|
|
if event.Get("severity").MustString() != "warning" {
|
|
t.Errorf("severity should be warning")
|
|
}
|
|
exception := event.Get("exceptions").GetIndex(0)
|
|
|
|
if exception.Get("message").MustString() != "ham" {
|
|
t.Errorf("caught wrong panic")
|
|
}
|
|
}
|
|
|
|
func handleGet(w http.ResponseWriter, r *http.Request) {
|
|
|
|
}
|
|
|
|
var createAccount = handleGet
|
|
|
|
type _job struct {
|
|
Name string
|
|
Process func()
|
|
}
|
|
|
|
func ExampleAutoNotify() interface{} {
|
|
return func(w http.ResponseWriter, request *http.Request) {
|
|
defer AutoNotify(request, Context{"createAccount"})
|
|
|
|
createAccount(w, request)
|
|
}
|
|
}
|
|
|
|
func ExampleRecover(job _job) {
|
|
go func() {
|
|
defer Recover(Context{job.Name}, SeverityWarning)
|
|
|
|
job.Process()
|
|
}()
|
|
}
|
|
|
|
func ExampleConfigure() {
|
|
Configure(Configuration{
|
|
APIKey: "YOUR_API_KEY_HERE",
|
|
|
|
ReleaseStage: "production",
|
|
|
|
// See Configuration{} for other fields
|
|
})
|
|
}
|
|
|
|
func ExampleHandler() {
|
|
// Set up your http handlers as usual
|
|
http.HandleFunc("/", handleGet)
|
|
|
|
// use bugsnag.Handler(nil) to wrap the default http handlers
|
|
// so that Bugsnag is automatically notified about panics.
|
|
http.ListenAndServe(":1234", Handler(nil))
|
|
}
|
|
|
|
func ExampleHandler_customServer() {
|
|
// If you're using a custom server, set the handlers explicitly.
|
|
http.HandleFunc("/", handleGet)
|
|
|
|
srv := http.Server{
|
|
Addr: ":1234",
|
|
ReadTimeout: 10 * time.Second,
|
|
// use bugsnag.Handler(nil) to wrap the default http handlers
|
|
// so that Bugsnag is automatically notified about panics.
|
|
Handler: Handler(nil),
|
|
}
|
|
srv.ListenAndServe()
|
|
}
|
|
|
|
func ExampleHandler_customHandlers() {
|
|
// If you're using custom handlers, wrap the handlers explicitly.
|
|
handler := http.NewServeMux()
|
|
http.HandleFunc("/", handleGet)
|
|
// use bugsnag.Handler(handler) to wrap the handlers so that Bugsnag is
|
|
// automatically notified about panics
|
|
http.ListenAndServe(":1234", Handler(handler))
|
|
}
|
|
|
|
func ExampleNotify() {
|
|
_, err := net.Listen("tcp", ":80")
|
|
|
|
if err != nil {
|
|
Notify(err)
|
|
}
|
|
}
|
|
|
|
func ExampleNotify_details(userID string) {
|
|
_, err := net.Listen("tcp", ":80")
|
|
|
|
if err != nil {
|
|
Notify(err,
|
|
// show as low-severity
|
|
SeverityInfo,
|
|
// set the context
|
|
Context{"createlistener"},
|
|
// pass the user id in to count users affected.
|
|
User{Id: userID},
|
|
// custom meta-data tab
|
|
MetaData{
|
|
"Listen": {
|
|
"Protocol": "tcp",
|
|
"Port": "80",
|
|
},
|
|
},
|
|
)
|
|
}
|
|
|
|
}
|
|
|
|
type Job struct {
|
|
Retry bool
|
|
UserId string
|
|
UserEmail string
|
|
Name string
|
|
Params map[string]string
|
|
}
|
|
|
|
func ExampleOnBeforeNotify() {
|
|
OnBeforeNotify(func(event *Event, config *Configuration) error {
|
|
|
|
// Search all the RawData for any *Job pointers that we're passed in
|
|
// to bugsnag.Notify() and friends.
|
|
for _, datum := range event.RawData {
|
|
if job, ok := datum.(*Job); ok {
|
|
// don't notify bugsnag about errors in retries
|
|
if job.Retry {
|
|
return fmt.Errorf("bugsnag middleware: not notifying about job retry")
|
|
}
|
|
|
|
// add the job as a tab on Bugsnag.com
|
|
event.MetaData.AddStruct("Job", job)
|
|
|
|
// set the user correctly
|
|
event.User = &User{Id: job.UserId, Email: job.UserEmail}
|
|
}
|
|
}
|
|
|
|
// continue notifying as normal
|
|
return nil
|
|
})
|
|
}
|