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/libmachine",
|
||||
"github.com/docker/machine/libmachine/auth",
|
||||
"github.com/docker/machine/libmachine/bugsnag",
|
||||
"github.com/docker/machine/libmachine/cert",
|
||||
"github.com/docker/machine/libmachine/check",
|
||||
"github.com/docker/machine/libmachine/drivers",
|
||||
"github.com/docker/machine/libmachine/drivers/plugin",
|
||||
"github.com/docker/machine/libmachine/drivers/plugin/localbinary",
|
||||
|
|
@ -39,6 +41,7 @@
|
|||
"github.com/docker/machine/libmachine/hosttest",
|
||||
"github.com/docker/machine/libmachine/libmachinetest",
|
||||
"github.com/docker/machine/libmachine/log",
|
||||
"github.com/docker/machine/libmachine/mcndockerclient",
|
||||
"github.com/docker/machine/libmachine/mcnerror",
|
||||
"github.com/docker/machine/libmachine/mcnflag",
|
||||
"github.com/docker/machine/libmachine/mcnutils",
|
||||
|
|
@ -56,9 +59,22 @@
|
|||
],
|
||||
"Deps": [
|
||||
{
|
||||
"ImportPath": "github.com/Sirupsen/logrus",
|
||||
"Comment": "v0.8.7-49-gcdaedc6",
|
||||
"Rev": "cdaedc68f2894175ac2b3221869685602c759e71"
|
||||
"ImportPath": "github.com/Sirupsen/logrus",
|
||||
"Comment": "v0.8.7-49-gcdaedc6",
|
||||
"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",
|
||||
|
|
|
|||
|
|
@ -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