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