add bugsnag logrus hook
Signed-off-by: Matt Tescher <matthew.tescher@docker.com>master
							parent
							
								
									93e082742a
								
							
						
					
					
						commit
						7c4d584e58
					
				|  | @ -14,6 +14,7 @@ import ( | |||
| 
 | ||||
| 	"rsc.io/letsencrypt" | ||||
| 
 | ||||
| 	"github.com/Shopify/logrus-bugsnag" | ||||
| 	logstash "github.com/bshuster-repo/logrus-logstash-hook" | ||||
| 	"github.com/bugsnag/bugsnag-go" | ||||
| 	"github.com/docker/distribution/configuration" | ||||
|  | @ -95,6 +96,8 @@ 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 | ||||
|  | @ -229,19 +232,6 @@ func configureReporting(app *handlers.App) http.Handler { | |||
| 	var handler http.Handler = app | ||||
| 
 | ||||
| 	if app.Config.Reporting.Bugsnag.APIKey != "" { | ||||
| 		bugsnagConfig := bugsnag.Configuration{ | ||||
| 			APIKey: app.Config.Reporting.Bugsnag.APIKey, | ||||
| 			// TODO(brianbland): provide the registry version here
 | ||||
| 			// AppVersion: "2.0",
 | ||||
| 		} | ||||
| 		if app.Config.Reporting.Bugsnag.ReleaseStage != "" { | ||||
| 			bugsnagConfig.ReleaseStage = app.Config.Reporting.Bugsnag.ReleaseStage | ||||
| 		} | ||||
| 		if app.Config.Reporting.Bugsnag.Endpoint != "" { | ||||
| 			bugsnagConfig.Endpoint = app.Config.Reporting.Bugsnag.Endpoint | ||||
| 		} | ||||
| 		bugsnag.Configure(bugsnagConfig) | ||||
| 
 | ||||
| 		handler = bugsnag.Handler(handler) | ||||
| 	} | ||||
| 
 | ||||
|  | @ -319,6 +309,32 @@ func logLevel(level configuration.Loglevel) log.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 { | ||||
| 		log.Fatalln(err) | ||||
| 	} | ||||
| 
 | ||||
| 	log.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.
 | ||||
|  |  | |||
|  | @ -28,6 +28,7 @@ github.com/prometheus/client_golang c332b6f63c0658a65eca15c0e5247ded801cf564 | |||
| github.com/prometheus/client_model 99fa1f4be8e564e8a6b613da7fa6f46c9edafc6c | ||||
| github.com/prometheus/common 89604d197083d4781071d3c65855d24ecfb0a563 | ||||
| github.com/prometheus/procfs cb4147076ac75738c9a7d279075a253c0cc5acbd | ||||
| github.com/Shopify/logrus-bugsnag 577dee27f20dd8f1a529f82210094af593be12bd | ||||
| github.com/spf13/cobra 312092086bed4968099259622145a0c9ae280064 | ||||
| github.com/spf13/pflag 5644820622454e71517561946e3d94b9f9db6842 | ||||
| github.com/xenolf/lego a9d8cec0e6563575e5868a005359ac97911b5985 | ||||
|  |  | |||
|  | @ -0,0 +1,21 @@ | |||
| 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. | ||||
|  | @ -0,0 +1,24 @@ | |||
| ## logrus-bugsnag | ||||
| 
 | ||||
| [](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) | ||||
| } | ||||
| ``` | ||||
| 
 | ||||
|  | @ -0,0 +1,81 @@ | |||
| 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, | ||||
| 	} | ||||
| } | ||||
		Loading…
	
		Reference in New Issue