mirror of https://github.com/docker/docs.git
Vendoring bugsnag
Signed-off-by: Jean-Laurent de Morlhon <jeanlaurent@morlhon.net>
This commit is contained in:
parent
53a7528e4d
commit
2051e6eeae
|
|
@ -28,7 +28,9 @@
|
||||||
"github.com/docker/machine/drivers/vmwarevsphere/errors",
|
"github.com/docker/machine/drivers/vmwarevsphere/errors",
|
||||||
"github.com/docker/machine/libmachine",
|
"github.com/docker/machine/libmachine",
|
||||||
"github.com/docker/machine/libmachine/auth",
|
"github.com/docker/machine/libmachine/auth",
|
||||||
|
"github.com/docker/machine/libmachine/bugsnag",
|
||||||
"github.com/docker/machine/libmachine/cert",
|
"github.com/docker/machine/libmachine/cert",
|
||||||
|
"github.com/docker/machine/libmachine/check",
|
||||||
"github.com/docker/machine/libmachine/drivers",
|
"github.com/docker/machine/libmachine/drivers",
|
||||||
"github.com/docker/machine/libmachine/drivers/plugin",
|
"github.com/docker/machine/libmachine/drivers/plugin",
|
||||||
"github.com/docker/machine/libmachine/drivers/plugin/localbinary",
|
"github.com/docker/machine/libmachine/drivers/plugin/localbinary",
|
||||||
|
|
@ -39,6 +41,7 @@
|
||||||
"github.com/docker/machine/libmachine/hosttest",
|
"github.com/docker/machine/libmachine/hosttest",
|
||||||
"github.com/docker/machine/libmachine/libmachinetest",
|
"github.com/docker/machine/libmachine/libmachinetest",
|
||||||
"github.com/docker/machine/libmachine/log",
|
"github.com/docker/machine/libmachine/log",
|
||||||
|
"github.com/docker/machine/libmachine/mcndockerclient",
|
||||||
"github.com/docker/machine/libmachine/mcnerror",
|
"github.com/docker/machine/libmachine/mcnerror",
|
||||||
"github.com/docker/machine/libmachine/mcnflag",
|
"github.com/docker/machine/libmachine/mcnflag",
|
||||||
"github.com/docker/machine/libmachine/mcnutils",
|
"github.com/docker/machine/libmachine/mcnutils",
|
||||||
|
|
@ -60,6 +63,19 @@
|
||||||
"Comment": "v0.8.7-49-gcdaedc6",
|
"Comment": "v0.8.7-49-gcdaedc6",
|
||||||
"Rev": "cdaedc68f2894175ac2b3221869685602c759e71"
|
"Rev": "cdaedc68f2894175ac2b3221869685602c759e71"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"ImportPath": "github.com/bugsnag/bugsnag-go",
|
||||||
|
"Comment": "v1.0.5-19-g02e9528",
|
||||||
|
"Rev": "02e952891c52fbcb15f113d90633897355783b6e"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ImportPath": "github.com/bugsnag/osext",
|
||||||
|
"Rev": "0dd3f918b21bec95ace9dc86c7e70266cfc5c702"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ImportPath": "github.com/bugsnag/panicwrap",
|
||||||
|
"Rev": "e5f9854865b9778a45169fc249e99e338d4d6f27"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"ImportPath": "github.com/MSOpenTech/azure-sdk-for-go",
|
"ImportPath": "github.com/MSOpenTech/azure-sdk-for-go",
|
||||||
"Comment": "v1.1-17-g515f3ec",
|
"Comment": "v1.1-17-g515f3ec",
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,11 @@
|
||||||
|
sudo: false
|
||||||
|
language: go
|
||||||
|
|
||||||
|
go:
|
||||||
|
- 1.3
|
||||||
|
- 1.4
|
||||||
|
- 1.5
|
||||||
|
- tip
|
||||||
|
|
||||||
|
script:
|
||||||
|
- make ci
|
||||||
|
|
@ -0,0 +1,22 @@
|
||||||
|
1.0.4
|
||||||
|
-----
|
||||||
|
|
||||||
|
- Fix appengine integration broken by 1.0.3
|
||||||
|
|
||||||
|
1.0.3
|
||||||
|
-----
|
||||||
|
|
||||||
|
- Allow any Logger with a Printf method.
|
||||||
|
|
||||||
|
1.0.2
|
||||||
|
-----
|
||||||
|
|
||||||
|
- Use bugsnag copies of dependencies to avoid potential link rot
|
||||||
|
|
||||||
|
1.0.1
|
||||||
|
-----
|
||||||
|
|
||||||
|
- gofmt/golint/govet docs improvements.
|
||||||
|
|
||||||
|
1.0.0
|
||||||
|
-----
|
||||||
|
|
@ -0,0 +1,78 @@
|
||||||
|
Contributing
|
||||||
|
============
|
||||||
|
|
||||||
|
- [Fork](https://help.github.com/articles/fork-a-repo) the [notifier on github](https://github.com/bugsnag/bugsnag-go)
|
||||||
|
- Build and test your changes
|
||||||
|
- Commit and push until you are happy with your contribution
|
||||||
|
- [Make a pull request](https://help.github.com/articles/using-pull-requests)
|
||||||
|
- Thanks!
|
||||||
|
|
||||||
|
|
||||||
|
Installing the go development environment
|
||||||
|
-------------------------------------
|
||||||
|
|
||||||
|
1. Install homebrew
|
||||||
|
|
||||||
|
```
|
||||||
|
ruby -e "$(curl -fsSL https://raw.github.com/Homebrew/homebrew/go/install)"
|
||||||
|
```
|
||||||
|
|
||||||
|
1. Install go
|
||||||
|
|
||||||
|
```
|
||||||
|
brew install go --cross-compile-all
|
||||||
|
```
|
||||||
|
|
||||||
|
1. Configure `$GOPATH` in `~/.bashrc`
|
||||||
|
|
||||||
|
```
|
||||||
|
export GOPATH="$HOME/go"
|
||||||
|
export PATH=$PATH:$GOPATH/bin
|
||||||
|
```
|
||||||
|
|
||||||
|
Installing the appengine development environment
|
||||||
|
------------------------------------------------
|
||||||
|
|
||||||
|
1. Follow the [Google instructions](https://cloud.google.com/appengine/downloads).
|
||||||
|
|
||||||
|
Downloading the code
|
||||||
|
--------------------
|
||||||
|
|
||||||
|
You can download the code and its dependencies using
|
||||||
|
|
||||||
|
```
|
||||||
|
go get -t github.com/bugsnag/bugsnag-go
|
||||||
|
```
|
||||||
|
|
||||||
|
It will be put into "$GOPATH/src/github.com/bugsnag/bugsnag-go"
|
||||||
|
|
||||||
|
Then install depend
|
||||||
|
|
||||||
|
|
||||||
|
Running Tests
|
||||||
|
-------------
|
||||||
|
|
||||||
|
You can run the tests with
|
||||||
|
|
||||||
|
```shell
|
||||||
|
go test
|
||||||
|
```
|
||||||
|
|
||||||
|
If you've made significant changes, please also test the appengine integration with
|
||||||
|
|
||||||
|
```shell
|
||||||
|
goapp test
|
||||||
|
```
|
||||||
|
|
||||||
|
Releasing a New Version
|
||||||
|
-----------------------
|
||||||
|
|
||||||
|
If you are a project maintainer, you can build and release a new version of
|
||||||
|
`bugsnag-go` as follows:
|
||||||
|
|
||||||
|
1. Commit all your changes.
|
||||||
|
2. Update the version number in `bugsnag.go`.
|
||||||
|
3. Add an entry to `CHANGELOG.md` and update the README if necessary.
|
||||||
|
4. commit tag and push
|
||||||
|
|
||||||
|
git commit -mv1.0.x && git tag v1.0.x && git push origin v1.0.x
|
||||||
|
|
@ -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.
|
||||||
|
|
@ -0,0 +1,30 @@
|
||||||
|
TEST?=./...
|
||||||
|
|
||||||
|
default: alldeps test
|
||||||
|
|
||||||
|
deps:
|
||||||
|
go get -v -d ./...
|
||||||
|
|
||||||
|
alldeps:
|
||||||
|
go get -v -d -t ./...
|
||||||
|
|
||||||
|
updatedeps:
|
||||||
|
go get -v -d -u ./...
|
||||||
|
|
||||||
|
test: alldeps
|
||||||
|
go test
|
||||||
|
@go vet 2>/dev/null ; if [ $$? -eq 3 ]; then \
|
||||||
|
go get golang.org/x/tools/cmd/vet; \
|
||||||
|
fi
|
||||||
|
@go vet $(TEST) ; if [ $$? -eq 1 ]; then \
|
||||||
|
echo "go-vet: Issues running go vet ./..."; \
|
||||||
|
exit 1; \
|
||||||
|
fi
|
||||||
|
|
||||||
|
ci: alldeps test
|
||||||
|
|
||||||
|
bench:
|
||||||
|
go test --bench=.*
|
||||||
|
|
||||||
|
|
||||||
|
.PHONY: bin checkversion ci default deps generate releasebin test testacc testrace updatedeps
|
||||||
|
|
@ -0,0 +1,553 @@
|
||||||
|
# Bugsnag Notifier for Golang
|
||||||
|
[](https://github.com/bugsnag/bugsnag-go/releases)
|
||||||
|
[](https://travis-ci.org/bugsnag/bugsnag-go)
|
||||||
|
[](http://godoc.org/github.com/bugsnag/bugsnag-go)
|
||||||
|
|
||||||
|
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 martini apps
|
||||||
|
|
||||||
|
1. Add `bugsnagmartini.AutoNotify` immediately after the `martini.Recovery` middleware in `main.go`.
|
||||||
|
This causes unhandled panics to notify bugsnag.
|
||||||
|
|
||||||
|
```go
|
||||||
|
|
||||||
|
import "github.com/bugsnag/bugsnag-go/martini"
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
|
||||||
|
m.Use(martini.Recover()
|
||||||
|
|
||||||
|
m.Use(bugsnagmartini.AutoNotify(bugsnag.Configuration{
|
||||||
|
APIKey: "YOUR_API_KEY_HERE",
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
2. Use `bugsnag` from the context injection if you need to notify about non-fatal errors.
|
||||||
|
|
||||||
|
```
|
||||||
|
func MyHandler(r *http.Request, bugsnag *bugsnag.Notifier) string {
|
||||||
|
bugsnag.Notify(err);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 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 (This is done automatically for `bugsnag.Handler` and `bugsnag.HandlerFunc`).
|
||||||
|
The easiest way to do this is to create a new instance of the 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"})
|
||||||
|
```
|
||||||
|
|
||||||
|
### Error Class
|
||||||
|
|
||||||
|
Errors in your Bugsnag dashboard are grouped by their "error class" and by line number.
|
||||||
|
You can override the error class by passing a
|
||||||
|
[`bugsnag.ErrorClass`](https://godoc.org/github.com/bugsnag/bugsnag-go/#ErrorClass) object as
|
||||||
|
rawData.
|
||||||
|
|
||||||
|
```go
|
||||||
|
bugsnag.Notify(err, bugsnag.ErrorClass{"I/O Timeout"})
|
||||||
|
```
|
||||||
|
|
||||||
|
### 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).
|
||||||
|
|
||||||
|
For matching subpackages within a package you may use the `**` notation. For example, `github.com/domain/package/**` will match all subpackages under `package/`.
|
||||||
|
|
||||||
|
```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
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
### Skipping lines in stacktrace
|
||||||
|
|
||||||
|
If you have your own logging wrapper all of your errors will appear to
|
||||||
|
originate from inside it. You can avoid this problem by constructing
|
||||||
|
an error with a stacktrace manually, and then passing that to Bugsnag.notify:
|
||||||
|
|
||||||
|
```go
|
||||||
|
import (
|
||||||
|
"github.com/bugsnag/bugsnag-go"
|
||||||
|
"github.com/bugsnag/bugsnag-go/errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
func LogError(e error) {
|
||||||
|
// 1 removes one line of stacktrace, so the caller of LogError
|
||||||
|
// will be at the top.
|
||||||
|
e = errors.New(e, 1)
|
||||||
|
bugsnag.Notify(e)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
|
@ -0,0 +1,81 @@
|
||||||
|
// +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.
|
||||||
|
|
||||||
|
if configuredLogger, ok := config.Logger.(*log.Logger); ok {
|
||||||
|
config.Logger = log.New(appengineWriter{c}, configuredLogger.Prefix(), configuredLogger.Flags())
|
||||||
|
} else {
|
||||||
|
config.Logger = log.New(appengineWriter{c}, log.Prefix(), log.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
|
||||||
|
}
|
||||||
|
|
@ -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.3"
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,169 @@
|
||||||
|
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 interface {
|
||||||
|
Printf(format string, v ...interface{}) // limited to the functions used
|
||||||
|
}
|
||||||
|
// 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 d, f := filepath.Split(p); f == "**" {
|
||||||
|
if strings.HasPrefix(pkg, d) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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 if p[len(p)-1] == '*' && p[len(p)-2] == '*' {
|
||||||
|
p = p[:len(p)-2]
|
||||||
|
} else {
|
||||||
|
p = p + "/"
|
||||||
|
}
|
||||||
|
if strings.HasPrefix(file, p) {
|
||||||
|
return strings.TrimPrefix(file, p)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return file
|
||||||
|
}
|
||||||
|
|
||||||
|
func (config *Configuration) logf(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
|
||||||
|
}
|
||||||
|
|
@ -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
|
||||||
|
|
@ -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.
|
||||||
|
|
@ -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()
|
||||||
|
}
|
||||||
|
|
@ -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
|
||||||
|
}
|
||||||
|
|
@ -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
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,143 @@
|
||||||
|
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"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// ErrorClass overrides the error class in Bugsnag.
|
||||||
|
// This struct enables you to group errors as you like.
|
||||||
|
type ErrorClass struct {
|
||||||
|
Name string
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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
|
||||||
|
|
||||||
|
case ErrorClass:
|
||||||
|
event.ErrorClass = datum.Name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,10 @@
|
||||||
|
This is an example google app-engine app.
|
||||||
|
|
||||||
|
To use it you will need to install the [App Engine
|
||||||
|
SDK](https://cloud.google.com/appengine/downloads) for Go.
|
||||||
|
|
||||||
|
Then run:
|
||||||
|
|
||||||
|
goapp deploy
|
||||||
|
|
||||||
|
Then open: https://bugsnag-test.appspot.com/ in your web-browser.
|
||||||
|
|
@ -0,0 +1,8 @@
|
||||||
|
application: bugsnag-test
|
||||||
|
version: 1
|
||||||
|
runtime: go
|
||||||
|
api_version: go1
|
||||||
|
|
||||||
|
handlers:
|
||||||
|
- url: /.*
|
||||||
|
script: _go_app
|
||||||
|
|
@ -0,0 +1,30 @@
|
||||||
|
package mellow
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"github.com/bugsnag/bugsnag-go"
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
bugsnag.OnBeforeNotify(func(event *bugsnag.Event, config *bugsnag.Configuration) error {
|
||||||
|
event.MetaData.AddStruct("original", event.Error.StackFrames())
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
bugsnag.Configure(bugsnag.Configuration{
|
||||||
|
APIKey: "066f5ad3590596f9aa8d601ea89af845",
|
||||||
|
})
|
||||||
|
|
||||||
|
http.HandleFunc("/", bugsnag.HandlerFunc(handler))
|
||||||
|
}
|
||||||
|
|
||||||
|
func handler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
fmt.Fprint(w, "welcome")
|
||||||
|
notifier := bugsnag.New(r)
|
||||||
|
notifier.Notify(fmt.Errorf("oh hia"), bugsnag.MetaData{"env": {"values": os.Environ()}})
|
||||||
|
fmt.Fprint(w, "welcome\n")
|
||||||
|
|
||||||
|
panic("zoomg")
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,4 @@
|
||||||
|
2601:9:8480:11d2:7909:b2e5:3722:ef57 - - [08/Jul/2014:01:16:25 -0700] "GET / HTTP/1.1" 500 0 - "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_9_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/35.0.1916.153 Safari/537.36"
|
||||||
|
2601:9:8480:11d2:7909:b2e5:3722:ef57 - - [08/Jul/2014:01:16:25 -0700] "GET /favicon.ico HTTP/1.1" 500 0 - "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_9_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/35.0.1916.153 Safari/537.36"
|
||||||
|
2601:9:8480:11d2:7909:b2e5:3722:ef57 - - [08/Jul/2014:01:18:20 -0700] "GET / HTTP/1.1" 500 0 - "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_9_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/35.0.1916.153 Safari/537.36"
|
||||||
|
2601:9:8480:11d2:7909:b2e5:3722:ef57 - - [08/Jul/2014:01:18:20 -0700] "GET /favicon.ico HTTP/1.1" 500 0 - "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_9_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/35.0.1916.153 Safari/537.36"
|
||||||
|
|
@ -0,0 +1,31 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/bugsnag/bugsnag-go"
|
||||||
|
"log"
|
||||||
|
"net/http"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
|
||||||
|
http.HandleFunc("/", Get)
|
||||||
|
|
||||||
|
bugsnag.Configure(bugsnag.Configuration{
|
||||||
|
APIKey: "066f5ad3590596f9aa8d601ea89af845",
|
||||||
|
})
|
||||||
|
|
||||||
|
log.Println("Serving on 9001")
|
||||||
|
http.ListenAndServe(":9001", bugsnag.Handler(nil))
|
||||||
|
}
|
||||||
|
|
||||||
|
func Get(w http.ResponseWriter, r *http.Request) {
|
||||||
|
w.WriteHeader(200)
|
||||||
|
w.Write([]byte("OK\n"))
|
||||||
|
|
||||||
|
var a struct{}
|
||||||
|
crash(a)
|
||||||
|
}
|
||||||
|
|
||||||
|
func crash(a interface{}) string {
|
||||||
|
return a.(string)
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
test-results/
|
||||||
|
tmp/
|
||||||
|
routes/
|
||||||
19
vendor/github.com/bugsnag/bugsnag-go/examples/revelapp/app/controllers/app.go
generated
vendored
Normal file
19
vendor/github.com/bugsnag/bugsnag-go/examples/revelapp/app/controllers/app.go
generated
vendored
Normal file
|
|
@ -0,0 +1,19 @@
|
||||||
|
package controllers
|
||||||
|
|
||||||
|
import "github.com/revel/revel"
|
||||||
|
import "time"
|
||||||
|
|
||||||
|
type App struct {
|
||||||
|
*revel.Controller
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c App) Index() revel.Result {
|
||||||
|
go func() {
|
||||||
|
time.Sleep(5 * time.Second)
|
||||||
|
panic("hello!")
|
||||||
|
}()
|
||||||
|
|
||||||
|
s := make([]string, 0)
|
||||||
|
revel.INFO.Print(s[0])
|
||||||
|
return c.Render()
|
||||||
|
}
|
||||||
40
vendor/github.com/bugsnag/bugsnag-go/examples/revelapp/app/init.go
generated
vendored
Normal file
40
vendor/github.com/bugsnag/bugsnag-go/examples/revelapp/app/init.go
generated
vendored
Normal file
|
|
@ -0,0 +1,40 @@
|
||||||
|
package app
|
||||||
|
|
||||||
|
import "github.com/revel/revel"
|
||||||
|
import "github.com/bugsnag/bugsnag-go/revel"
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
// Filters is the default set of global filters.
|
||||||
|
revel.Filters = []revel.Filter{
|
||||||
|
revel.PanicFilter, // Recover from panics and display an error page instead.
|
||||||
|
bugsnagrevel.Filter, // Send panics to Bugsnag
|
||||||
|
revel.RouterFilter, // Use the routing table to select the right Action
|
||||||
|
revel.FilterConfiguringFilter, // A hook for adding or removing per-Action filters.
|
||||||
|
revel.ParamsFilter, // Parse parameters into Controller.Params.
|
||||||
|
revel.SessionFilter, // Restore and write the session cookie.
|
||||||
|
revel.FlashFilter, // Restore and write the flash cookie.
|
||||||
|
revel.ValidationFilter, // Restore kept validation errors and save new ones from cookie.
|
||||||
|
revel.I18nFilter, // Resolve the requested language
|
||||||
|
HeaderFilter, // Add some security based headers
|
||||||
|
revel.InterceptorFilter, // Run interceptors around the action.
|
||||||
|
revel.CompressFilter, // Compress the result.
|
||||||
|
revel.ActionInvoker, // Invoke the action.
|
||||||
|
}
|
||||||
|
|
||||||
|
// register startup functions with OnAppStart
|
||||||
|
// ( order dependent )
|
||||||
|
// revel.OnAppStart(InitDB())
|
||||||
|
// revel.OnAppStart(FillCache())
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO turn this into revel.HeaderFilter
|
||||||
|
// should probably also have a filter for CSRF
|
||||||
|
// not sure if it can go in the same filter or not
|
||||||
|
var HeaderFilter = func(c *revel.Controller, fc []revel.Filter) {
|
||||||
|
// Add some common security headers
|
||||||
|
c.Response.Out.Header().Add("X-Frame-Options", "SAMEORIGIN")
|
||||||
|
c.Response.Out.Header().Add("X-XSS-Protection", "1; mode=block")
|
||||||
|
c.Response.Out.Header().Add("X-Content-Type-Options", "nosniff")
|
||||||
|
|
||||||
|
fc[0](c, fc[1:]) // Execute the next filter stage.
|
||||||
|
}
|
||||||
23
vendor/github.com/bugsnag/bugsnag-go/examples/revelapp/app/views/App/Index.html
generated
vendored
Normal file
23
vendor/github.com/bugsnag/bugsnag-go/examples/revelapp/app/views/App/Index.html
generated
vendored
Normal file
|
|
@ -0,0 +1,23 @@
|
||||||
|
{{set . "title" "Home"}}
|
||||||
|
{{template "header.html" .}}
|
||||||
|
|
||||||
|
<header class="hero-unit" style="background-color:#A9F16C">
|
||||||
|
<div class="container">
|
||||||
|
<div class="row">
|
||||||
|
<div class="hero-text">
|
||||||
|
<h1>It works!</h1>
|
||||||
|
<p></p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<div class="container">
|
||||||
|
<div class="row">
|
||||||
|
<div class="span6">
|
||||||
|
{{template "flash.html" .}}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{{template "footer.html" .}}
|
||||||
64
vendor/github.com/bugsnag/bugsnag-go/examples/revelapp/app/views/debug.html
generated
vendored
Normal file
64
vendor/github.com/bugsnag/bugsnag-go/examples/revelapp/app/views/debug.html
generated
vendored
Normal file
|
|
@ -0,0 +1,64 @@
|
||||||
|
<style type="text/css">
|
||||||
|
#sidebar {
|
||||||
|
position: absolute;
|
||||||
|
right: 0px;
|
||||||
|
top:69px;
|
||||||
|
max-width: 75%;
|
||||||
|
z-index: 1000;
|
||||||
|
background-color: #fee;
|
||||||
|
border: thin solid grey;
|
||||||
|
padding: 10px;
|
||||||
|
}
|
||||||
|
#toggleSidebar {
|
||||||
|
position: absolute;
|
||||||
|
right: 0px;
|
||||||
|
top: 50px;
|
||||||
|
background-color: #fee;
|
||||||
|
}
|
||||||
|
|
||||||
|
</style>
|
||||||
|
<div id="sidebar" style="display:none;">
|
||||||
|
<h4>Available pipelines</h4>
|
||||||
|
<dl>
|
||||||
|
{{ range $index, $value := .}}
|
||||||
|
<dt>{{$index}}</dt>
|
||||||
|
<dd>{{$value}}</dd>
|
||||||
|
{{end}}
|
||||||
|
</dl>
|
||||||
|
<h4>Flash</h4>
|
||||||
|
<dl>
|
||||||
|
{{ range $index, $value := .flash}}
|
||||||
|
<dt>{{$index}}</dt>
|
||||||
|
<dd>{{$value}}</dd>
|
||||||
|
{{end}}
|
||||||
|
</dl>
|
||||||
|
|
||||||
|
<h4>Errors</h4>
|
||||||
|
<dl>
|
||||||
|
{{ range $index, $value := .errors}}
|
||||||
|
<dt>{{$index}}</dt>
|
||||||
|
<dd>{{$value}}</dd>
|
||||||
|
{{end}}
|
||||||
|
</dl>
|
||||||
|
</div>
|
||||||
|
<a id="toggleSidebar" href="#" class="toggles"><i class="icon-chevron-left"></i></a>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
$sidebar = 0;
|
||||||
|
$('#toggleSidebar').click(function() {
|
||||||
|
if ($sidebar === 1) {
|
||||||
|
$('#sidebar').hide();
|
||||||
|
$('#toggleSidebar i').addClass('icon-chevron-left');
|
||||||
|
$('#toggleSidebar i').removeClass('icon-chevron-right');
|
||||||
|
$sidebar = 0;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
$('#sidebar').show();
|
||||||
|
$('#toggleSidebar i').addClass('icon-chevron-right');
|
||||||
|
$('#toggleSidebar i').removeClass('icon-chevron-left');
|
||||||
|
$sidebar = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
</script>
|
||||||
20
vendor/github.com/bugsnag/bugsnag-go/examples/revelapp/app/views/errors/404.html
generated
vendored
Normal file
20
vendor/github.com/bugsnag/bugsnag-go/examples/revelapp/app/views/errors/404.html
generated
vendored
Normal file
|
|
@ -0,0 +1,20 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<title>Not found</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
{{if eq .RunMode "dev"}}
|
||||||
|
{{template "errors/404-dev.html" .}}
|
||||||
|
{{else}}
|
||||||
|
{{with .Error}}
|
||||||
|
<h1>
|
||||||
|
{{.Title}}
|
||||||
|
</h1>
|
||||||
|
<p>
|
||||||
|
{{.Description}}
|
||||||
|
</p>
|
||||||
|
{{end}}
|
||||||
|
{{end}}
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
16
vendor/github.com/bugsnag/bugsnag-go/examples/revelapp/app/views/errors/500.html
generated
vendored
Normal file
16
vendor/github.com/bugsnag/bugsnag-go/examples/revelapp/app/views/errors/500.html
generated
vendored
Normal file
|
|
@ -0,0 +1,16 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>Application error</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
{{if eq .RunMode "dev"}}
|
||||||
|
{{template "errors/500-dev.html" .}}
|
||||||
|
{{else}}
|
||||||
|
<h1>Oops, an error occured</h1>
|
||||||
|
<p>
|
||||||
|
This exception has been logged.
|
||||||
|
</p>
|
||||||
|
{{end}}
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
18
vendor/github.com/bugsnag/bugsnag-go/examples/revelapp/app/views/flash.html
generated
vendored
Normal file
18
vendor/github.com/bugsnag/bugsnag-go/examples/revelapp/app/views/flash.html
generated
vendored
Normal file
|
|
@ -0,0 +1,18 @@
|
||||||
|
{{if .flash.success}}
|
||||||
|
<div class="alert alert-success">
|
||||||
|
{{.flash.success}}
|
||||||
|
</div>
|
||||||
|
{{end}}
|
||||||
|
|
||||||
|
{{if or .errors .flash.error}}
|
||||||
|
<div class="alert alert-error">
|
||||||
|
{{if .flash.error}}
|
||||||
|
{{.flash.error}}
|
||||||
|
{{end}}
|
||||||
|
<ul style="margin-top:10px;">
|
||||||
|
{{range .errors}}
|
||||||
|
<li>{{.}}</li>
|
||||||
|
{{end}}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
{{end}}
|
||||||
5
vendor/github.com/bugsnag/bugsnag-go/examples/revelapp/app/views/footer.html
generated
vendored
Normal file
5
vendor/github.com/bugsnag/bugsnag-go/examples/revelapp/app/views/footer.html
generated
vendored
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
{{if eq .RunMode "dev"}}
|
||||||
|
{{template "debug.html" .}}
|
||||||
|
{{end}}
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
17
vendor/github.com/bugsnag/bugsnag-go/examples/revelapp/app/views/header.html
generated
vendored
Normal file
17
vendor/github.com/bugsnag/bugsnag-go/examples/revelapp/app/views/header.html
generated
vendored
Normal file
|
|
@ -0,0 +1,17 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>{{.title}}</title>
|
||||||
|
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
|
||||||
|
<link rel="stylesheet" type="text/css" href="/public/css/bootstrap.css">
|
||||||
|
<link rel="shortcut icon" type="image/png" href="/public/img/favicon.png">
|
||||||
|
<script src="/public/js/jquery-1.9.1.min.js" type="text/javascript" charset="utf-8"></script>
|
||||||
|
{{range .moreStyles}}
|
||||||
|
<link rel="stylesheet" type="text/css" href="/public/{{.}}">
|
||||||
|
{{end}}
|
||||||
|
{{range .moreScripts}}
|
||||||
|
<script src="/public/{{.}}" type="text/javascript" charset="utf-8"></script>
|
||||||
|
{{end}}
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
49
vendor/github.com/bugsnag/bugsnag-go/examples/revelapp/conf/app.conf
generated
vendored
Normal file
49
vendor/github.com/bugsnag/bugsnag-go/examples/revelapp/conf/app.conf
generated
vendored
Normal file
|
|
@ -0,0 +1,49 @@
|
||||||
|
app.name=revelapp
|
||||||
|
app.secret=80ytgNbBeB0X4mfEYybT4QRXKEoYHoaGAIgZkIB7Z8cEVJjZJOPZupIoluJBMorr
|
||||||
|
http.addr=
|
||||||
|
http.port=9000
|
||||||
|
http.ssl=false
|
||||||
|
http.sslcert=
|
||||||
|
http.sslkey=
|
||||||
|
cookie.httponly=false
|
||||||
|
cookie.prefix=REVEL
|
||||||
|
cookie.secure=false
|
||||||
|
format.date=01/02/2006
|
||||||
|
format.datetime=01/02/2006 15:04
|
||||||
|
results.chunked=false
|
||||||
|
|
||||||
|
log.trace.prefix = "TRACE "
|
||||||
|
log.info.prefix = "INFO "
|
||||||
|
log.warn.prefix = "WARN "
|
||||||
|
log.error.prefix = "ERROR "
|
||||||
|
|
||||||
|
# The default language of this application.
|
||||||
|
i18n.default_language=en
|
||||||
|
|
||||||
|
module.static=github.com/revel/revel/modules/static
|
||||||
|
|
||||||
|
bugsnag.apikey=066f5ad3590596f9aa8d601ea89af845
|
||||||
|
|
||||||
|
[dev]
|
||||||
|
mode.dev=true
|
||||||
|
results.pretty=true
|
||||||
|
watch=true
|
||||||
|
|
||||||
|
module.testrunner = github.com/revel/revel/modules/testrunner
|
||||||
|
|
||||||
|
log.trace.output = off
|
||||||
|
log.info.output = stderr
|
||||||
|
log.warn.output = stderr
|
||||||
|
log.error.output = stderr
|
||||||
|
|
||||||
|
[prod]
|
||||||
|
mode.dev=false
|
||||||
|
results.pretty=false
|
||||||
|
watch=false
|
||||||
|
|
||||||
|
module.testrunner =
|
||||||
|
|
||||||
|
log.trace.output = off
|
||||||
|
log.info.output = off
|
||||||
|
log.warn.output = %(app.name)s.log
|
||||||
|
log.error.output = %(app.name)s.log
|
||||||
16
vendor/github.com/bugsnag/bugsnag-go/examples/revelapp/conf/routes
generated
vendored
Normal file
16
vendor/github.com/bugsnag/bugsnag-go/examples/revelapp/conf/routes
generated
vendored
Normal file
|
|
@ -0,0 +1,16 @@
|
||||||
|
# Routes
|
||||||
|
# This file defines all application routes (Higher priority routes first)
|
||||||
|
# ~~~~
|
||||||
|
|
||||||
|
module:testrunner
|
||||||
|
|
||||||
|
GET / App.Index
|
||||||
|
|
||||||
|
# Ignore favicon requests
|
||||||
|
GET /favicon.ico 404
|
||||||
|
|
||||||
|
# Map static resources from the /app/public folder to the /public path
|
||||||
|
GET /public/*filepath Static.Serve("public")
|
||||||
|
|
||||||
|
# Catch all
|
||||||
|
* /:controller/:action :controller.:action
|
||||||
7
vendor/github.com/bugsnag/bugsnag-go/examples/revelapp/messages/sample.en
generated
vendored
Normal file
7
vendor/github.com/bugsnag/bugsnag-go/examples/revelapp/messages/sample.en
generated
vendored
Normal file
|
|
@ -0,0 +1,7 @@
|
||||||
|
# Sample messages file for the English language (en)
|
||||||
|
# Message file extensions should be ISO 639-1 codes (http://en.wikipedia.org/wiki/List_of_ISO_639-1_codes)
|
||||||
|
# Sections within each message file can optionally override the defaults using ISO 3166-1 alpha-2 codes (http://en.wikipedia.org/wiki/ISO_3166-1_alpha-2)
|
||||||
|
# See also:
|
||||||
|
# - http://www.rfc-editor.org/rfc/bcp/bcp47.txt
|
||||||
|
# - http://www.w3.org/International/questions/qa-accept-lang-locales
|
||||||
|
|
||||||
5774
vendor/github.com/bugsnag/bugsnag-go/examples/revelapp/public/css/bootstrap.css
generated
vendored
Normal file
5774
vendor/github.com/bugsnag/bugsnag-go/examples/revelapp/public/css/bootstrap.css
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
BIN
vendor/github.com/bugsnag/bugsnag-go/examples/revelapp/public/img/favicon.png
generated
vendored
Normal file
BIN
vendor/github.com/bugsnag/bugsnag-go/examples/revelapp/public/img/favicon.png
generated
vendored
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 5.5 KiB |
BIN
vendor/github.com/bugsnag/bugsnag-go/examples/revelapp/public/img/glyphicons-halflings-white.png
generated
vendored
Normal file
BIN
vendor/github.com/bugsnag/bugsnag-go/examples/revelapp/public/img/glyphicons-halflings-white.png
generated
vendored
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 8.6 KiB |
BIN
vendor/github.com/bugsnag/bugsnag-go/examples/revelapp/public/img/glyphicons-halflings.png
generated
vendored
Normal file
BIN
vendor/github.com/bugsnag/bugsnag-go/examples/revelapp/public/img/glyphicons-halflings.png
generated
vendored
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 12 KiB |
5
vendor/github.com/bugsnag/bugsnag-go/examples/revelapp/public/js/jquery-1.9.1.min.js
generated
vendored
Normal file
5
vendor/github.com/bugsnag/bugsnag-go/examples/revelapp/public/js/jquery-1.9.1.min.js
generated
vendored
Normal file
File diff suppressed because one or more lines are too long
21
vendor/github.com/bugsnag/bugsnag-go/examples/revelapp/tests/apptest.go
generated
vendored
Normal file
21
vendor/github.com/bugsnag/bugsnag-go/examples/revelapp/tests/apptest.go
generated
vendored
Normal file
|
|
@ -0,0 +1,21 @@
|
||||||
|
package tests
|
||||||
|
|
||||||
|
import "github.com/revel/revel"
|
||||||
|
|
||||||
|
type AppTest struct {
|
||||||
|
revel.TestSuite
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *AppTest) Before() {
|
||||||
|
println("Set up")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t AppTest) TestThatIndexPageWorks() {
|
||||||
|
t.Get("/")
|
||||||
|
t.AssertOk()
|
||||||
|
t.AssertContentType("text/html; charset=utf-8")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *AppTest) After() {
|
||||||
|
println("Tear down")
|
||||||
|
}
|
||||||
|
|
@ -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
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,58 @@
|
||||||
|
/*
|
||||||
|
Package bugsnagmartini provides a martini middleware that sends
|
||||||
|
panics to Bugsnag. You should use this middleware in combination
|
||||||
|
with martini.Recover() if you want to send error messages to your
|
||||||
|
clients:
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
m := martini.New()
|
||||||
|
// used to stop panics bubbling and return a 500 error.
|
||||||
|
m.Use(martini.Recovery())
|
||||||
|
|
||||||
|
// used to send panics to Bugsnag.
|
||||||
|
m.Use(bugsnagmartini.AutoNotify(bugsnag.Configuration{
|
||||||
|
APIKey: "YOUR_API_KEY_HERE",
|
||||||
|
})
|
||||||
|
|
||||||
|
// ...
|
||||||
|
}
|
||||||
|
|
||||||
|
This middleware also makes bugsnag available to martini handlers via
|
||||||
|
the context.
|
||||||
|
|
||||||
|
func myHandler(w http.ResponseWriter, r *http.Request, bugsnag *bugsnag.Notifier) {
|
||||||
|
// ...
|
||||||
|
bugsnag.Notify(err)
|
||||||
|
// ...
|
||||||
|
}
|
||||||
|
|
||||||
|
*/
|
||||||
|
package bugsnagmartini
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/bugsnag/bugsnag-go"
|
||||||
|
"github.com/go-martini/martini"
|
||||||
|
)
|
||||||
|
|
||||||
|
// AutoNotify sends any panics to bugsnag, and then re-raises them.
|
||||||
|
// You should use this after another middleware that
|
||||||
|
// returns an error page to the client, for example martini.Recover().
|
||||||
|
// The arguments can be any RawData to pass to Bugsnag, most usually
|
||||||
|
// you'll pass a bugsnag.Configuration object.
|
||||||
|
func AutoNotify(rawData ...interface{}) martini.Handler {
|
||||||
|
|
||||||
|
// set the release stage based on the martini environment.
|
||||||
|
rawData = append([]interface{}{bugsnag.Configuration{ReleaseStage: martini.Env}},
|
||||||
|
rawData...)
|
||||||
|
|
||||||
|
return func(r *http.Request, c martini.Context) {
|
||||||
|
|
||||||
|
// create a notifier that has the current request bound to it
|
||||||
|
notifier := bugsnag.New(append(rawData, r)...)
|
||||||
|
defer notifier.AutoNotify(r)
|
||||||
|
c.Map(notifier)
|
||||||
|
c.Next()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,189 @@
|
||||||
|
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)
|
||||||
|
|
||||||
|
if t == nil {
|
||||||
|
return "<nil>"
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
@ -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.logf("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
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,102 @@
|
||||||
|
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.logf("notifying bugsnag: %s", event.Message)
|
||||||
|
if config.notifyInReleaseStage() {
|
||||||
|
if config.Synchronous {
|
||||||
|
return (&payload{event, config}).deliver()
|
||||||
|
}
|
||||||
|
// Ensure that any errors are logged if they occur in a goroutine.
|
||||||
|
go func(event *Event, config *Configuration) {
|
||||||
|
err := (&payload{event, config}).deliver()
|
||||||
|
if err != nil {
|
||||||
|
config.logf("bugsnag.Notify: %v", err)
|
||||||
|
}
|
||||||
|
}(event, config)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return fmt.Errorf("not notifying in %s", config.ReleaseStage)
|
||||||
|
})
|
||||||
|
|
||||||
|
if e != nil {
|
||||||
|
config.logf("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.logf("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)
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,27 @@
|
||||||
|
// +build !appengine
|
||||||
|
|
||||||
|
package bugsnag
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/bugsnag/bugsnag-go/errors"
|
||||||
|
"github.com/bugsnag/panicwrap"
|
||||||
|
)
|
||||||
|
|
||||||
|
// 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.logf("bugsnag.handleUncaughtPanic: %v", err)
|
||||||
|
}
|
||||||
|
Notify(toNotify, SeverityError, Configuration{Synchronous: true})
|
||||||
|
})
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
defaultNotifier.Config.logf("bugsnag.handleUncaughtPanic: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -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,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,20 @@
|
||||||
|
Copyright (c) 2012 Daniel Theophanes
|
||||||
|
|
||||||
|
This software is provided 'as-is', without any express or implied
|
||||||
|
warranty. In no event will the authors be held liable for any damages
|
||||||
|
arising from the use of this software.
|
||||||
|
|
||||||
|
Permission is granted to anyone to use this software for any purpose,
|
||||||
|
including commercial applications, and to alter it and redistribute it
|
||||||
|
freely, subject to the following restrictions:
|
||||||
|
|
||||||
|
1. The origin of this software must not be misrepresented; you must not
|
||||||
|
claim that you wrote the original software. If you use this software
|
||||||
|
in a product, an acknowledgment in the product documentation would be
|
||||||
|
appreciated but is not required.
|
||||||
|
|
||||||
|
2. Altered source versions must be plainly marked as such, and must not be
|
||||||
|
misrepresented as being the original software.
|
||||||
|
|
||||||
|
3. This notice may not be removed or altered from any source
|
||||||
|
distribution.
|
||||||
|
|
@ -0,0 +1,32 @@
|
||||||
|
// Copyright 2012 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
// Extensions to the standard "os" package.
|
||||||
|
package osext
|
||||||
|
|
||||||
|
import "path/filepath"
|
||||||
|
|
||||||
|
// Executable returns an absolute path that can be used to
|
||||||
|
// re-invoke the current program.
|
||||||
|
// It may not be valid after the current program exits.
|
||||||
|
func Executable() (string, error) {
|
||||||
|
p, err := executable()
|
||||||
|
return filepath.Clean(p), err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns same path as Executable, returns just the folder
|
||||||
|
// path. Excludes the executable name.
|
||||||
|
func ExecutableFolder() (string, error) {
|
||||||
|
p, err := Executable()
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
folder, _ := filepath.Split(p)
|
||||||
|
return folder, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Depricated. Same as Executable().
|
||||||
|
func GetExePath() (exePath string, err error) {
|
||||||
|
return Executable()
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,16 @@
|
||||||
|
// Copyright 2012 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package osext
|
||||||
|
|
||||||
|
import "syscall"
|
||||||
|
|
||||||
|
func executable() (string, error) {
|
||||||
|
f, err := Open("/proc/" + itoa(Getpid()) + "/text")
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
defer f.Close()
|
||||||
|
return syscall.Fd2path(int(f.Fd()))
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,25 @@
|
||||||
|
// Copyright 2012 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
// +build linux netbsd openbsd
|
||||||
|
|
||||||
|
package osext
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"os"
|
||||||
|
"runtime"
|
||||||
|
)
|
||||||
|
|
||||||
|
func executable() (string, error) {
|
||||||
|
switch runtime.GOOS {
|
||||||
|
case "linux":
|
||||||
|
return os.Readlink("/proc/self/exe")
|
||||||
|
case "netbsd":
|
||||||
|
return os.Readlink("/proc/curproc/exe")
|
||||||
|
case "openbsd":
|
||||||
|
return os.Readlink("/proc/curproc/file")
|
||||||
|
}
|
||||||
|
return "", errors.New("ExecPath not implemented for " + runtime.GOOS)
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,64 @@
|
||||||
|
// Copyright 2012 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
// +build darwin freebsd
|
||||||
|
|
||||||
|
package osext
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"runtime"
|
||||||
|
"syscall"
|
||||||
|
"unsafe"
|
||||||
|
)
|
||||||
|
|
||||||
|
var startUpcwd, getwdError = os.Getwd()
|
||||||
|
|
||||||
|
func executable() (string, error) {
|
||||||
|
var mib [4]int32
|
||||||
|
switch runtime.GOOS {
|
||||||
|
case "freebsd":
|
||||||
|
mib = [4]int32{1 /* CTL_KERN */, 14 /* KERN_PROC */, 12 /* KERN_PROC_PATHNAME */, -1}
|
||||||
|
case "darwin":
|
||||||
|
mib = [4]int32{1 /* CTL_KERN */, 38 /* KERN_PROCARGS */, int32(os.Getpid()), -1}
|
||||||
|
}
|
||||||
|
|
||||||
|
n := uintptr(0)
|
||||||
|
// get length
|
||||||
|
_, _, err := syscall.Syscall6(syscall.SYS___SYSCTL, uintptr(unsafe.Pointer(&mib[0])), 4, 0, uintptr(unsafe.Pointer(&n)), 0, 0)
|
||||||
|
if err != 0 {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
if n == 0 { // shouldn't happen
|
||||||
|
return "", nil
|
||||||
|
}
|
||||||
|
buf := make([]byte, n)
|
||||||
|
_, _, err = syscall.Syscall6(syscall.SYS___SYSCTL, uintptr(unsafe.Pointer(&mib[0])), 4, uintptr(unsafe.Pointer(&buf[0])), uintptr(unsafe.Pointer(&n)), 0, 0)
|
||||||
|
if err != 0 {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
if n == 0 { // shouldn't happen
|
||||||
|
return "", nil
|
||||||
|
}
|
||||||
|
for i, v := range buf {
|
||||||
|
if v == 0 {
|
||||||
|
buf = buf[:i]
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if buf[0] != '/' {
|
||||||
|
if getwdError != nil {
|
||||||
|
return string(buf), getwdError
|
||||||
|
} else {
|
||||||
|
if buf[0] == '.' {
|
||||||
|
buf = buf[1:]
|
||||||
|
}
|
||||||
|
if startUpcwd[len(startUpcwd)-1] != '/' {
|
||||||
|
return startUpcwd + "/" + string(buf), nil
|
||||||
|
}
|
||||||
|
return startUpcwd + string(buf), nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return string(buf), nil
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,34 @@
|
||||||
|
// Copyright 2012 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package osext
|
||||||
|
|
||||||
|
import (
|
||||||
|
"syscall"
|
||||||
|
"unicode/utf16"
|
||||||
|
"unsafe"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
kernel = syscall.MustLoadDLL("kernel32.dll")
|
||||||
|
getModuleFileNameProc = kernel.MustFindProc("GetModuleFileNameW")
|
||||||
|
)
|
||||||
|
|
||||||
|
// GetModuleFileName() with hModule = NULL
|
||||||
|
func executable() (exePath string, err error) {
|
||||||
|
return getModuleFileName()
|
||||||
|
}
|
||||||
|
|
||||||
|
func getModuleFileName() (string, error) {
|
||||||
|
var n uint32
|
||||||
|
b := make([]uint16, syscall.MAX_PATH)
|
||||||
|
size := uint32(len(b))
|
||||||
|
|
||||||
|
r0, _, e1 := getModuleFileNameProc.Call(0, uintptr(unsafe.Pointer(&b[0])), uintptr(size))
|
||||||
|
n = uint32(r0)
|
||||||
|
if n == 0 {
|
||||||
|
return "", e1
|
||||||
|
}
|
||||||
|
return string(utf16.Decode(b[0:n])), nil
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,21 @@
|
||||||
|
The MIT License (MIT)
|
||||||
|
|
||||||
|
Copyright (c) 2013 Mitchell Hashimoto
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
@ -0,0 +1,101 @@
|
||||||
|
# panicwrap
|
||||||
|
|
||||||
|
panicwrap is a Go library that re-executes a Go binary and monitors stderr
|
||||||
|
output from the binary for a panic. When it find a panic, it executes a
|
||||||
|
user-defined handler function. Stdout, stderr, stdin, signals, and exit
|
||||||
|
codes continue to work as normal, making the existence of panicwrap mostly
|
||||||
|
invisble to the end user until a panic actually occurs.
|
||||||
|
|
||||||
|
Since a panic is truly a bug in the program meant to crash the runtime,
|
||||||
|
globally catching panics within Go applications is not supposed to be possible.
|
||||||
|
Despite this, it is often useful to have a way to know when panics occur.
|
||||||
|
panicwrap allows you to do something with these panics, such as writing them
|
||||||
|
to a file, so that you can track when panics occur.
|
||||||
|
|
||||||
|
panicwrap is ***not a panic recovery system***. Panics indicate serious
|
||||||
|
problems with your application and _should_ crash the runtime. panicwrap
|
||||||
|
is just meant as a way to monitor for panics. If you still think this is
|
||||||
|
the worst idea ever, read the section below on why.
|
||||||
|
|
||||||
|
## Features
|
||||||
|
|
||||||
|
* **SIMPLE!**
|
||||||
|
* Works with all Go applications on all platforms Go supports
|
||||||
|
* Custom behavior when a panic occurs
|
||||||
|
* Stdout, stderr, stdin, exit codes, and signals continue to work as
|
||||||
|
expected.
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
Using panicwrap is simple. It behaves a lot like `fork`, if you know
|
||||||
|
how that works. A basic example is shown below.
|
||||||
|
|
||||||
|
Because it would be sad to panic while capturing a panic, it is recommended
|
||||||
|
that the handler functions for panicwrap remain relatively simple and well
|
||||||
|
tested. panicwrap itself contains many tests.
|
||||||
|
|
||||||
|
```go
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"github.com/mitchellh/panicwrap"
|
||||||
|
"os"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
exitStatus, err := panicwrap.BasicWrap(panicHandler)
|
||||||
|
if err != nil {
|
||||||
|
// Something went wrong setting up the panic wrapper. Unlikely,
|
||||||
|
// but possible.
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// If exitStatus >= 0, then we're the parent process and the panicwrap
|
||||||
|
// re-executed ourselves and completed. Just exit with the proper status.
|
||||||
|
if exitStatus >= 0 {
|
||||||
|
os.Exit(exitStatus)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Otherwise, exitStatus < 0 means we're the child. Continue executing as
|
||||||
|
// normal...
|
||||||
|
|
||||||
|
// Let's say we panic
|
||||||
|
panic("oh shucks")
|
||||||
|
}
|
||||||
|
|
||||||
|
func panicHandler(output string) {
|
||||||
|
// output contains the full output (including stack traces) of the
|
||||||
|
// panic. Put it in a file or something.
|
||||||
|
fmt.Printf("The child panicked:\n\n%s\n", output)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## How Does it Work?
|
||||||
|
|
||||||
|
panicwrap works by re-executing the running program (retaining arguments,
|
||||||
|
environmental variables, etc.) and monitoring the stderr of the program.
|
||||||
|
Since Go always outputs panics in a predictable way with a predictable
|
||||||
|
exit code, panicwrap is able to reliably detect panics and allow the parent
|
||||||
|
process to handle them.
|
||||||
|
|
||||||
|
## WHY?! Panics should CRASH!
|
||||||
|
|
||||||
|
Yes, panics _should_ crash. They are 100% always indicative of bugs.
|
||||||
|
However, in some cases, such as user-facing programs (programs like
|
||||||
|
[Packer](http://github.com/mitchellh/packer) or
|
||||||
|
[Docker](http://github.com/dotcloud/docker)), it is up to the user to
|
||||||
|
report such panics. This is unreliable, at best, and it would be better if the
|
||||||
|
program could have a way to automatically report panics. panicwrap provides
|
||||||
|
a way to do this.
|
||||||
|
|
||||||
|
For backend applications, it is easier to detect crashes (since the application
|
||||||
|
exits). However, it is still nice sometimes to more intelligently log
|
||||||
|
panics in some way. For example, at [HashiCorp](http://www.hashicorp.com),
|
||||||
|
we use panicwrap to log panics to timestamped files with some additional
|
||||||
|
data (configuration settings at the time, environmental variables, etc.)
|
||||||
|
|
||||||
|
The goal of panicwrap is _not_ to hide panics. It is instead to provide
|
||||||
|
a clean mechanism for handling them before bubbling the up to the user
|
||||||
|
and ultimately crashing.
|
||||||
|
|
@ -0,0 +1,63 @@
|
||||||
|
// +build !windows
|
||||||
|
|
||||||
|
package panicwrap
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/bugsnag/osext"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"syscall"
|
||||||
|
)
|
||||||
|
|
||||||
|
func monitor(c *WrapConfig) (int, error) {
|
||||||
|
|
||||||
|
// If we're the child process, absorb panics.
|
||||||
|
if Wrapped(c) {
|
||||||
|
panicCh := make(chan string)
|
||||||
|
|
||||||
|
go trackPanic(os.Stdin, os.Stderr, c.DetectDuration, panicCh)
|
||||||
|
|
||||||
|
// Wait on the panic data
|
||||||
|
panicTxt := <-panicCh
|
||||||
|
if panicTxt != "" {
|
||||||
|
if !c.HidePanic {
|
||||||
|
os.Stderr.Write([]byte(panicTxt))
|
||||||
|
}
|
||||||
|
|
||||||
|
c.Handler(panicTxt)
|
||||||
|
}
|
||||||
|
|
||||||
|
os.Exit(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
exePath, err := osext.Executable()
|
||||||
|
if err != nil {
|
||||||
|
return -1, err
|
||||||
|
}
|
||||||
|
cmd := exec.Command(exePath, os.Args[1:]...)
|
||||||
|
|
||||||
|
read, write, err := os.Pipe()
|
||||||
|
if err != nil {
|
||||||
|
return -1, err
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd.Stdin = read
|
||||||
|
cmd.Stdout = os.Stdout
|
||||||
|
cmd.Stderr = os.Stderr
|
||||||
|
cmd.Env = append(os.Environ(), c.CookieKey+"="+c.CookieValue)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return -1, err
|
||||||
|
}
|
||||||
|
err = cmd.Start()
|
||||||
|
if err != nil {
|
||||||
|
return -1, err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = syscall.Dup2(int(write.Fd()), int(os.Stderr.Fd()))
|
||||||
|
if err != nil {
|
||||||
|
return -1, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return -1, nil
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,7 @@
|
||||||
|
package panicwrap
|
||||||
|
|
||||||
|
import "fmt"
|
||||||
|
|
||||||
|
func monitor(c *WrapConfig) (int, error) {
|
||||||
|
return -1, fmt.Errorf("Monitor is not supported on windows")
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,339 @@
|
||||||
|
// The panicwrap package provides functions for capturing and handling
|
||||||
|
// panics in your application. It does this by re-executing the running
|
||||||
|
// application and monitoring stderr for any panics. At the same time,
|
||||||
|
// stdout/stderr/etc. are set to the same values so that data is shuttled
|
||||||
|
// through properly, making the existence of panicwrap mostly transparent.
|
||||||
|
//
|
||||||
|
// Panics are only detected when the subprocess exits with a non-zero
|
||||||
|
// exit status, since this is the only time panics are real. Otherwise,
|
||||||
|
// "panic-like" output is ignored.
|
||||||
|
package panicwrap
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"errors"
|
||||||
|
"github.com/bugsnag/osext"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"os/signal"
|
||||||
|
"runtime"
|
||||||
|
"syscall"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
DEFAULT_COOKIE_KEY = "cccf35992f8f3cd8d1d28f0109dd953e26664531"
|
||||||
|
DEFAULT_COOKIE_VAL = "7c28215aca87789f95b406b8dd91aa5198406750"
|
||||||
|
)
|
||||||
|
|
||||||
|
// HandlerFunc is the type called when a panic is detected.
|
||||||
|
type HandlerFunc func(string)
|
||||||
|
|
||||||
|
// WrapConfig is the configuration for panicwrap when wrapping an existing
|
||||||
|
// binary. To get started, in general, you only need the BasicWrap function
|
||||||
|
// that will set this up for you. However, for more customizability,
|
||||||
|
// WrapConfig and Wrap can be used.
|
||||||
|
type WrapConfig struct {
|
||||||
|
// Handler is the function called when a panic occurs.
|
||||||
|
Handler HandlerFunc
|
||||||
|
|
||||||
|
// The cookie key and value are used within environmental variables
|
||||||
|
// to tell the child process that it is already executing so that
|
||||||
|
// wrap doesn't re-wrap itself.
|
||||||
|
CookieKey string
|
||||||
|
CookieValue string
|
||||||
|
|
||||||
|
// If true, the panic will not be mirrored to the configured writer
|
||||||
|
// and will instead ONLY go to the handler. This lets you effectively
|
||||||
|
// hide panics from the end user. This is not recommended because if
|
||||||
|
// your handler fails, the panic is effectively lost.
|
||||||
|
HidePanic bool
|
||||||
|
|
||||||
|
// If true, panicwrap will boot a monitor sub-process and let the parent
|
||||||
|
// run the app. This mode is useful for processes run under supervisors
|
||||||
|
// like runit as signals get sent to the correct codebase. This is not
|
||||||
|
// supported when GOOS=windows, and ignores c.Stderr and c.Stdout.
|
||||||
|
Monitor bool
|
||||||
|
|
||||||
|
// The amount of time that a process must exit within after detecting
|
||||||
|
// a panic header for panicwrap to assume it is a panic. Defaults to
|
||||||
|
// 300 milliseconds.
|
||||||
|
DetectDuration time.Duration
|
||||||
|
|
||||||
|
// The writer to send the stderr to. If this is nil, then it defaults
|
||||||
|
// to os.Stderr.
|
||||||
|
Writer io.Writer
|
||||||
|
|
||||||
|
// The writer to send stdout to. If this is nil, then it defaults to
|
||||||
|
// os.Stdout.
|
||||||
|
Stdout io.Writer
|
||||||
|
}
|
||||||
|
|
||||||
|
// BasicWrap calls Wrap with the given handler function, using defaults
|
||||||
|
// for everything else. See Wrap and WrapConfig for more information on
|
||||||
|
// functionality and return values.
|
||||||
|
func BasicWrap(f HandlerFunc) (int, error) {
|
||||||
|
return Wrap(&WrapConfig{
|
||||||
|
Handler: f,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// BasicMonitor calls Wrap with Monitor set to true on supported platforms.
|
||||||
|
// It forks your program and runs it again form the start. In one process
|
||||||
|
// BasicMonitor never returns, it just listens on stderr of the other process,
|
||||||
|
// and calls your handler when a panic is seen. In the other it either returns
|
||||||
|
// nil to indicate that the panic monitoring is enabled, or an error to indicate
|
||||||
|
// that something else went wrong.
|
||||||
|
func BasicMonitor(f HandlerFunc) error {
|
||||||
|
exitStatus, err := Wrap(&WrapConfig{
|
||||||
|
Handler: f,
|
||||||
|
Monitor: runtime.GOOS != "windows",
|
||||||
|
})
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if exitStatus >= 0 {
|
||||||
|
os.Exit(exitStatus)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wrap wraps the current executable in a handler to catch panics. It
|
||||||
|
// returns an error if there was an error during the wrapping process.
|
||||||
|
// If the error is nil, then the int result indicates the exit status of the
|
||||||
|
// child process. If the exit status is -1, then this is the child process,
|
||||||
|
// and execution should continue as normal. Otherwise, this is the parent
|
||||||
|
// process and the child successfully ran already, and you should exit the
|
||||||
|
// process with the returned exit status.
|
||||||
|
//
|
||||||
|
// This function should be called very very early in your program's execution.
|
||||||
|
// Ideally, this runs as the first line of code of main.
|
||||||
|
//
|
||||||
|
// Once this is called, the given WrapConfig shouldn't be modified or used
|
||||||
|
// any further.
|
||||||
|
func Wrap(c *WrapConfig) (int, error) {
|
||||||
|
if c.Handler == nil {
|
||||||
|
return -1, errors.New("Handler must be set")
|
||||||
|
}
|
||||||
|
|
||||||
|
if c.DetectDuration == 0 {
|
||||||
|
c.DetectDuration = 300 * time.Millisecond
|
||||||
|
}
|
||||||
|
|
||||||
|
if c.Writer == nil {
|
||||||
|
c.Writer = os.Stderr
|
||||||
|
}
|
||||||
|
|
||||||
|
if c.Monitor {
|
||||||
|
return monitor(c)
|
||||||
|
} else {
|
||||||
|
return wrap(c)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func wrap(c *WrapConfig) (int, error) {
|
||||||
|
|
||||||
|
// If we're already wrapped, exit out.
|
||||||
|
if Wrapped(c) {
|
||||||
|
return -1, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the path to our current executable
|
||||||
|
exePath, err := osext.Executable()
|
||||||
|
if err != nil {
|
||||||
|
return -1, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pipe the stderr so we can read all the data as we look for panics
|
||||||
|
stderr_r, stderr_w := io.Pipe()
|
||||||
|
|
||||||
|
// doneCh is closed when we're done, signaling any other goroutines
|
||||||
|
// to end immediately.
|
||||||
|
doneCh := make(chan struct{})
|
||||||
|
|
||||||
|
// panicCh is the channel on which the panic text will actually be
|
||||||
|
// sent.
|
||||||
|
panicCh := make(chan string)
|
||||||
|
|
||||||
|
// On close, make sure to finish off the copying of data to stderr
|
||||||
|
defer func() {
|
||||||
|
defer close(doneCh)
|
||||||
|
stderr_w.Close()
|
||||||
|
<-panicCh
|
||||||
|
}()
|
||||||
|
|
||||||
|
// Start the goroutine that will watch stderr for any panics
|
||||||
|
go trackPanic(stderr_r, c.Writer, c.DetectDuration, panicCh)
|
||||||
|
|
||||||
|
// Create the writer for stdout that we're going to use
|
||||||
|
var stdout_w io.Writer = os.Stdout
|
||||||
|
if c.Stdout != nil {
|
||||||
|
stdout_w = c.Stdout
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build a subcommand to re-execute ourselves. We make sure to
|
||||||
|
// set the environmental variable to include our cookie. We also
|
||||||
|
// set stdin/stdout to match the config. Finally, we pipe stderr
|
||||||
|
// through ourselves in order to watch for panics.
|
||||||
|
cmd := exec.Command(exePath, os.Args[1:]...)
|
||||||
|
cmd.Env = append(os.Environ(), c.CookieKey+"="+c.CookieValue)
|
||||||
|
cmd.Stdin = os.Stdin
|
||||||
|
cmd.Stdout = stdout_w
|
||||||
|
cmd.Stderr = stderr_w
|
||||||
|
if err := cmd.Start(); err != nil {
|
||||||
|
return 1, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Listen to signals and capture them forever. We allow the child
|
||||||
|
// process to handle them in some way.
|
||||||
|
sigCh := make(chan os.Signal)
|
||||||
|
signal.Notify(sigCh, os.Interrupt)
|
||||||
|
go func() {
|
||||||
|
defer signal.Stop(sigCh)
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-doneCh:
|
||||||
|
return
|
||||||
|
case <-sigCh:
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
if err := cmd.Wait(); err != nil {
|
||||||
|
exitErr, ok := err.(*exec.ExitError)
|
||||||
|
if !ok {
|
||||||
|
// This is some other kind of subprocessing error.
|
||||||
|
return 1, err
|
||||||
|
}
|
||||||
|
|
||||||
|
exitStatus := 1
|
||||||
|
if status, ok := exitErr.Sys().(syscall.WaitStatus); ok {
|
||||||
|
exitStatus = status.ExitStatus()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close the writer end so that the tracker goroutine ends at some point
|
||||||
|
stderr_w.Close()
|
||||||
|
|
||||||
|
// Wait on the panic data
|
||||||
|
panicTxt := <-panicCh
|
||||||
|
if panicTxt != "" {
|
||||||
|
if !c.HidePanic {
|
||||||
|
c.Writer.Write([]byte(panicTxt))
|
||||||
|
}
|
||||||
|
|
||||||
|
c.Handler(panicTxt)
|
||||||
|
}
|
||||||
|
|
||||||
|
return exitStatus, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wrapped checks if we're already wrapped according to the configuration
|
||||||
|
// given.
|
||||||
|
//
|
||||||
|
// Wrapped is very cheap and can be used early to short-circuit some pre-wrap
|
||||||
|
// logic your application may have.
|
||||||
|
func Wrapped(c *WrapConfig) bool {
|
||||||
|
if c.CookieKey == "" {
|
||||||
|
c.CookieKey = DEFAULT_COOKIE_KEY
|
||||||
|
}
|
||||||
|
|
||||||
|
if c.CookieValue == "" {
|
||||||
|
c.CookieValue = DEFAULT_COOKIE_VAL
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the cookie key/value match our environment, then we are the
|
||||||
|
// child, so just exit now and tell the caller that we're the child
|
||||||
|
return os.Getenv(c.CookieKey) == c.CookieValue
|
||||||
|
}
|
||||||
|
|
||||||
|
// trackPanic monitors the given reader for a panic. If a panic is detected,
|
||||||
|
// it is outputted on the result channel. This will close the channel once
|
||||||
|
// it is complete.
|
||||||
|
func trackPanic(r io.Reader, w io.Writer, dur time.Duration, result chan<- string) {
|
||||||
|
defer close(result)
|
||||||
|
|
||||||
|
var panicTimer <-chan time.Time
|
||||||
|
panicBuf := new(bytes.Buffer)
|
||||||
|
panicHeader := []byte("panic:")
|
||||||
|
|
||||||
|
tempBuf := make([]byte, 2048)
|
||||||
|
for {
|
||||||
|
var buf []byte
|
||||||
|
var n int
|
||||||
|
|
||||||
|
if panicTimer == nil && panicBuf.Len() > 0 {
|
||||||
|
// We're not tracking a panic but the buffer length is
|
||||||
|
// greater than 0. We need to clear out that buffer, but
|
||||||
|
// look for another panic along the way.
|
||||||
|
|
||||||
|
// First, remove the previous panic header so we don't loop
|
||||||
|
w.Write(panicBuf.Next(len(panicHeader)))
|
||||||
|
|
||||||
|
// Next, assume that this is our new buffer to inspect
|
||||||
|
n = panicBuf.Len()
|
||||||
|
buf = make([]byte, n)
|
||||||
|
copy(buf, panicBuf.Bytes())
|
||||||
|
panicBuf.Reset()
|
||||||
|
} else {
|
||||||
|
var err error
|
||||||
|
buf = tempBuf
|
||||||
|
n, err = r.Read(buf)
|
||||||
|
if n <= 0 && err == io.EOF {
|
||||||
|
if panicBuf.Len() > 0 {
|
||||||
|
// We were tracking a panic, assume it was a panic
|
||||||
|
// and return that as the result.
|
||||||
|
result <- panicBuf.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if panicTimer != nil {
|
||||||
|
// We're tracking what we think is a panic right now.
|
||||||
|
// If the timer ended, then it is not a panic.
|
||||||
|
isPanic := true
|
||||||
|
select {
|
||||||
|
case <-panicTimer:
|
||||||
|
isPanic = false
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
|
||||||
|
// No matter what, buffer the text some more.
|
||||||
|
panicBuf.Write(buf[0:n])
|
||||||
|
|
||||||
|
if !isPanic {
|
||||||
|
// It isn't a panic, stop tracking. Clean-up will happen
|
||||||
|
// on the next iteration.
|
||||||
|
panicTimer = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
flushIdx := n
|
||||||
|
idx := bytes.Index(buf[0:n], panicHeader)
|
||||||
|
if idx >= 0 {
|
||||||
|
flushIdx = idx
|
||||||
|
}
|
||||||
|
|
||||||
|
// Flush to stderr what isn't a panic
|
||||||
|
w.Write(buf[0:flushIdx])
|
||||||
|
|
||||||
|
if idx < 0 {
|
||||||
|
// Not a panic so just continue along
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// We have a panic header. Write we assume is a panic os far.
|
||||||
|
panicBuf.Write(buf[idx:n])
|
||||||
|
panicTimer = time.After(dur)
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue