Merge pull request #4000 from DavidSpek/remove-bugsnag

Remove bugsnag
This commit is contained in:
Milos Gajdos 2023-08-23 15:21:18 +01:00 committed by GitHub
commit 4f7424c8eb
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
43 changed files with 1 additions and 2992 deletions

View File

@ -62,9 +62,6 @@ type Configuration struct {
// Middleware lists all middlewares to be used by the registry.
Middleware map[string][]Middleware `yaml:"middleware,omitempty"`
// Reporting is the configuration for error reporting
Reporting Reporting `yaml:"reporting,omitempty"`
// HTTP contains configuration parameters for the registry's http
// interface.
HTTP struct {
@ -627,23 +624,6 @@ type Ignore struct {
Actions []string `yaml:"actions"` // ignore action types
}
// Reporting defines error reporting methods.
type Reporting struct {
// Bugsnag configures error reporting for Bugsnag (bugsnag.com).
Bugsnag BugsnagReporting `yaml:"bugsnag,omitempty"`
}
// BugsnagReporting configures error reporting for Bugsnag (bugsnag.com).
type BugsnagReporting struct {
// APIKey is the Bugsnag api key.
APIKey string `yaml:"apikey,omitempty"`
// ReleaseStage tracks where the registry is deployed.
// Examples: production, staging, development
ReleaseStage string `yaml:"releasestage,omitempty"`
// Endpoint is used for specifying an enterprise Bugsnag endpoint.
Endpoint string `yaml:"endpoint,omitempty"`
}
// Middleware configures named middlewares to be applied at injection points.
type Middleware struct {
// Name the middleware registers itself as

View File

@ -50,11 +50,6 @@ var configStruct = Configuration{
"service": "silly",
},
},
Reporting: Reporting{
Bugsnag: BugsnagReporting{
APIKey: "BugsnagApiKey",
},
},
Notifications: Notifications{
Endpoints: []Endpoint{
{
@ -201,9 +196,6 @@ notifications:
- application/octet-stream
actions:
- pull
reporting:
bugsnag:
apikey: BugsnagApiKey
http:
clientcas:
- /path/to/ca.pem
@ -286,7 +278,6 @@ func (suite *ConfigSuite) TestParseSimple(c *check.C) {
// a string can be parsed into a Configuration struct with no storage parameters
func (suite *ConfigSuite) TestParseInmemory(c *check.C) {
suite.expectedConfig.Storage = Storage{"inmemory": Parameters{}}
suite.expectedConfig.Reporting = Reporting{}
suite.expectedConfig.Log.Fields = nil
suite.expectedConfig.Redis = struct {
Addr string `yaml:"addr,omitempty"`
@ -322,7 +313,6 @@ func (suite *ConfigSuite) TestParseIncomplete(c *check.C) {
suite.expectedConfig.Log.Fields = nil
suite.expectedConfig.Storage = Storage{"filesystem": Parameters{"rootdirectory": "/tmp/testroot"}}
suite.expectedConfig.Auth = Auth{"silly": Parameters{"realm": "silly"}}
suite.expectedConfig.Reporting = Reporting{}
suite.expectedConfig.Notifications = Notifications{}
suite.expectedConfig.HTTP.Headers = nil
suite.expectedConfig.Redis = struct {
@ -448,20 +438,6 @@ func (suite *ConfigSuite) TestParseInvalidLoglevel(c *check.C) {
c.Assert(err, check.NotNil)
}
// TestParseWithDifferentEnvReporting validates that environment variables
// properly override reporting parameters
func (suite *ConfigSuite) TestParseWithDifferentEnvReporting(c *check.C) {
suite.expectedConfig.Reporting.Bugsnag.APIKey = "anotherBugsnagApiKey"
suite.expectedConfig.Reporting.Bugsnag.Endpoint = "localhost:8080"
os.Setenv("REGISTRY_REPORTING_BUGSNAG_APIKEY", "anotherBugsnagApiKey")
os.Setenv("REGISTRY_REPORTING_BUGSNAG_ENDPOINT", "localhost:8080")
config, err := Parse(bytes.NewReader([]byte(configYamlV0_1)))
c.Assert(err, check.IsNil)
c.Assert(config, check.DeepEquals, suite.expectedConfig)
}
// TestParseInvalidVersion validates that the parser will fail to parse a newer configuration
// version than the CurrentVersion
func (suite *ConfigSuite) TestParseInvalidVersion(c *check.C) {
@ -475,10 +451,6 @@ func (suite *ConfigSuite) TestParseInvalidVersion(c *check.C) {
// TestParseExtraneousVars validates that environment variables referring to
// nonexistent variables don't cause side effects.
func (suite *ConfigSuite) TestParseExtraneousVars(c *check.C) {
suite.expectedConfig.Reporting.Bugsnag.Endpoint = "localhost:8080"
// A valid environment variable
os.Setenv("REGISTRY_REPORTING_BUGSNAG_ENDPOINT", "localhost:8080")
// Environment variables which shouldn't set config items
os.Setenv("REGISTRY_DUCKS", "quack")
@ -611,9 +583,6 @@ func copyConfig(config Configuration) *Configuration {
for k, v := range config.Storage.Parameters() {
configCopy.Storage.setParameter(k, v)
}
configCopy.Reporting = Reporting{
Bugsnag: BugsnagReporting{config.Reporting.Bugsnag.APIKey, config.Reporting.Bugsnag.ReleaseStage, config.Reporting.Bugsnag.Endpoint},
}
configCopy.Auth = Auth{config.Auth.Type(): Parameters{}}
for k, v := range config.Auth.Parameters() {

View File

@ -192,11 +192,6 @@ middleware:
- name: redirect
options:
baseurl: https://example.com/
reporting:
bugsnag:
apikey: bugsnagapikey
releasestage: bugsnagreleasestage
endpoint: bugsnagendpoint
http:
addr: localhost:5000
prefix: /my/nested/registry/
@ -699,31 +694,6 @@ location of a proxy for the layer stored by the S3 storage driver.
|-----------|----------|-------------------------------------------------------------------------------------------------------------|
| `baseurl` | yes | `SCHEME://HOST` at which layers are served. Can also contain port. For example, `https://example.com:5443`. |
## `reporting`
```
reporting:
bugsnag:
apikey: bugsnagapikey
releasestage: bugsnagreleasestage
endpoint: bugsnagendpoint
```
The `reporting` option is **optional** and configures error and metrics
reporting tools. At the moment only two services are supported:
- [Bugsnag](#bugsnag)
A valid configuration may contain both.
### `bugsnag`
| Parameter | Required | Description |
|-----------|----------|-------------------------------------------------------|
| `apikey` | yes | The API Key provided by Bugsnag. |
| `releasestage` | no | Tracks where the registry is deployed, using a string like `production`, `staging`, or `development`.|
| `endpoint`| no | The enterprise Bugsnag endpoint. |
## `http`
```none

6
go.mod
View File

@ -8,10 +8,8 @@ require (
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.6.0
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.3.0
github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.0.0
github.com/Shopify/logrus-bugsnag v0.0.0-20171204204709-577dee27f20d
github.com/aws/aws-sdk-go v1.44.325
github.com/bshuster-repo/logrus-logstash-hook v1.0.0
github.com/bugsnag/bugsnag-go v0.0.0-20141110184014-b1d153021fcd
github.com/docker/go-events v0.0.0-20190806004212-e31b211e4f1c
github.com/docker/go-metrics v0.0.1
github.com/docker/libtrust v0.0.0-20150114040149-fa567046d9b1
@ -40,9 +38,6 @@ require (
github.com/Azure/azure-sdk-for-go/sdk/internal v1.3.0 // indirect
github.com/AzureAD/microsoft-authentication-library-for-go v1.0.0 // indirect
github.com/beorn7/perks v1.0.1 // indirect
github.com/bitly/go-simplejson v0.5.0 // indirect
github.com/bugsnag/osext v0.0.0-20130617224835-0dd3f918b21b // indirect
github.com/bugsnag/panicwrap v0.0.0-20151223152923-e2c28503fcd0 // indirect
github.com/cespare/xxhash/v2 v2.2.0 // indirect
github.com/cyphar/filepath-securejoin v0.2.3 // indirect
github.com/felixge/httpsnoop v1.0.1 // indirect
@ -60,7 +55,6 @@ require (
github.com/kr/text v0.2.0 // indirect
github.com/kylelemons/godebug v1.1.0 // indirect
github.com/matttproud/golang_protobuf_extensions v1.0.1 // indirect
github.com/mitchellh/osext v0.0.0-20151018003038-5e2d6d41470f // indirect
github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8 // indirect
github.com/prometheus/client_golang v1.12.1 // indirect; updated to latest
github.com/prometheus/client_model v0.2.0 // indirect

12
go.sum
View File

@ -56,8 +56,6 @@ github.com/AzureAD/microsoft-authentication-library-for-go v1.0.0 h1:OBhqkivkhkM
github.com/AzureAD/microsoft-authentication-library-for-go v1.0.0/go.mod h1:kgDmCTgBzIEPFElEF+FK0SdjAor06dRq2Go927dnQ6o=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
github.com/Shopify/logrus-bugsnag v0.0.0-20171204204709-577dee27f20d h1:UrqY+r/OJnIp5u0s1SbQ8dVfLCZJsnvazdBP5hS4iRs=
github.com/Shopify/logrus-bugsnag v0.0.0-20171204204709-577dee27f20d/go.mod h1:HI8ITrYtUY+O+ZhtlqUnD8+KwNPOyugEhfP9fdUIaEQ=
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
@ -69,16 +67,8 @@ github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
github.com/bitly/go-simplejson v0.5.0 h1:6IH+V8/tVMab511d5bn4M7EwGXZf9Hj6i2xSwkNEM+Y=
github.com/bitly/go-simplejson v0.5.0/go.mod h1:cXHtHw4XUPsvGaxgjIAn8PhEWG9NfngEKAMDJEczWVA=
github.com/bshuster-repo/logrus-logstash-hook v1.0.0 h1:e+C0SB5R1pu//O4MQ3f9cFuPGoOVeF2fE4Og9otCc70=
github.com/bshuster-repo/logrus-logstash-hook v1.0.0/go.mod h1:zsTqEiSzDgAa/8GZR7E1qaXrhYNDKBYy5/dWPTIflbk=
github.com/bugsnag/bugsnag-go v0.0.0-20141110184014-b1d153021fcd h1:rFt+Y/IK1aEZkEHchZRSq9OQbsSzIT/OrI8YFFmRIng=
github.com/bugsnag/bugsnag-go v0.0.0-20141110184014-b1d153021fcd/go.mod h1:2oa8nejYd4cQ/b0hMIopN0lCRxU0bueqREvZLWFrtK8=
github.com/bugsnag/osext v0.0.0-20130617224835-0dd3f918b21b h1:otBG+dV+YK+Soembjv71DPz3uX/V/6MMlSyD9JBQ6kQ=
github.com/bugsnag/osext v0.0.0-20130617224835-0dd3f918b21b/go.mod h1:obH5gd0BsqsP2LwDJ9aOkm/6J86V6lyAXCoQWGw3K50=
github.com/bugsnag/panicwrap v0.0.0-20151223152923-e2c28503fcd0 h1:nvj0OLI3YqYXer/kZD8Ri1aaunCxIEsOst1BVJswV0o=
github.com/bugsnag/panicwrap v0.0.0-20151223152923-e2c28503fcd0/go.mod h1:D/8v3kj0zr8ZAKg1AQ6crr+5VwKN5eIywRkfhyM/+dE=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
@ -235,8 +225,6 @@ github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0j
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE=
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
github.com/mitchellh/osext v0.0.0-20151018003038-5e2d6d41470f h1:2+myh5ml7lgEU/51gbeLHfKGNfgEQQIWrlbdaOsidbQ=
github.com/mitchellh/osext v0.0.0-20151018003038-5e2d6d41470f/go.mod h1:OkQIRizQZAeMln+1tSwduZz7+Af5oFlKirV/MSYes2A=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=

View File

@ -12,10 +12,7 @@ import (
"syscall"
"time"
logrus_bugsnag "github.com/Shopify/logrus-bugsnag"
logstash "github.com/bshuster-repo/logrus-logstash-hook"
"github.com/bugsnag/bugsnag-go"
"github.com/docker/go-metrics"
gorhandlers "github.com/gorilla/handlers"
"github.com/sirupsen/logrus"
@ -139,8 +136,6 @@ func NewRegistry(ctx context.Context, config *configuration.Configuration) (*Reg
return nil, fmt.Errorf("error configuring logger: %v", err)
}
configureBugsnag(config)
// inject a logger into the uuid library. warns us if there is a problem
// with uuid generation under low entropy.
uuid.Loggerf = dcontext.GetLogger(ctx).Warnf
@ -149,7 +144,7 @@ func NewRegistry(ctx context.Context, config *configuration.Configuration) (*Reg
// TODO(aaronl): The global scope of the health checks means NewRegistry
// can only be called once per process.
app.RegisterHealthChecks()
handler := configureReporting(app)
var handler http.Handler = app
handler = alive("/", handler)
handler = health.Handler(handler)
handler = panicHandler(handler)
@ -345,16 +340,6 @@ func configurePrometheus(config *configuration.Configuration) {
}
}
func configureReporting(app *handlers.App) http.Handler {
var handler http.Handler = app
if app.Config.Reporting.Bugsnag.APIKey != "" {
handler = bugsnag.Handler(handler)
}
return handler
}
// configureLogging prepares the context with a logger using the
// configuration.
func configureLogging(ctx context.Context, config *configuration.Configuration) (context.Context, error) {
@ -410,32 +395,6 @@ func logLevel(level configuration.Loglevel) logrus.Level {
return l
}
// configureBugsnag configures bugsnag reporting, if enabled
func configureBugsnag(config *configuration.Configuration) {
if config.Reporting.Bugsnag.APIKey == "" {
return
}
bugsnagConfig := bugsnag.Configuration{
APIKey: config.Reporting.Bugsnag.APIKey,
}
if config.Reporting.Bugsnag.ReleaseStage != "" {
bugsnagConfig.ReleaseStage = config.Reporting.Bugsnag.ReleaseStage
}
if config.Reporting.Bugsnag.Endpoint != "" {
bugsnagConfig.Endpoint = config.Reporting.Bugsnag.Endpoint
}
bugsnag.Configure(bugsnagConfig)
// configure logrus bugsnag hook
hook, err := logrus_bugsnag.NewBugsnagHook()
if err != nil {
logrus.Fatalln(err)
}
logrus.AddHook(hook)
}
// panicHandler add an HTTP handler to web app. The handler recover the happening
// panic. logrus.Panic transmits panic message to pre-config log hooks, which is
// defined in config.yml.

View File

@ -1,4 +0,0 @@
language: go
go:
- 1.7

View File

@ -1,21 +0,0 @@
The MIT License (MIT)
Copyright (c) 2016 Shopify
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.

View File

@ -1,24 +0,0 @@
## logrus-bugsnag
[![Build Status](https://travis-ci.org/Shopify/logrus-bugsnag.svg)](https://travis-ci.org/Shopify/logrus-bugsnag)
logrus-bugsnag is a hook that allows [Logrus](https://github.com/sirupsen/logrus) to interface with [Bugsnag](https://bugsnag.com).
#### Usage
```go
import (
log "github.com/sirupsen/logrus"
"github.com/Shopify/logrus-bugsnag"
bugsnag "github.com/bugsnag/bugsnag-go"
)
func init() {
bugsnag.Configure(bugsnag.Configuration{
APIKey: apiKey,
})
hook, err := logrus_bugsnag.NewBugsnagHook()
logrus.StandardLogger().Hooks.Add(hook)
}
```

View File

@ -1,81 +0,0 @@
package logrus_bugsnag
import (
"errors"
"github.com/sirupsen/logrus"
"github.com/bugsnag/bugsnag-go"
bugsnag_errors "github.com/bugsnag/bugsnag-go/errors"
)
type bugsnagHook struct{}
// ErrBugsnagUnconfigured is returned if NewBugsnagHook is called before
// bugsnag.Configure. Bugsnag must be configured before the hook.
var ErrBugsnagUnconfigured = errors.New("bugsnag must be configured before installing this logrus hook")
// ErrBugsnagSendFailed indicates that the hook failed to submit an error to
// bugsnag. The error was successfully generated, but `bugsnag.Notify()`
// failed.
type ErrBugsnagSendFailed struct {
err error
}
func (e ErrBugsnagSendFailed) Error() string {
return "failed to send error to Bugsnag: " + e.err.Error()
}
// NewBugsnagHook initializes a logrus hook which sends exceptions to an
// exception-tracking service compatible with the Bugsnag API. Before using
// this hook, you must call bugsnag.Configure(). The returned object should be
// registered with a log via `AddHook()`
//
// Entries that trigger an Error, Fatal or Panic should now include an "error"
// field to send to Bugsnag.
func NewBugsnagHook() (*bugsnagHook, error) {
if bugsnag.Config.APIKey == "" {
return nil, ErrBugsnagUnconfigured
}
return &bugsnagHook{}, nil
}
// skipStackFrames skips logrus stack frames before logging to Bugsnag.
const skipStackFrames = 4
// Fire forwards an error to Bugsnag. Given a logrus.Entry, it extracts the
// "error" field (or the Message if the error isn't present) and sends it off.
func (hook *bugsnagHook) Fire(entry *logrus.Entry) error {
var notifyErr error
err, ok := entry.Data["error"].(error)
if ok {
notifyErr = err
} else {
notifyErr = errors.New(entry.Message)
}
metadata := bugsnag.MetaData{}
metadata["metadata"] = make(map[string]interface{})
for key, val := range entry.Data {
if key != "error" {
metadata["metadata"][key] = val
}
}
errWithStack := bugsnag_errors.New(notifyErr, skipStackFrames)
bugsnagErr := bugsnag.Notify(errWithStack, metadata)
if bugsnagErr != nil {
return ErrBugsnagSendFailed{bugsnagErr}
}
return nil
}
// Levels enumerates the log levels on which the error should be forwarded to
// bugsnag: everything at or above the "Error" level.
func (hook *bugsnagHook) Levels() []logrus.Level {
return []logrus.Level{
logrus.ErrorLevel,
logrus.FatalLevel,
logrus.PanicLevel,
}
}

View File

@ -1,8 +0,0 @@
name: logrus-bugsnag
up:
- go: 1.6.2
commands:
test:
run: go get -t ./... && go test ./...

View File

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

View File

@ -1,20 +0,0 @@
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.

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,6 +0,0 @@
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.

View File

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

View File

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

View File

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

View File

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

View File

@ -1,43 +0,0 @@
// 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
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,20 +0,0 @@
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.

View File

@ -1,32 +0,0 @@
// 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()
}

View File

@ -1,16 +0,0 @@
// 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()))
}

View File

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

View File

@ -1,64 +0,0 @@
// 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
}

View File

@ -1,34 +0,0 @@
// 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
}

View File

@ -1,21 +0,0 @@
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.

View File

@ -1,101 +0,0 @@
# 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.

View File

@ -1,11 +0,0 @@
// +build darwin dragonfly freebsd linux,!arm64 netbsd openbsd
package panicwrap
import (
"syscall"
)
func dup2(oldfd, newfd int) error {
return syscall.Dup2(oldfd, newfd)
}

View File

@ -1,11 +0,0 @@
// +build linux,arm64
package panicwrap
import (
"syscall"
)
func dup2(oldfd, newfd int) error {
return syscall.Dup3(oldfd, newfd, 0)
}

View File

@ -1,62 +0,0 @@
// +build !windows
package panicwrap
import (
"github.com/bugsnag/osext"
"os"
"os/exec"
)
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 = dup2(int(write.Fd()), int(os.Stderr.Fd()))
if err != nil {
return -1, err
}
return -1, nil
}

View File

@ -1,7 +0,0 @@
package panicwrap
import "fmt"
func monitor(c *WrapConfig) (int, error) {
return -1, fmt.Errorf("Monitor is not supported on windows")
}

View File

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

17
vendor/modules.txt vendored
View File

@ -91,9 +91,6 @@ github.com/AzureAD/microsoft-authentication-library-for-go/apps/internal/options
github.com/AzureAD/microsoft-authentication-library-for-go/apps/internal/shared
github.com/AzureAD/microsoft-authentication-library-for-go/apps/internal/version
github.com/AzureAD/microsoft-authentication-library-for-go/apps/public
# github.com/Shopify/logrus-bugsnag v0.0.0-20171204204709-577dee27f20d
## explicit
github.com/Shopify/logrus-bugsnag
# github.com/aws/aws-sdk-go v1.44.325
## explicit; go 1.11
github.com/aws/aws-sdk-go/aws
@ -151,21 +148,9 @@ github.com/aws/aws-sdk-go/service/sts/stsiface
# github.com/beorn7/perks v1.0.1
## explicit; go 1.11
github.com/beorn7/perks/quantile
# github.com/bitly/go-simplejson v0.5.0
## explicit
# github.com/bshuster-repo/logrus-logstash-hook v1.0.0
## explicit
github.com/bshuster-repo/logrus-logstash-hook
# github.com/bugsnag/bugsnag-go v0.0.0-20141110184014-b1d153021fcd
## explicit
github.com/bugsnag/bugsnag-go
github.com/bugsnag/bugsnag-go/errors
# github.com/bugsnag/osext v0.0.0-20130617224835-0dd3f918b21b
## explicit
github.com/bugsnag/osext
# github.com/bugsnag/panicwrap v0.0.0-20151223152923-e2c28503fcd0
## explicit
github.com/bugsnag/panicwrap
# github.com/cespare/xxhash/v2 v2.2.0
## explicit; go 1.11
github.com/cespare/xxhash/v2
@ -265,8 +250,6 @@ github.com/matttproud/golang_protobuf_extensions/pbutil
# github.com/mitchellh/mapstructure v1.1.2
## explicit
github.com/mitchellh/mapstructure
# github.com/mitchellh/osext v0.0.0-20151018003038-5e2d6d41470f
## explicit
# github.com/opencontainers/go-digest v1.0.0
## explicit; go 1.13
github.com/opencontainers/go-digest