Move to vendor

Signed-off-by: Olivier Gambier <olivier@docker.com>
This commit is contained in:
Olivier Gambier 2016-03-18 14:07:13 -07:00
parent c8d8e7e357
commit 77e69b9cf3
1268 changed files with 34 additions and 24 deletions

13
vendor/github.com/bugsnag/bugsnag-go/.travis.yml generated vendored Normal file
View file

@ -0,0 +1,13 @@
language: go
go:
- 1.1
- 1.2
- 1.3
- tip
install:
- go get github.com/bugsnag/panicwrap
- go get github.com/bugsnag/osext
- go get github.com/bitly/go-simplejson
- go get github.com/revel/revel

20
vendor/github.com/bugsnag/bugsnag-go/LICENSE.txt generated vendored Normal file
View file

@ -0,0 +1,20 @@
Copyright (c) 2014 Bugsnag
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.

489
vendor/github.com/bugsnag/bugsnag-go/README.md generated vendored Normal file
View file

@ -0,0 +1,489 @@
Bugsnag Notifier for Golang
===========================
The Bugsnag Notifier for Golang gives you instant notification of panics, or
unexpected errors, in your golang app. Any unhandled panics will trigger a
notification to be sent to your Bugsnag project.
[Bugsnag](http://bugsnag.com) captures errors in real-time from your web,
mobile and desktop applications, helping you to understand and resolve them
as fast as possible. [Create a free account](http://bugsnag.com) to start
capturing exceptions from your applications.
## How to Install
1. Download the code
```shell
go get github.com/bugsnag/bugsnag-go
```
### Using with net/http apps
For a golang app based on [net/http](https://godoc.org/net/http), integrating
Bugsnag takes two steps. You should also use these instructions if you're using
the [gorilla toolkit](http://www.gorillatoolkit.org/), or the
[pat](https://github.com/bmizerany/pat/) muxer.
1. Configure bugsnag at the start of your `main()` function:
```go
import "github.com/bugsnag/bugsnag-go"
func main() {
bugsnag.Configure(bugsnag.Configuration{
APIKey: "YOUR_API_KEY_HERE",
ReleaseStage: "production",
// more configuration options
})
// rest of your program.
}
```
2. Wrap your server in a [bugsnag.Handler](https://godoc.org/github.com/bugsnag/bugsnag-go/#Handler)
```go
// a. If you're using the builtin http mux, you can just pass
// bugsnag.Handler(nil) to http.ListenAndServer
http.ListenAndServe(":8080", bugsnag.Handler(nil))
// b. If you're creating a server manually yourself, you can set
// its handlers the same way
srv := http.Server{
Handler: bugsnag.Handler(nil)
}
// c. If you're not using the builtin http mux, wrap your own handler
// (though make sure that it doesn't already catch panics)
http.ListenAndServe(":8080", bugsnag.Handler(handler))
```
### Using with Revel apps
There are two steps to get panic handling in [revel](https://revel.github.io) apps.
1. Add the `bugsnagrevel.Filter` immediately after the `revel.PanicFilter` in `app/init.go`:
```go
import "github.com/bugsnag/bugsnag-go/revel"
revel.Filters = []revel.Filter{
revel.PanicFilter,
bugsnagrevel.Filter,
// ...
}
```
2. Set bugsnag.apikey in the top section of `conf/app.conf`.
```
module.static=github.com/revel/revel/modules/static
bugsnag.apikey=YOUR_API_KEY_HERE
[dev]
```
### Using with Google App Engine
1. Configure bugsnag at the start of your `init()` function:
```go
import "github.com/bugsnag/bugsnag-go"
func init() {
bugsnag.Configure(bugsnag.Configuration{
APIKey: "YOUR_API_KEY_HERE",
})
// ...
}
```
2. Wrap *every* http.Handler or http.HandlerFunc with Bugsnag:
```go
// a. If you're using HandlerFuncs
http.HandleFunc("/", bugsnag.HandlerFunc(
func (w http.ResponseWriter, r *http.Request) {
// ...
}))
// b. If you're using Handlers
http.Handle("/", bugsnag.Handler(myHttpHandler))
```
3. In order to use Bugsnag, you must provide the current
[`appengine.Context`](https://developers.google.com/appengine/docs/go/reference#Context), or
current `*http.Request` as rawData. The easiest way to do this is to create a new notifier.
```go
c := appengine.NewContext(r)
notifier := bugsnag.New(c)
if err != nil {
notifier.Notify(err)
}
go func () {
defer notifier.Recover()
// ...
}()
```
## Notifying Bugsnag manually
Bugsnag will automatically handle any panics that crash your program and notify
you of them. If you've integrated with `revel` or `net/http`, then you'll also
be notified of any panics() that happen while processing a request.
Sometimes however it's useful to manually notify Bugsnag of a problem. To do this,
call [`bugsnag.Notify()`](https://godoc.org/github.com/bugsnag/bugsnag-go/#Notify)
```go
if err != nil {
bugsnag.Notify(err)
}
```
### Manual panic handling
To avoid a panic in a goroutine from crashing your entire app, you can use
[`bugsnag.Recover()`](https://godoc.org/github.com/bugsnag/bugsnag-go/#Recover)
to stop a panic from unwinding the stack any further. When `Recover()` is hit,
it will send any current panic to Bugsnag and then stop panicking. This is
most useful at the start of a goroutine:
```go
go func() {
defer bugsnag.Recover()
// ...
}()
```
Alternatively you can use
[`bugsnag.AutoNotify()`](https://godoc.org/github.com/bugsnag/bugsnag-go/#Recover)
to notify bugsnag of a panic while letting the program continue to panic. This
is useful if you're using a Framework that already has some handling of panics
and you are retrofitting bugsnag support.
```go
defer bugsnag.AutoNotify()
```
## Sending Custom Data
Most functions in the Bugsnag API, including `bugsnag.Notify()`,
`bugsnag.Recover()`, `bugsnag.AutoNotify()`, and `bugsnag.Handler()` let you
attach data to the notifications that they send. To do this you pass in rawData,
which can be any of the supported types listed here. To add support for more
types of rawData see [OnBeforeNotify](#custom-data-with-onbeforenotify).
### Custom MetaData
Custom metaData appears as tabs on Bugsnag.com. You can set it by passing
a [`bugsnag.MetaData`](https://godoc.org/github.com/bugsnag/bugsnag-go/#MetaData)
object as rawData.
```go
bugsnag.Notify(err,
bugsnag.MetaData{
"Account": {
"Name": Account.Name,
"Paying": Account.Plan.Premium,
},
})
```
### Request data
Bugsnag can extract interesting data from
[`*http.Request`](https://godoc.org/net/http/#Request) objects, and
[`*revel.Controller`](https://godoc.org/github.com/revel/revel/#Controller)
objects. These are automatically passed in when handling panics, and you can
pass them yourself.
```go
func (w http.ResponseWriter, r *http.Request) {
bugsnag.Notify(err, r)
}
```
### User data
User data is searchable, and the `Id` powers the count of users affected. You
can set which user an error affects by passing a
[`bugsnag.User`](https://godoc.org/github.com/bugsnag/bugsnag-go/#User) object as
rawData.
```go
bugsnag.Notify(err,
bugsnag.User{Id: "1234", Name: "Conrad", Email: "me@cirw.in"})
```
### Context
The context shows up prominently in the list view so that you can get an idea
of where a problem occurred. You can set it by passing a
[`bugsnag.Context`](https://godoc.org/github.com/bugsnag/bugsnag-go/#Context)
object as rawData.
```go
bugsnag.Notify(err, bugsnag.Context{"backgroundJob"})
```
### Severity
Bugsnag supports three severities, `SeverityError`, `SeverityWarning`, and `SeverityInfo`.
You can set the severity of an error by passing one of these objects as rawData.
```go
bugsnag.Notify(err, bugsnag.SeverityInfo)
```
## Configuration
You must call `bugsnag.Configure()` at the start of your program to use Bugsnag, you pass it
a [`bugsnag.Configuration`](https://godoc.org/github.com/bugsnag/bugsnag-go/#Configuration) object
containing any of the following values.
### APIKey
The Bugsnag API key can be found on your [Bugsnag dashboard](https://bugsnag.com) under "Settings".
```go
bugsnag.Configure(bugsnag.Configuration{
APIKey: "YOUR_API_KEY_HERE",
})
```
### Endpoint
The Bugsnag endpoint defaults to `https://notify.bugsnag.com/`. If you're using Bugsnag enterprise,
you should set this to the endpoint of your local instance.
```go
bugsnag.Configure(bugsnag.Configuration{
Endpoint: "http://bugsnag.internal:49000/",
})
```
### ReleaseStage
The ReleaseStage tracks where your app is deployed. You should set this to `production`, `staging`,
`development` or similar as appropriate.
```go
bugsnag.Configure(bugsnag.Configuration{
ReleaseStage: "development",
})
```
### NotifyReleaseStages
The list of ReleaseStages to notify in. By default Bugsnag will notify you in all release stages, but
you can use this to silence development errors.
```go
bugsnag.Configure(bugsnag.Configuration{
NotifyReleaseStages: []string{"production", "staging"},
})
```
### AppVersion
If you use a versioning scheme for deploys of your app, Bugsnag can use the `AppVersion` to only
re-open errors if they occur in later version of the app.
```go
bugsnag.Configure(bugsnag.Configuration{
AppVersion: "1.2.3",
})
```
### Hostname
The hostname is used to track where exceptions are coming from in the Bugsnag dashboard. The
default value is obtained from `os.Hostname()` so you won't often need to change this.
```go
bugsnag.Configure(bugsnag.Configuration{
Hostname: "go1",
})
```
### ProjectPackages
In order to determine where a crash happens Bugsnag needs to know which packages you consider to
be part of your app (as opposed to a library). By default this is set to `[]string{"main*"}`. Strings
are matched to package names using [`filepath.Match`](http://godoc.org/path/filepath#Match).
```go
bugsnag.Configure(bugsnag.Configuration{
ProjectPackages: []string{"main", "github.com/domain/myapp/*"},
}
```
### ParamsFilters
Sometimes sensitive data is accidentally included in Bugsnag MetaData. You can remove it by
setting `ParamsFilters`. Any key in the `MetaData` that includes any string in the filters
will be redacted. The default is `[]string{"password", "secret"}`, which prevents fields like
`password`, `password_confirmation` and `secret_answer` from being sent.
```go
bugsnag.Configure(bugsnag.Configuration{
ParamsFilters: []string{"password", "secret"},
}
```
### Logger
The Logger to write to in case of an error inside Bugsnag. This defaults to the global logger.
```go
bugsnag.Configure(bugsnag.Configuration{
Logger: app.Logger,
}
```
### PanicHandler
The first time Bugsnag is configured, it wraps the running program in a panic
handler using [panicwrap](http://godoc.org/github.com/ConradIrwin/panicwrap). This
forks a sub-process which monitors unhandled panics. To prevent this, set
`PanicHandler` to `func() {}` the first time you call
`bugsnag.Configure`. This will prevent bugsnag from being able to notify you about
unhandled panics.
```go
bugsnag.Configure(bugsnag.Configuration{
PanicHandler: func() {},
})
```
### Synchronous
Bugsnag usually starts a new goroutine before sending notifications. This means
that notifications can be lost if you do a bugsnag.Notify and then immediately
os.Exit. To avoid this problem, set Bugsnag to Synchronous (or just `panic()`
instead ;).
```go
bugsnag.Configure(bugsnag.Configuration{
Synchronous: true
})
```
Or just for one error:
```go
bugsnag.Notify(err, bugsnag.Configuration{Synchronous: true})
```
### Transport
The transport configures how Bugsnag makes http requests. By default we use
[`http.DefaultTransport`](http://godoc.org/net/http#RoundTripper) which handles
HTTP proxies automatically using the `$HTTP_PROXY` environment variable.
```go
bugsnag.Configure(bugsnag.Configuration{
Transport: http.DefaultTransport,
})
```
## Custom data with OnBeforeNotify
While it's nice that you can pass `MetaData` directly into `bugsnag.Notify`,
`bugsnag.AutoNotify`, and `bugsnag.Recover`, this can be a bit cumbersome and
inefficient — you're constructing the meta-data whether or not it will actually
be used. A better idea is to pass raw data in to these functions, and add an
`OnBeforeNotify` filter that converts them into `MetaData`.
For example, lets say our system processes jobs:
```go
type Job struct{
Retry bool
UserId string
UserEmail string
Name string
Params map[string]string
}
```
You can pass a job directly into Bugsnag.notify:
```go
bugsnag.Notify(err, job)
```
And then add a filter to extract information from that job and attach it to the
Bugsnag event:
```go
bugsnag.OnBeforeNotify(
func(event *bugsnag.Event, config *bugsnag.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("not notifying about retried jobs")
}
// 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
})
```
## Advanced Usage
If you want to have multiple different configurations around in one program,
you can use `bugsnag.New()` to create multiple independent instances of
Bugsnag. You can use these without calling `bugsnag.Configure()`, but bear in
mind that until you call `bugsnag.Configure()` unhandled panics will not be
sent to bugsnag.
```go
notifier := bugsnag.New(bugsnag.Configuration{
APIKey: "YOUR_OTHER_API_KEY",
})
```
In fact any place that lets you pass in `rawData` also allows you to pass in
configuration. For example to send http errors to one bugsnag project, you
could do:
```go
bugsnag.Handler(nil, bugsnag.Configuration{APIKey: "YOUR_OTHER_API_KEY"})
```
### GroupingHash
If you need to override Bugsnag's grouping algorithm, you can set the
`GroupingHash` in an `OnBeforeNotify`:
```go
bugsnag.OnBeforeNotify(
func (event *bugsnag.Event, config *bugsnag.Configuration) error {
event.GroupingHash = calculateGroupingHash(event)
return nil
})
```

76
vendor/github.com/bugsnag/bugsnag-go/appengine.go generated vendored Normal file
View file

@ -0,0 +1,76 @@
// +build appengine
package bugsnag
import (
"appengine"
"appengine/urlfetch"
"appengine/user"
"fmt"
"log"
"net/http"
)
func defaultPanicHandler() {}
func init() {
OnBeforeNotify(appengineMiddleware)
}
func appengineMiddleware(event *Event, config *Configuration) (err error) {
var c appengine.Context
for _, datum := range event.RawData {
if r, ok := datum.(*http.Request); ok {
c = appengine.NewContext(r)
break
} else if context, ok := datum.(appengine.Context); ok {
c = context
break
}
}
if c == nil {
return fmt.Errorf("No appengine context given")
}
// You can only use the builtin http library if you pay for appengine,
// so we use the appengine urlfetch service instead.
config.Transport = &urlfetch.Transport{
Context: c,
}
// Anything written to stderr/stdout is discarded, so lets log to the request.
config.Logger = log.New(appengineWriter{c}, config.Logger.Prefix(), config.Logger.Flags())
// Set the releaseStage appropriately
if config.ReleaseStage == "" {
if appengine.IsDevAppServer() {
config.ReleaseStage = "development"
} else {
config.ReleaseStage = "production"
}
}
if event.User == nil {
u := user.Current(c)
if u != nil {
event.User = &User{
Id: u.ID,
Email: u.Email,
}
}
}
return nil
}
// Convert an appengine.Context into an io.Writer so we can create a log.Logger.
type appengineWriter struct {
appengine.Context
}
func (c appengineWriter) Write(b []byte) (int, error) {
c.Warningf(string(b))
return len(b), nil
}

131
vendor/github.com/bugsnag/bugsnag-go/bugsnag.go generated vendored Normal file
View file

@ -0,0 +1,131 @@
package bugsnag
import (
"github.com/bugsnag/bugsnag-go/errors"
"log"
"net/http"
"os"
"sync"
// Fixes a bug with SHA-384 intermediate certs on some platforms.
// - https://github.com/bugsnag/bugsnag-go/issues/9
_ "crypto/sha512"
)
// The current version of bugsnag-go.
const VERSION = "1.0.2"
var once sync.Once
var middleware middlewareStack
// The configuration for the default bugsnag notifier.
var Config Configuration
var defaultNotifier = Notifier{&Config, nil}
// Configure Bugsnag. The only required setting is the APIKey, which can be
// obtained by clicking on "Settings" in your Bugsnag dashboard. This function
// is also responsible for installing the global panic handler, so it should be
// called as early as possible in your initialization process.
func Configure(config Configuration) {
Config.update(&config)
once.Do(Config.PanicHandler)
}
// Notify sends an error to Bugsnag along with the current stack trace. The
// rawData is used to send extra information along with the error. For example
// you can pass the current http.Request to Bugsnag to see information about it
// in the dashboard, or set the severity of the notification.
func Notify(err error, rawData ...interface{}) error {
return defaultNotifier.Notify(errors.New(err, 1), rawData...)
}
// AutoNotify logs a panic on a goroutine and then repanics.
// It should only be used in places that have existing panic handlers further
// up the stack. See bugsnag.Recover(). The rawData is used to send extra
// information along with any panics that are handled this way.
// Usage: defer bugsnag.AutoNotify()
func AutoNotify(rawData ...interface{}) {
if err := recover(); err != nil {
rawData = defaultNotifier.addDefaultSeverity(rawData, SeverityError)
defaultNotifier.Notify(errors.New(err, 2), rawData...)
panic(err)
}
}
// Recover logs a panic on a goroutine and then recovers.
// The rawData is used to send extra information along with
// any panics that are handled this way
// Usage: defer bugsnag.Recover()
func Recover(rawData ...interface{}) {
if err := recover(); err != nil {
rawData = defaultNotifier.addDefaultSeverity(rawData, SeverityWarning)
defaultNotifier.Notify(errors.New(err, 2), rawData...)
}
}
// OnBeforeNotify adds a callback to be run before a notification is sent to
// Bugsnag. It can be used to modify the event or its MetaData. Changes made
// to the configuration are local to notifying about this event. To prevent the
// event from being sent to Bugsnag return an error, this error will be
// returned from bugsnag.Notify() and the event will not be sent.
func OnBeforeNotify(callback func(event *Event, config *Configuration) error) {
middleware.OnBeforeNotify(callback)
}
// Handler creates an http Handler that notifies Bugsnag any panics that
// happen. It then repanics so that the default http Server panic handler can
// handle the panic too. The rawData is used to send extra information along
// with any panics that are handled this way.
func Handler(h http.Handler, rawData ...interface{}) http.Handler {
notifier := New(rawData...)
if h == nil {
h = http.DefaultServeMux
}
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
defer notifier.AutoNotify(r)
h.ServeHTTP(w, r)
})
}
// HandlerFunc creates an http HandlerFunc that notifies Bugsnag about any
// panics that happen. It then repanics so that the default http Server panic
// handler can handle the panic too. The rawData is used to send extra
// information along with any panics that are handled this way. If you have
// already wrapped your http server using bugsnag.Handler() you don't also need
// to wrap each HandlerFunc.
func HandlerFunc(h http.HandlerFunc, rawData ...interface{}) http.HandlerFunc {
notifier := New(rawData...)
return func(w http.ResponseWriter, r *http.Request) {
defer notifier.AutoNotify(r)
h(w, r)
}
}
func init() {
// Set up builtin middlewarez
OnBeforeNotify(httpRequestMiddleware)
// Default configuration
Config.update(&Configuration{
APIKey: "",
Endpoint: "https://notify.bugsnag.com/",
Hostname: "",
AppVersion: "",
ReleaseStage: "",
ParamsFilters: []string{"password", "secret"},
// * for app-engine
ProjectPackages: []string{"main*"},
NotifyReleaseStages: nil,
Logger: log.New(os.Stdout, log.Prefix(), log.Flags()),
PanicHandler: defaultPanicHandler,
Transport: http.DefaultTransport,
})
hostname, err := os.Hostname()
if err == nil {
Config.Hostname = hostname
}
}

461
vendor/github.com/bugsnag/bugsnag-go/bugsnag_test.go generated vendored Normal file
View file

@ -0,0 +1,461 @@
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
})
}

159
vendor/github.com/bugsnag/bugsnag-go/configuration.go generated vendored Normal file
View file

@ -0,0 +1,159 @@
package bugsnag
import (
"log"
"net/http"
"path/filepath"
"strings"
)
// Configuration sets up and customizes communication with the Bugsnag API.
type Configuration struct {
// Your Bugsnag API key, e.g. "c9d60ae4c7e70c4b6c4ebd3e8056d2b8". You can
// find this by clicking Settings on https://bugsnag.com/.
APIKey string
// The Endpoint to notify about crashes. This defaults to
// "https://notify.bugsnag.com/", if you're using Bugsnag Enterprise then
// set it to your internal Bugsnag endpoint.
Endpoint string
// The current release stage. This defaults to "production" and is used to
// filter errors in the Bugsnag dashboard.
ReleaseStage string
// The currently running version of the app. This is used to filter errors
// in the Bugsnag dasboard. If you set this then Bugsnag will only re-open
// resolved errors if they happen in different app versions.
AppVersion string
// The hostname of the current server. This defaults to the return value of
// os.Hostname() and is graphed in the Bugsnag dashboard.
Hostname string
// The Release stages to notify in. If you set this then bugsnag-go will
// only send notifications to Bugsnag if the ReleaseStage is listed here.
NotifyReleaseStages []string
// packages that are part of your app. Bugsnag uses this to determine how
// to group errors and how to display them on your dashboard. You should
// include any packages that are part of your app, and exclude libraries
// and helpers. You can list wildcards here, and they'll be expanded using
// filepath.Glob. The default value is []string{"main*"}
ProjectPackages []string
// Any meta-data that matches these filters will be marked as [REDACTED]
// before sending a Notification to Bugsnag. It defaults to
// []string{"password", "secret"} so that request parameters like password,
// password_confirmation and auth_secret will not be sent to Bugsnag.
ParamsFilters []string
// The PanicHandler is used by Bugsnag to catch unhandled panics in your
// application. The default panicHandler uses mitchellh's panicwrap library,
// and you can disable this feature by passing an empty: func() {}
PanicHandler func()
// The logger that Bugsnag should log to. Uses the same defaults as go's
// builtin logging package. bugsnag-go logs whenever it notifies Bugsnag
// of an error, and when any error occurs inside the library itself.
Logger *log.Logger
// The http Transport to use, defaults to the default http Transport. This
// can be configured if you are in an environment like Google App Engine
// that has stringent conditions on making http requests.
Transport http.RoundTripper
// Whether bugsnag should notify synchronously. This defaults to false which
// causes bugsnag-go to spawn a new goroutine for each notification.
Synchronous bool
// TODO: remember to update the update() function when modifying this struct
}
func (config *Configuration) update(other *Configuration) *Configuration {
if other.APIKey != "" {
config.APIKey = other.APIKey
}
if other.Endpoint != "" {
config.Endpoint = other.Endpoint
}
if other.Hostname != "" {
config.Hostname = other.Hostname
}
if other.AppVersion != "" {
config.AppVersion = other.AppVersion
}
if other.ReleaseStage != "" {
config.ReleaseStage = other.ReleaseStage
}
if other.ParamsFilters != nil {
config.ParamsFilters = other.ParamsFilters
}
if other.ProjectPackages != nil {
config.ProjectPackages = other.ProjectPackages
}
if other.Logger != nil {
config.Logger = other.Logger
}
if other.NotifyReleaseStages != nil {
config.NotifyReleaseStages = other.NotifyReleaseStages
}
if other.PanicHandler != nil {
config.PanicHandler = other.PanicHandler
}
if other.Transport != nil {
config.Transport = other.Transport
}
if other.Synchronous {
config.Synchronous = true
}
return config
}
func (config *Configuration) merge(other *Configuration) *Configuration {
return config.clone().update(other)
}
func (config *Configuration) clone() *Configuration {
clone := *config
return &clone
}
func (config *Configuration) isProjectPackage(pkg string) bool {
for _, p := range config.ProjectPackages {
if match, _ := filepath.Match(p, pkg); match {
return true
}
}
return false
}
func (config *Configuration) stripProjectPackages(file string) string {
for _, p := range config.ProjectPackages {
if len(p) > 2 && p[len(p)-2] == '/' && p[len(p)-1] == '*' {
p = p[:len(p)-1]
} else {
p = p + "/"
}
if strings.HasPrefix(file, p) {
return strings.TrimPrefix(file, p)
}
}
return file
}
func (config *Configuration) log(fmt string, args ...interface{}) {
if config != nil && config.Logger != nil {
config.Logger.Printf(fmt, args...)
} else {
log.Printf(fmt, args...)
}
}
func (config *Configuration) notifyInReleaseStage() bool {
if config.NotifyReleaseStages == nil {
return true
}
for _, r := range config.NotifyReleaseStages {
if r == config.ReleaseStage {
return true
}
}
return false
}

View file

@ -0,0 +1,58 @@
package bugsnag
import (
"testing"
)
func TestNotifyReleaseStages(t *testing.T) {
var testCases = []struct {
stage string
configured []string
notify bool
msg string
}{
{
stage: "production",
notify: true,
msg: "Should notify in all release stages by default",
},
{
stage: "production",
configured: []string{"development", "production"},
notify: true,
msg: "Failed to notify in configured release stage",
},
{
stage: "staging",
configured: []string{"development", "production"},
notify: false,
msg: "Failed to prevent notification in excluded release stage",
},
}
for _, testCase := range testCases {
Configure(Configuration{ReleaseStage: testCase.stage, NotifyReleaseStages: testCase.configured})
if Config.notifyInReleaseStage() != testCase.notify {
t.Error(testCase.msg)
}
}
}
func TestProjectPackages(t *testing.T) {
Configure(Configuration{ProjectPackages: []string{"main", "github.com/ConradIrwin/*"}})
if !Config.isProjectPackage("main") {
t.Error("literal project package doesn't work")
}
if !Config.isProjectPackage("github.com/ConradIrwin/foo") {
t.Error("wildcard project package doesn't work")
}
if Config.isProjectPackage("runtime") {
t.Error("wrong packges being marked in project")
}
if Config.isProjectPackage("github.com/ConradIrwin/foo/bar") {
t.Error("wrong packges being marked in project")
}
}

69
vendor/github.com/bugsnag/bugsnag-go/doc.go generated vendored Normal file
View file

@ -0,0 +1,69 @@
/*
Package bugsnag captures errors in real-time and reports them to Bugsnag (http://bugsnag.com).
Using bugsnag-go is a three-step process.
1. As early as possible in your program configure the notifier with your APIKey. This sets up
handling of panics that would otherwise crash your app.
func init() {
bugsnag.Configure(bugsnag.Configuration{
APIKey: "YOUR_API_KEY_HERE",
})
}
2. Add bugsnag to places that already catch panics. For example you should add it to the HTTP server
when you call ListenAndServer:
http.ListenAndServe(":8080", bugsnag.Handler(nil))
If that's not possible, for example because you're using Google App Engine, you can also wrap each
HTTP handler manually:
http.HandleFunc("/" bugsnag.HandlerFunc(func (w http.ResponseWriter, r *http.Request) {
...
})
3. To notify Bugsnag of an error that is not a panic, pass it to bugsnag.Notify. This will also
log the error message using the configured Logger.
if err != nil {
bugsnag.Notify(err)
}
For detailed integration instructions see https://bugsnag.com/docs/notifiers/go.
Configuration
The only required configuration is the Bugsnag API key which can be obtained by clicking "Settings"
on the top of https://bugsnag.com/ after signing up. We also recommend you set the ReleaseStage
and AppVersion if these make sense for your deployment workflow.
RawData
If you need to attach extra data to Bugsnag notifications you can do that using
the rawData mechanism. Most of the functions that send errors to Bugsnag allow
you to pass in any number of interface{} values as rawData. The rawData can
consist of the Severity, Context, User or MetaData types listed below, and
there is also builtin support for *http.Requests.
bugsnag.Notify(err, bugsnag.SeverityError)
If you want to add custom tabs to your bugsnag dashboard you can pass any value in as rawData,
and then process it into the event's metadata using a bugsnag.OnBeforeNotify() hook.
bugsnag.Notify(err, account)
bugsnag.OnBeforeNotify(func (e *bugsnag.Event, c *bugsnag.Configuration) {
for datum := range e.RawData {
if account, ok := datum.(Account); ok {
e.MetaData.Add("account", "name", account.Name)
e.MetaData.Add("account", "url", account.URL)
}
}
})
If necessary you can pass Configuration in as rawData, or modify the Configuration object passed
into OnBeforeNotify hooks. Configuration passed in this way only affects the current notification.
*/
package bugsnag

View file

@ -0,0 +1,6 @@
Adds stacktraces to errors in golang.
This was made to help build the Bugsnag notifier but can be used standalone if
you like to have stacktraces on errors.
See [Godoc](https://godoc.org/github.com/bugsnag/bugsnag-go/errors) for the API docs.

90
vendor/github.com/bugsnag/bugsnag-go/errors/error.go generated vendored Normal file
View file

@ -0,0 +1,90 @@
// Package errors provides errors that have stack-traces.
package errors
import (
"bytes"
"fmt"
"reflect"
"runtime"
)
// The maximum number of stackframes on any error.
var MaxStackDepth = 50
// Error is an error with an attached stacktrace. It can be used
// wherever the builtin error interface is expected.
type Error struct {
Err error
stack []uintptr
frames []StackFrame
}
// New makes an Error from the given value. If that value is already an
// error then it will be used directly, if not, it will be passed to
// fmt.Errorf("%v"). The skip parameter indicates how far up the stack
// to start the stacktrace. 0 is from the current call, 1 from its caller, etc.
func New(e interface{}, skip int) *Error {
var err error
switch e := e.(type) {
case *Error:
return e
case error:
err = e
default:
err = fmt.Errorf("%v", e)
}
stack := make([]uintptr, MaxStackDepth)
length := runtime.Callers(2+skip, stack[:])
return &Error{
Err: err,
stack: stack[:length],
}
}
// Errorf creates a new error with the given message. You can use it
// as a drop-in replacement for fmt.Errorf() to provide descriptive
// errors in return values.
func Errorf(format string, a ...interface{}) *Error {
return New(fmt.Errorf(format, a...), 1)
}
// Error returns the underlying error's message.
func (err *Error) Error() string {
return err.Err.Error()
}
// Stack returns the callstack formatted the same way that go does
// in runtime/debug.Stack()
func (err *Error) Stack() []byte {
buf := bytes.Buffer{}
for _, frame := range err.StackFrames() {
buf.WriteString(frame.String())
}
return buf.Bytes()
}
// StackFrames returns an array of frames containing information about the
// stack.
func (err *Error) StackFrames() []StackFrame {
if err.frames == nil {
err.frames = make([]StackFrame, len(err.stack))
for i, pc := range err.stack {
err.frames[i] = NewStackFrame(pc)
}
}
return err.frames
}
// TypeName returns the type this error. e.g. *errors.stringError.
func (err *Error) TypeName() string {
if _, ok := err.Err.(uncaughtPanic); ok {
return "panic"
}
return reflect.TypeOf(err.Err).String()
}

View file

@ -0,0 +1,117 @@
package errors
import (
"bytes"
"fmt"
"io"
"runtime/debug"
"testing"
)
func TestStackFormatMatches(t *testing.T) {
defer func() {
err := recover()
if err != 'a' {
t.Fatal(err)
}
bs := [][]byte{Errorf("hi").Stack(), debug.Stack()}
// Ignore the first line (as it contains the PC of the .Stack() call)
bs[0] = bytes.SplitN(bs[0], []byte("\n"), 2)[1]
bs[1] = bytes.SplitN(bs[1], []byte("\n"), 2)[1]
if bytes.Compare(bs[0], bs[1]) != 0 {
t.Errorf("Stack didn't match")
t.Errorf("%s", bs[0])
t.Errorf("%s", bs[1])
}
}()
a()
}
func TestSkipWorks(t *testing.T) {
defer func() {
err := recover()
if err != 'a' {
t.Fatal(err)
}
bs := [][]byte{New("hi", 2).Stack(), debug.Stack()}
// should skip four lines of debug.Stack()
bs[1] = bytes.SplitN(bs[1], []byte("\n"), 5)[4]
if bytes.Compare(bs[0], bs[1]) != 0 {
t.Errorf("Stack didn't match")
t.Errorf("%s", bs[0])
t.Errorf("%s", bs[1])
}
}()
a()
}
func TestNewError(t *testing.T) {
e := func() error {
return New("hi", 1)
}()
if e.Error() != "hi" {
t.Errorf("Constructor with a string failed")
}
if New(fmt.Errorf("yo"), 0).Error() != "yo" {
t.Errorf("Constructor with an error failed")
}
if New(e, 0) != e {
t.Errorf("Constructor with an Error failed")
}
if New(nil, 0).Error() != "<nil>" {
t.Errorf("Constructor with nil failed")
}
}
func ExampleErrorf(x int) (int, error) {
if x%2 == 1 {
return 0, Errorf("can only halve even numbers, got %d", x)
}
return x / 2, nil
}
func ExampleNewError() (error, error) {
// Wrap io.EOF with the current stack-trace and return it
return nil, New(io.EOF, 0)
}
func ExampleNewError_skip() {
defer func() {
if err := recover(); err != nil {
// skip 1 frame (the deferred function) and then return the wrapped err
err = New(err, 1)
}
}()
}
func ExampleError_Stack(err Error) {
fmt.Printf("Error: %s\n%s", err.Error(), err.Stack())
}
func a() error {
b(5)
return nil
}
func b(i int) {
c()
}
func c() {
panic('a')
}

View file

@ -0,0 +1,127 @@
package errors
import (
"strconv"
"strings"
)
type uncaughtPanic struct{ message string }
func (p uncaughtPanic) Error() string {
return p.message
}
// ParsePanic allows you to get an error object from the output of a go program
// that panicked. This is particularly useful with https://github.com/mitchellh/panicwrap.
func ParsePanic(text string) (*Error, error) {
lines := strings.Split(text, "\n")
state := "start"
var message string
var stack []StackFrame
for i := 0; i < len(lines); i++ {
line := lines[i]
if state == "start" {
if strings.HasPrefix(line, "panic: ") {
message = strings.TrimPrefix(line, "panic: ")
state = "seek"
} else {
return nil, Errorf("bugsnag.panicParser: Invalid line (no prefix): %s", line)
}
} else if state == "seek" {
if strings.HasPrefix(line, "goroutine ") && strings.HasSuffix(line, "[running]:") {
state = "parsing"
}
} else if state == "parsing" {
if line == "" {
state = "done"
break
}
createdBy := false
if strings.HasPrefix(line, "created by ") {
line = strings.TrimPrefix(line, "created by ")
createdBy = true
}
i++
if i >= len(lines) {
return nil, Errorf("bugsnag.panicParser: Invalid line (unpaired): %s", line)
}
frame, err := parsePanicFrame(line, lines[i], createdBy)
if err != nil {
return nil, err
}
stack = append(stack, *frame)
if createdBy {
state = "done"
break
}
}
}
if state == "done" || state == "parsing" {
return &Error{Err: uncaughtPanic{message}, frames: stack}, nil
}
return nil, Errorf("could not parse panic: %v", text)
}
// The lines we're passing look like this:
//
// main.(*foo).destruct(0xc208067e98)
// /0/go/src/github.com/bugsnag/bugsnag-go/pan/main.go:22 +0x151
func parsePanicFrame(name string, line string, createdBy bool) (*StackFrame, error) {
idx := strings.LastIndex(name, "(")
if idx == -1 && !createdBy {
return nil, Errorf("bugsnag.panicParser: Invalid line (no call): %s", name)
}
if idx != -1 {
name = name[:idx]
}
pkg := ""
if lastslash := strings.LastIndex(name, "/"); lastslash >= 0 {
pkg += name[:lastslash] + "/"
name = name[lastslash+1:]
}
if period := strings.Index(name, "."); period >= 0 {
pkg += name[:period]
name = name[period+1:]
}
name = strings.Replace(name, "·", ".", -1)
if !strings.HasPrefix(line, "\t") {
return nil, Errorf("bugsnag.panicParser: Invalid line (no tab): %s", line)
}
idx = strings.LastIndex(line, ":")
if idx == -1 {
return nil, Errorf("bugsnag.panicParser: Invalid line (no line number): %s", line)
}
file := line[1:idx]
number := line[idx+1:]
if idx = strings.Index(number, " +"); idx > -1 {
number = number[:idx]
}
lno, err := strconv.ParseInt(number, 10, 32)
if err != nil {
return nil, Errorf("bugsnag.panicParser: Invalid line (bad line number): %s", line)
}
return &StackFrame{
File: file,
LineNumber: int(lno),
Package: pkg,
Name: name,
}, nil
}

View file

@ -0,0 +1,142 @@
package errors
import (
"reflect"
"testing"
)
var createdBy = `panic: hello!
goroutine 54 [running]:
runtime.panic(0x35ce40, 0xc208039db0)
/0/c/go/src/pkg/runtime/panic.c:279 +0xf5
github.com/loopj/bugsnag-example-apps/go/revelapp/app/controllers.func·001()
/0/go/src/github.com/loopj/bugsnag-example-apps/go/revelapp/app/controllers/app.go:13 +0x74
net/http.(*Server).Serve(0xc20806c780, 0x910c88, 0xc20803e168, 0x0, 0x0)
/0/c/go/src/pkg/net/http/server.go:1698 +0x91
created by github.com/loopj/bugsnag-example-apps/go/revelapp/app/controllers.App.Index
/0/go/src/github.com/loopj/bugsnag-example-apps/go/revelapp/app/controllers/app.go:14 +0x3e
goroutine 16 [IO wait]:
net.runtime_pollWait(0x911c30, 0x72, 0x0)
/0/c/go/src/pkg/runtime/netpoll.goc:146 +0x66
net.(*pollDesc).Wait(0xc2080ba990, 0x72, 0x0, 0x0)
/0/c/go/src/pkg/net/fd_poll_runtime.go:84 +0x46
net.(*pollDesc).WaitRead(0xc2080ba990, 0x0, 0x0)
/0/c/go/src/pkg/net/fd_poll_runtime.go:89 +0x42
net.(*netFD).accept(0xc2080ba930, 0x58be30, 0x0, 0x9103f0, 0x23)
/0/c/go/src/pkg/net/fd_unix.go:409 +0x343
net.(*TCPListener).AcceptTCP(0xc20803e168, 0x8, 0x0, 0x0)
/0/c/go/src/pkg/net/tcpsock_posix.go:234 +0x5d
net.(*TCPListener).Accept(0xc20803e168, 0x0, 0x0, 0x0, 0x0)
/0/c/go/src/pkg/net/tcpsock_posix.go:244 +0x4b
github.com/revel/revel.Run(0xe6d9)
/0/go/src/github.com/revel/revel/server.go:113 +0x926
main.main()
/0/go/src/github.com/loopj/bugsnag-example-apps/go/revelapp/app/tmp/main.go:109 +0xe1a
`
var normalSplit = `panic: hello!
goroutine 54 [running]:
runtime.panic(0x35ce40, 0xc208039db0)
/0/c/go/src/pkg/runtime/panic.c:279 +0xf5
github.com/loopj/bugsnag-example-apps/go/revelapp/app/controllers.func·001()
/0/go/src/github.com/loopj/bugsnag-example-apps/go/revelapp/app/controllers/app.go:13 +0x74
net/http.(*Server).Serve(0xc20806c780, 0x910c88, 0xc20803e168, 0x0, 0x0)
/0/c/go/src/pkg/net/http/server.go:1698 +0x91
goroutine 16 [IO wait]:
net.runtime_pollWait(0x911c30, 0x72, 0x0)
/0/c/go/src/pkg/runtime/netpoll.goc:146 +0x66
net.(*pollDesc).Wait(0xc2080ba990, 0x72, 0x0, 0x0)
/0/c/go/src/pkg/net/fd_poll_runtime.go:84 +0x46
net.(*pollDesc).WaitRead(0xc2080ba990, 0x0, 0x0)
/0/c/go/src/pkg/net/fd_poll_runtime.go:89 +0x42
net.(*netFD).accept(0xc2080ba930, 0x58be30, 0x0, 0x9103f0, 0x23)
/0/c/go/src/pkg/net/fd_unix.go:409 +0x343
net.(*TCPListener).AcceptTCP(0xc20803e168, 0x8, 0x0, 0x0)
/0/c/go/src/pkg/net/tcpsock_posix.go:234 +0x5d
net.(*TCPListener).Accept(0xc20803e168, 0x0, 0x0, 0x0, 0x0)
/0/c/go/src/pkg/net/tcpsock_posix.go:244 +0x4b
github.com/revel/revel.Run(0xe6d9)
/0/go/src/github.com/revel/revel/server.go:113 +0x926
main.main()
/0/go/src/github.com/loopj/bugsnag-example-apps/go/revelapp/app/tmp/main.go:109 +0xe1a
`
var lastGoroutine = `panic: hello!
goroutine 16 [IO wait]:
net.runtime_pollWait(0x911c30, 0x72, 0x0)
/0/c/go/src/pkg/runtime/netpoll.goc:146 +0x66
net.(*pollDesc).Wait(0xc2080ba990, 0x72, 0x0, 0x0)
/0/c/go/src/pkg/net/fd_poll_runtime.go:84 +0x46
net.(*pollDesc).WaitRead(0xc2080ba990, 0x0, 0x0)
/0/c/go/src/pkg/net/fd_poll_runtime.go:89 +0x42
net.(*netFD).accept(0xc2080ba930, 0x58be30, 0x0, 0x9103f0, 0x23)
/0/c/go/src/pkg/net/fd_unix.go:409 +0x343
net.(*TCPListener).AcceptTCP(0xc20803e168, 0x8, 0x0, 0x0)
/0/c/go/src/pkg/net/tcpsock_posix.go:234 +0x5d
net.(*TCPListener).Accept(0xc20803e168, 0x0, 0x0, 0x0, 0x0)
/0/c/go/src/pkg/net/tcpsock_posix.go:244 +0x4b
github.com/revel/revel.Run(0xe6d9)
/0/go/src/github.com/revel/revel/server.go:113 +0x926
main.main()
/0/go/src/github.com/loopj/bugsnag-example-apps/go/revelapp/app/tmp/main.go:109 +0xe1a
goroutine 54 [running]:
runtime.panic(0x35ce40, 0xc208039db0)
/0/c/go/src/pkg/runtime/panic.c:279 +0xf5
github.com/loopj/bugsnag-example-apps/go/revelapp/app/controllers.func·001()
/0/go/src/github.com/loopj/bugsnag-example-apps/go/revelapp/app/controllers/app.go:13 +0x74
net/http.(*Server).Serve(0xc20806c780, 0x910c88, 0xc20803e168, 0x0, 0x0)
/0/c/go/src/pkg/net/http/server.go:1698 +0x91
`
var result = []StackFrame{
StackFrame{File: "/0/c/go/src/pkg/runtime/panic.c", LineNumber: 279, Name: "panic", Package: "runtime"},
StackFrame{File: "/0/go/src/github.com/loopj/bugsnag-example-apps/go/revelapp/app/controllers/app.go", LineNumber: 13, Name: "func.001", Package: "github.com/loopj/bugsnag-example-apps/go/revelapp/app/controllers"},
StackFrame{File: "/0/c/go/src/pkg/net/http/server.go", LineNumber: 1698, Name: "(*Server).Serve", Package: "net/http"},
}
var resultCreatedBy = append(result,
StackFrame{File: "/0/go/src/github.com/loopj/bugsnag-example-apps/go/revelapp/app/controllers/app.go", LineNumber: 14, Name: "App.Index", Package: "github.com/loopj/bugsnag-example-apps/go/revelapp/app/controllers", ProgramCounter: 0x0})
func TestParsePanic(t *testing.T) {
todo := map[string]string{
"createdBy": createdBy,
"normalSplit": normalSplit,
"lastGoroutine": lastGoroutine,
}
for key, val := range todo {
Err, err := ParsePanic(val)
if err != nil {
t.Fatal(err)
}
if Err.TypeName() != "panic" {
t.Errorf("Wrong type: %s", Err.TypeName())
}
if Err.Error() != "hello!" {
t.Errorf("Wrong message: %s", Err.TypeName())
}
if Err.StackFrames()[0].Func() != nil {
t.Errorf("Somehow managed to find a func...")
}
result := result
if key == "createdBy" {
result = resultCreatedBy
}
if !reflect.DeepEqual(Err.StackFrames(), result) {
t.Errorf("Wrong stack for %s: %#v", key, Err.StackFrames())
}
}
}

View file

@ -0,0 +1,97 @@
package errors
import (
"bytes"
"fmt"
"io/ioutil"
"runtime"
"strings"
)
// A StackFrame contains all necessary information about to generate a line
// in a callstack.
type StackFrame struct {
File string
LineNumber int
Name string
Package string
ProgramCounter uintptr
}
// NewStackFrame popoulates a stack frame object from the program counter.
func NewStackFrame(pc uintptr) (frame StackFrame) {
frame = StackFrame{ProgramCounter: pc}
if frame.Func() == nil {
return
}
frame.Package, frame.Name = packageAndName(frame.Func())
// pc -1 because the program counters we use are usually return addresses,
// and we want to show the line that corresponds to the function call
frame.File, frame.LineNumber = frame.Func().FileLine(pc - 1)
return
}
// Func returns the function that this stackframe corresponds to
func (frame *StackFrame) Func() *runtime.Func {
if frame.ProgramCounter == 0 {
return nil
}
return runtime.FuncForPC(frame.ProgramCounter)
}
// String returns the stackframe formatted in the same way as go does
// in runtime/debug.Stack()
func (frame *StackFrame) String() string {
str := fmt.Sprintf("%s:%d (0x%x)\n", frame.File, frame.LineNumber, frame.ProgramCounter)
source, err := frame.SourceLine()
if err != nil {
return str
}
return str + fmt.Sprintf("\t%s: %s\n", frame.Name, source)
}
// SourceLine gets the line of code (from File and Line) of the original source if possible
func (frame *StackFrame) SourceLine() (string, error) {
data, err := ioutil.ReadFile(frame.File)
if err != nil {
return "", err
}
lines := bytes.Split(data, []byte{'\n'})
if frame.LineNumber <= 0 || frame.LineNumber >= len(lines) {
return "???", nil
}
// -1 because line-numbers are 1 based, but our array is 0 based
return string(bytes.Trim(lines[frame.LineNumber-1], " \t")), nil
}
func packageAndName(fn *runtime.Func) (string, string) {
name := fn.Name()
pkg := ""
// 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
// Since the package path might contains dots (e.g. code.google.com/...),
// we first remove the path prefix if there is one.
if lastslash := strings.LastIndex(name, "/"); lastslash >= 0 {
pkg += name[:lastslash] + "/"
name = name[lastslash+1:]
}
if period := strings.Index(name, "."); period >= 0 {
pkg += name[:period]
name = name[period+1:]
}
name = strings.Replace(name, "·", ".", -1)
return pkg, name
}

134
vendor/github.com/bugsnag/bugsnag-go/event.go generated vendored Normal file
View file

@ -0,0 +1,134 @@
package bugsnag
import (
"strings"
"github.com/bugsnag/bugsnag-go/errors"
)
// Context is the context of the error in Bugsnag.
// This can be passed to Notify, Recover or AutoNotify as rawData.
type Context struct {
String string
}
// User represents the searchable user-data on Bugsnag. The Id is also used
// to determine the number of users affected by a bug. This can be
// passed to Notify, Recover or AutoNotify as rawData.
type User struct {
Id string `json:"id,omitempty"`
Name string `json:"name,omitempty"`
Email string `json:"email,omitempty"`
}
// Sets the severity of the error on Bugsnag. These values can be
// passed to Notify, Recover or AutoNotify as rawData.
var (
SeverityError = severity{"error"}
SeverityWarning = severity{"warning"}
SeverityInfo = severity{"info"}
)
// The severity tag type, private so that people can only use Error,Warning,Info
type severity struct {
String string
}
// The form of stacktrace that Bugsnag expects
type stackFrame struct {
Method string `json:"method"`
File string `json:"file"`
LineNumber int `json:"lineNumber"`
InProject bool `json:"inProject,omitempty"`
}
// Event represents a payload of data that gets sent to Bugsnag.
// This is passed to each OnBeforeNotify hook.
type Event struct {
// The original error that caused this event, not sent to Bugsnag.
Error *errors.Error
// The rawData affecting this error, not sent to Bugsnag.
RawData []interface{}
// The error class to be sent to Bugsnag. This defaults to the type name of the Error, for
// example *error.String
ErrorClass string
// The error message to be sent to Bugsnag. This defaults to the return value of Error.Error()
Message string
// The stacktrrace of the error to be sent to Bugsnag.
Stacktrace []stackFrame
// The context to be sent to Bugsnag. This should be set to the part of the app that was running,
// e.g. for http requests, set it to the path.
Context string
// The severity of the error. Can be SeverityError, SeverityWarning or SeverityInfo.
Severity severity
// The grouping hash is used to override Bugsnag's grouping. Set this if you'd like all errors with
// the same grouping hash to group together in the dashboard.
GroupingHash string
// User data to send to Bugsnag. This is searchable on the dashboard.
User *User
// Other MetaData to send to Bugsnag. Appears as a set of tabbed tables in the dashboard.
MetaData MetaData
}
func newEvent(err *errors.Error, rawData []interface{}, notifier *Notifier) (*Event, *Configuration) {
config := notifier.Config
event := &Event{
Error: err,
RawData: append(notifier.RawData, rawData...),
ErrorClass: err.TypeName(),
Message: err.Error(),
Stacktrace: make([]stackFrame, len(err.StackFrames())),
Severity: SeverityWarning,
MetaData: make(MetaData),
}
for _, datum := range event.RawData {
switch datum := datum.(type) {
case severity:
event.Severity = datum
case Context:
event.Context = datum.String
case Configuration:
config = config.merge(&datum)
case MetaData:
event.MetaData.Update(datum)
case User:
event.User = &datum
}
}
for i, frame := range err.StackFrames() {
file := frame.File
inProject := config.isProjectPackage(frame.Package)
// remove $GOROOT and $GOHOME from other frames
if idx := strings.Index(file, frame.Package); idx > -1 {
file = file[idx:]
}
if inProject {
file = config.stripProjectPackages(file)
}
event.Stacktrace[i] = stackFrame{
Method: frame.Name,
File: file,
LineNumber: frame.LineNumber,
InProject: inProject,
}
}
return event, config
}

43
vendor/github.com/bugsnag/bugsnag-go/json_tags.go generated vendored Normal file
View file

@ -0,0 +1,43 @@
// The code is stripped from:
// http://golang.org/src/pkg/encoding/json/tags.go?m=text
package bugsnag
import (
"strings"
)
// tagOptions is the string following a comma in a struct field's "json"
// tag, or the empty string. It does not include the leading comma.
type tagOptions string
// parseTag splits a struct field's json tag into its name and
// comma-separated options.
func parseTag(tag string) (string, tagOptions) {
if idx := strings.Index(tag, ","); idx != -1 {
return tag[:idx], tagOptions(tag[idx+1:])
}
return tag, tagOptions("")
}
// Contains reports whether a comma-separated list of options
// contains a particular substr flag. substr must be surrounded by a
// string boundary or commas.
func (o tagOptions) Contains(optionName string) bool {
if len(o) == 0 {
return false
}
s := string(o)
for s != "" {
var next string
i := strings.Index(s, ",")
if i >= 0 {
s, next = s[:i], s[i+1:]
}
if s == optionName {
return true
}
s = next
}
return false
}

185
vendor/github.com/bugsnag/bugsnag-go/metadata.go generated vendored Normal file
View file

@ -0,0 +1,185 @@
package bugsnag
import (
"fmt"
"reflect"
"strings"
)
// MetaData is added to the Bugsnag dashboard in tabs. Each tab is
// a map of strings -> values. You can pass MetaData to Notify, Recover
// and AutoNotify as rawData.
type MetaData map[string]map[string]interface{}
// Update the meta-data with more information. Tabs are merged together such
// that unique keys from both sides are preserved, and duplicate keys end up
// with the provided values.
func (meta MetaData) Update(other MetaData) {
for name, tab := range other {
if meta[name] == nil {
meta[name] = make(map[string]interface{})
}
for key, value := range tab {
meta[name][key] = value
}
}
}
// Add creates a tab of Bugsnag meta-data.
// If the tab doesn't yet exist it will be created.
// If the key already exists, it will be overwritten.
func (meta MetaData) Add(tab string, key string, value interface{}) {
if meta[tab] == nil {
meta[tab] = make(map[string]interface{})
}
meta[tab][key] = value
}
// AddStruct creates a tab of Bugsnag meta-data.
// The struct will be converted to an Object using the
// reflect library so any private fields will not be exported.
// As a safety measure, if you pass a non-struct the value will be
// sent to Bugsnag under the "Extra data" tab.
func (meta MetaData) AddStruct(tab string, obj interface{}) {
val := sanitizer{}.Sanitize(obj)
content, ok := val.(map[string]interface{})
if ok {
meta[tab] = content
} else {
// Wasn't a struct
meta.Add("Extra data", tab, obj)
}
}
// Remove any values from meta-data that have keys matching the filters,
// and any that are recursive data-structures
func (meta MetaData) sanitize(filters []string) interface{} {
return sanitizer{
Filters: filters,
Seen: make([]interface{}, 0),
}.Sanitize(meta)
}
// The sanitizer is used to remove filtered params and recursion from meta-data.
type sanitizer struct {
Filters []string
Seen []interface{}
}
func (s sanitizer) Sanitize(data interface{}) interface{} {
for _, s := range s.Seen {
// TODO: we don't need deep equal here, just type-ignoring equality
if reflect.DeepEqual(data, s) {
return "[RECURSION]"
}
}
// Sanitizers are passed by value, so we can modify s and it only affects
// s.Seen for nested calls.
s.Seen = append(s.Seen, data)
t := reflect.TypeOf(data)
v := reflect.ValueOf(data)
switch t.Kind() {
case reflect.Bool,
reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64,
reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr,
reflect.Float32, reflect.Float64:
return data
case reflect.String:
return data
case reflect.Interface, reflect.Ptr:
return s.Sanitize(v.Elem().Interface())
case reflect.Array, reflect.Slice:
ret := make([]interface{}, v.Len())
for i := 0; i < v.Len(); i++ {
ret[i] = s.Sanitize(v.Index(i).Interface())
}
return ret
case reflect.Map:
return s.sanitizeMap(v)
case reflect.Struct:
return s.sanitizeStruct(v, t)
// Things JSON can't serialize:
// case t.Chan, t.Func, reflect.Complex64, reflect.Complex128, reflect.UnsafePointer:
default:
return "[" + t.String() + "]"
}
}
func (s sanitizer) sanitizeMap(v reflect.Value) interface{} {
ret := make(map[string]interface{})
for _, key := range v.MapKeys() {
val := s.Sanitize(v.MapIndex(key).Interface())
newKey := fmt.Sprintf("%v", key.Interface())
if s.shouldRedact(newKey) {
val = "[REDACTED]"
}
ret[newKey] = val
}
return ret
}
func (s sanitizer) sanitizeStruct(v reflect.Value, t reflect.Type) interface{} {
ret := make(map[string]interface{})
for i := 0; i < v.NumField(); i++ {
val := v.Field(i)
// Don't export private fields
if !val.CanInterface() {
continue
}
name := t.Field(i).Name
var opts tagOptions
// Parse JSON tags. Supports name and "omitempty"
if jsonTag := t.Field(i).Tag.Get("json"); len(jsonTag) != 0 {
name, opts = parseTag(jsonTag)
}
if s.shouldRedact(name) {
ret[name] = "[REDACTED]"
} else {
sanitized := s.Sanitize(val.Interface())
if str, ok := sanitized.(string); ok {
if !(opts.Contains("omitempty") && len(str) == 0) {
ret[name] = str
}
} else {
ret[name] = sanitized
}
}
}
return ret
}
func (s sanitizer) shouldRedact(key string) bool {
for _, filter := range s.Filters {
if strings.Contains(strings.ToLower(filter), strings.ToLower(key)) {
return true
}
}
return false
}

182
vendor/github.com/bugsnag/bugsnag-go/metadata_test.go generated vendored Normal file
View file

@ -0,0 +1,182 @@
package bugsnag
import (
"reflect"
"testing"
"unsafe"
"github.com/bugsnag/bugsnag-go/errors"
)
type _account struct {
ID string
Name string
Plan struct {
Premium bool
}
Password string
secret string
Email string `json:"email"`
EmptyEmail string `json:"emptyemail,omitempty"`
NotEmptyEmail string `json:"not_empty_email,omitempty"`
}
type _broken struct {
Me *_broken
Data string
}
var account = _account{}
var notifier = New(Configuration{})
func TestMetaDataAdd(t *testing.T) {
m := MetaData{
"one": {
"key": "value",
"override": false,
}}
m.Add("one", "override", true)
m.Add("one", "new", "key")
m.Add("new", "tab", account)
m.AddStruct("lol", "not really a struct")
m.AddStruct("account", account)
if !reflect.DeepEqual(m, MetaData{
"one": {
"key": "value",
"override": true,
"new": "key",
},
"new": {
"tab": account,
},
"Extra data": {
"lol": "not really a struct",
},
"account": {
"ID": "",
"Name": "",
"Plan": map[string]interface{}{
"Premium": false,
},
"Password": "",
"email": "",
},
}) {
t.Errorf("metadata.Add didn't work: %#v", m)
}
}
func TestMetaDataUpdate(t *testing.T) {
m := MetaData{
"one": {
"key": "value",
"override": false,
}}
m.Update(MetaData{
"one": {
"override": true,
"new": "key",
},
"new": {
"tab": account,
},
})
if !reflect.DeepEqual(m, MetaData{
"one": {
"key": "value",
"override": true,
"new": "key",
},
"new": {
"tab": account,
},
}) {
t.Errorf("metadata.Update didn't work: %#v", m)
}
}
func TestMetaDataSanitize(t *testing.T) {
var broken = _broken{}
broken.Me = &broken
broken.Data = "ohai"
account.Name = "test"
account.ID = "test"
account.secret = "hush"
account.Email = "example@example.com"
account.EmptyEmail = ""
account.NotEmptyEmail = "not_empty_email@example.com"
m := MetaData{
"one": {
"bool": true,
"int": 7,
"float": 7.1,
"complex": complex(1, 1),
"func": func() {},
"unsafe": unsafe.Pointer(broken.Me),
"string": "string",
"password": "secret",
"array": []hash{{
"creditcard": "1234567812345678",
"broken": broken,
}},
"broken": broken,
"account": account,
},
}
n := m.sanitize([]string{"password", "creditcard"})
if !reflect.DeepEqual(n, map[string]interface{}{
"one": map[string]interface{}{
"bool": true,
"int": 7,
"float": 7.1,
"complex": "[complex128]",
"string": "string",
"unsafe": "[unsafe.Pointer]",
"func": "[func()]",
"password": "[REDACTED]",
"array": []interface{}{map[string]interface{}{
"creditcard": "[REDACTED]",
"broken": map[string]interface{}{
"Me": "[RECURSION]",
"Data": "ohai",
},
}},
"broken": map[string]interface{}{
"Me": "[RECURSION]",
"Data": "ohai",
},
"account": map[string]interface{}{
"ID": "test",
"Name": "test",
"Plan": map[string]interface{}{
"Premium": false,
},
"Password": "[REDACTED]",
"email": "example@example.com",
"not_empty_email": "not_empty_email@example.com",
},
},
}) {
t.Errorf("metadata.Sanitize didn't work: %#v", n)
}
}
func ExampleMetaData() {
notifier.Notify(errors.Errorf("hi world"),
MetaData{"Account": {
"id": account.ID,
"name": account.Name,
"paying?": account.Plan.Premium,
}})
}

96
vendor/github.com/bugsnag/bugsnag-go/middleware.go generated vendored Normal file
View file

@ -0,0 +1,96 @@
package bugsnag
import (
"net/http"
"strings"
)
type (
beforeFunc func(*Event, *Configuration) error
// MiddlewareStacks keep middleware in the correct order. They are
// called in reverse order, so if you add a new middleware it will
// be called before all existing middleware.
middlewareStack struct {
before []beforeFunc
}
)
// AddMiddleware adds a new middleware to the outside of the existing ones,
// when the middlewareStack is Run it will be run before all middleware that
// have been added before.
func (stack *middlewareStack) OnBeforeNotify(middleware beforeFunc) {
stack.before = append(stack.before, middleware)
}
// Run causes all the middleware to be run. If they all permit it the next callback
// will be called with all the middleware on the stack.
func (stack *middlewareStack) Run(event *Event, config *Configuration, next func() error) error {
// run all the before filters in reverse order
for i := range stack.before {
before := stack.before[len(stack.before)-i-1]
err := stack.runBeforeFilter(before, event, config)
if err != nil {
return err
}
}
return next()
}
func (stack *middlewareStack) runBeforeFilter(f beforeFunc, event *Event, config *Configuration) error {
defer func() {
if err := recover(); err != nil {
config.log("bugsnag/middleware: unexpected panic: %v", err)
}
}()
return f(event, config)
}
// catchMiddlewarePanic is used to log any panics that happen inside Middleware,
// we wouldn't want to not notify Bugsnag in this case.
func catchMiddlewarePanic(event *Event, config *Configuration, next func() error) {
}
// httpRequestMiddleware is added OnBeforeNotify by default. It takes information
// from an http.Request passed in as rawData, and adds it to the Event. You can
// use this as a template for writing your own Middleware.
func httpRequestMiddleware(event *Event, config *Configuration) error {
for _, datum := range event.RawData {
if request, ok := datum.(*http.Request); ok {
proto := "http://"
if request.TLS != nil {
proto = "https://"
}
event.MetaData.Update(MetaData{
"Request": {
"RemoteAddr": request.RemoteAddr,
"Method": request.Method,
"Url": proto + request.Host + request.RequestURI,
"Params": request.URL.Query(),
},
})
// Add headers as a separate tab.
event.MetaData.AddStruct("Headers", request.Header)
// Default context to Path
if event.Context == "" {
event.Context = request.URL.Path
}
// Default user.id to IP so that users-affected works.
if event.User == nil {
ip := request.RemoteAddr
if idx := strings.LastIndex(ip, ":"); idx != -1 {
ip = ip[:idx]
}
event.User = &User{Id: ip}
}
}
}
return nil
}

View file

@ -0,0 +1,88 @@
package bugsnag
import (
"bytes"
"fmt"
"log"
"reflect"
"testing"
)
func TestMiddlewareOrder(t *testing.T) {
result := make([]int, 0, 7)
stack := middlewareStack{}
stack.OnBeforeNotify(func(e *Event, c *Configuration) error {
result = append(result, 2)
return nil
})
stack.OnBeforeNotify(func(e *Event, c *Configuration) error {
result = append(result, 1)
return nil
})
stack.OnBeforeNotify(func(e *Event, c *Configuration) error {
result = append(result, 0)
return nil
})
stack.Run(nil, nil, func() error {
result = append(result, 3)
return nil
})
if !reflect.DeepEqual(result, []int{0, 1, 2, 3}) {
t.Errorf("unexpected middleware order %v", result)
}
}
func TestBeforeNotifyReturnErr(t *testing.T) {
stack := middlewareStack{}
err := fmt.Errorf("test")
stack.OnBeforeNotify(func(e *Event, c *Configuration) error {
return err
})
called := false
e := stack.Run(nil, nil, func() error {
called = true
return nil
})
if e != err {
t.Errorf("Middleware didn't return the error")
}
if called == true {
t.Errorf("Notify was called when BeforeNotify returned False")
}
}
func TestBeforeNotifyPanic(t *testing.T) {
stack := middlewareStack{}
stack.OnBeforeNotify(func(e *Event, c *Configuration) error {
panic("oops")
})
called := false
b := &bytes.Buffer{}
stack.Run(nil, &Configuration{Logger: log.New(b, log.Prefix(), 0)}, func() error {
called = true
return nil
})
logged := b.String()
if logged != "bugsnag/middleware: unexpected panic: oops\n" {
t.Errorf("Logged: %s", logged)
}
if called == false {
t.Errorf("Notify was not called when BeforeNotify panicked")
}
}

95
vendor/github.com/bugsnag/bugsnag-go/notifier.go generated vendored Normal file
View file

@ -0,0 +1,95 @@
package bugsnag
import (
"fmt"
"github.com/bugsnag/bugsnag-go/errors"
)
// Notifier sends errors to Bugsnag.
type Notifier struct {
Config *Configuration
RawData []interface{}
}
// New creates a new notifier.
// You can pass an instance of bugsnag.Configuration in rawData to change the configuration.
// Other values of rawData will be passed to Notify.
func New(rawData ...interface{}) *Notifier {
config := Config.clone()
for i, datum := range rawData {
if c, ok := datum.(Configuration); ok {
config.update(&c)
rawData[i] = nil
}
}
return &Notifier{
Config: config,
RawData: rawData,
}
}
// Notify sends an error to Bugsnag. Any rawData you pass here will be sent to
// Bugsnag after being converted to JSON. e.g. bugsnag.SeverityError, bugsnag.Context,
// or bugsnag.MetaData.
func (notifier *Notifier) Notify(err error, rawData ...interface{}) (e error) {
event, config := newEvent(errors.New(err, 1), rawData, notifier)
// Never block, start throwing away errors if we have too many.
e = middleware.Run(event, config, func() error {
config.log("notifying bugsnag: %s", event.Message)
if config.notifyInReleaseStage() {
if config.Synchronous {
return (&payload{event, config}).deliver()
}
go (&payload{event, config}).deliver()
return nil
}
return fmt.Errorf("not notifying in %s", config.ReleaseStage)
})
if e != nil {
config.log("bugsnag.Notify: %v", e)
}
return e
}
// AutoNotify notifies Bugsnag of any panics, then repanics.
// It sends along any rawData that gets passed in.
// Usage: defer AutoNotify()
func (notifier *Notifier) AutoNotify(rawData ...interface{}) {
if err := recover(); err != nil {
rawData = notifier.addDefaultSeverity(rawData, SeverityError)
notifier.Notify(errors.New(err, 2), rawData...)
panic(err)
}
}
// Recover logs any panics, then recovers.
// It sends along any rawData that gets passed in.
// Usage: defer Recover()
func (notifier *Notifier) Recover(rawData ...interface{}) {
if err := recover(); err != nil {
rawData = notifier.addDefaultSeverity(rawData, SeverityWarning)
notifier.Notify(errors.New(err, 2), rawData...)
}
}
func (notifier *Notifier) dontPanic() {
if err := recover(); err != nil {
notifier.Config.log("bugsnag/notifier.Notify: panic! %s", err)
}
}
// Add a severity to raw data only if the default is not set.
func (notifier *Notifier) addDefaultSeverity(rawData []interface{}, s severity) []interface{} {
for _, datum := range append(notifier.RawData, rawData...) {
if _, ok := datum.(severity); ok {
return rawData
}
}
return append(rawData, s)
}

27
vendor/github.com/bugsnag/bugsnag-go/panicwrap.go generated vendored Normal file
View file

@ -0,0 +1,27 @@
// +build !appengine
package bugsnag
import (
"github.com/bugsnag/panicwrap"
"github.com/bugsnag/bugsnag-go/errors"
)
// NOTE: this function does not return when you call it, instead it
// re-exec()s the current process with panic monitoring.
func defaultPanicHandler() {
defer defaultNotifier.dontPanic()
err := panicwrap.BasicMonitor(func(output string) {
toNotify, err := errors.ParsePanic(output)
if err != nil {
defaultNotifier.Config.log("bugsnag.handleUncaughtPanic: %v", err)
}
Notify(toNotify, SeverityError, Configuration{Synchronous: true})
})
if err != nil {
defaultNotifier.Config.log("bugsnag.handleUncaughtPanic: %v", err)
}
}

79
vendor/github.com/bugsnag/bugsnag-go/panicwrap_test.go generated vendored Normal file
View file

@ -0,0 +1,79 @@
// +build !appengine
package bugsnag
import (
"github.com/bitly/go-simplejson"
"github.com/mitchellh/osext"
"os"
"os/exec"
"testing"
"time"
)
func TestPanicHandler(t *testing.T) {
startTestServer()
exePath, err := osext.Executable()
if err != nil {
t.Fatal(err)
}
// Use the same trick as panicwrap() to re-run ourselves.
// In the init() block below, we will then panic.
cmd := exec.Command(exePath, os.Args[1:]...)
cmd.Env = append(os.Environ(), "BUGSNAG_API_KEY="+testAPIKey, "BUGSNAG_ENDPOINT="+testEndpoint, "please_panic=please_panic")
if err = cmd.Start(); err != nil {
t.Fatal(err)
}
if err = cmd.Wait(); err.Error() != "exit status 2" {
t.Fatal(err)
}
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() != "ruh roh" {
t.Errorf("caught wrong panic")
}
if exception.Get("errorClass").MustString() != "panic" {
t.Errorf("caught wrong panic")
}
frame := exception.Get("stacktrace").GetIndex(1)
// Yeah, we just caught a panic from the init() function below and sent it to the server running above (mindblown)
if frame.Get("inProject").MustBool() != true ||
frame.Get("file").MustString() != "panicwrap_test.go" ||
frame.Get("method").MustString() != "panick" ||
frame.Get("lineNumber").MustInt() == 0 {
t.Errorf("stack trace seemed wrong")
}
}
func init() {
if os.Getenv("please_panic") != "" {
Configure(Configuration{APIKey: os.Getenv("BUGSNAG_API_KEY"), Endpoint: os.Getenv("BUGSNAG_ENDPOINT"), ProjectPackages: []string{"github.com/bugsnag/bugsnag-go"}})
go func() {
panick()
}()
// Plenty of time to crash, it shouldn't need any of it.
time.Sleep(1 * time.Second)
}
}
func panick() {
panic("ruh roh")
}

96
vendor/github.com/bugsnag/bugsnag-go/payload.go generated vendored Normal file
View file

@ -0,0 +1,96 @@
package bugsnag
import (
"bytes"
"encoding/json"
"fmt"
"net/http"
)
type payload struct {
*Event
*Configuration
}
type hash map[string]interface{}
func (p *payload) deliver() error {
if len(p.APIKey) != 32 {
return fmt.Errorf("bugsnag/payload.deliver: invalid api key")
}
buf, err := json.Marshal(p)
if err != nil {
return fmt.Errorf("bugsnag/payload.deliver: %v", err)
}
client := http.Client{
Transport: p.Transport,
}
resp, err := client.Post(p.Endpoint, "application/json", bytes.NewBuffer(buf))
if err != nil {
return fmt.Errorf("bugsnag/payload.deliver: %v", err)
}
defer resp.Body.Close()
if resp.StatusCode != 200 {
return fmt.Errorf("bugsnag/payload.deliver: Got HTTP %s\n", resp.Status)
}
return nil
}
func (p *payload) MarshalJSON() ([]byte, error) {
data := hash{
"apiKey": p.APIKey,
"notifier": hash{
"name": "Bugsnag Go",
"url": "https://github.com/bugsnag/bugsnag-go",
"version": VERSION,
},
"events": []hash{
{
"payloadVersion": "2",
"exceptions": []hash{
{
"errorClass": p.ErrorClass,
"message": p.Message,
"stacktrace": p.Stacktrace,
},
},
"severity": p.Severity.String,
"app": hash{
"releaseStage": p.ReleaseStage,
},
"user": p.User,
"metaData": p.MetaData.sanitize(p.ParamsFilters),
},
},
}
event := data["events"].([]hash)[0]
if p.Context != "" {
event["context"] = p.Context
}
if p.GroupingHash != "" {
event["groupingHash"] = p.GroupingHash
}
if p.Hostname != "" {
event["device"] = hash{
"hostname": p.Hostname,
}
}
if p.AppVersion != "" {
event["app"].(hash)["version"] = p.AppVersion
}
return json.Marshal(data)
}

View file

@ -0,0 +1,60 @@
// Package bugsnagrevel adds Bugsnag to revel.
// It lets you pass *revel.Controller into bugsnag.Notify(),
// and provides a Filter to catch errors.
package bugsnagrevel
import (
"strings"
"sync"
"github.com/bugsnag/bugsnag-go"
"github.com/revel/revel"
)
var once sync.Once
// Filter should be added to the filter chain just after the PanicFilter.
// It sends errors to Bugsnag automatically. Configuration is read out of
// conf/app.conf, you should set bugsnag.apikey, and can also set
// bugsnag.endpoint, bugsnag.releasestage, bugsnag.appversion,
// bugsnag.projectroot, bugsnag.projectpackages if needed.
func Filter(c *revel.Controller, fc []revel.Filter) {
defer bugsnag.AutoNotify(c)
fc[0](c, fc[1:])
}
// Add support to bugsnag for reading data out of *revel.Controllers
func middleware(event *bugsnag.Event, config *bugsnag.Configuration) error {
for _, datum := range event.RawData {
if controller, ok := datum.(*revel.Controller); ok {
// make the request visible to the builtin HttpMIddleware
event.RawData = append(event.RawData, controller.Request.Request)
event.Context = controller.Action
event.MetaData.AddStruct("Session", controller.Session)
}
}
return nil
}
func init() {
revel.OnAppStart(func() {
bugsnag.OnBeforeNotify(middleware)
var projectPackages []string
if packages, ok := revel.Config.String("bugsnag.projectpackages"); ok {
projectPackages = strings.Split(packages, ",")
} else {
projectPackages = []string{revel.ImportPath + "/app/*", revel.ImportPath + "/app"}
}
bugsnag.Configure(bugsnag.Configuration{
APIKey: revel.Config.StringDefault("bugsnag.apikey", ""),
Endpoint: revel.Config.StringDefault("bugsnag.endpoint", ""),
AppVersion: revel.Config.StringDefault("bugsnag.appversion", ""),
ReleaseStage: revel.Config.StringDefault("bugsnag.releasestage", revel.RunMode),
ProjectPackages: projectPackages,
Logger: revel.ERROR,
})
})
}