From 7c4d584e58eeb0195c7478353a1544d0a288ee49 Mon Sep 17 00:00:00 2001 From: Matt Tescher Date: Thu, 25 Oct 2018 14:38:26 -0700 Subject: [PATCH] add bugsnag logrus hook Signed-off-by: Matt Tescher --- registry/registry.go | 42 +++++++--- vendor.conf | 1 + .../github.com/Shopify/logrus-bugsnag/LICENSE | 21 +++++ .../Shopify/logrus-bugsnag/README.md | 24 ++++++ .../Shopify/logrus-bugsnag/bugsnag.go | 81 +++++++++++++++++++ 5 files changed, 156 insertions(+), 13 deletions(-) create mode 100644 vendor/github.com/Shopify/logrus-bugsnag/LICENSE create mode 100644 vendor/github.com/Shopify/logrus-bugsnag/README.md create mode 100644 vendor/github.com/Shopify/logrus-bugsnag/bugsnag.go diff --git a/registry/registry.go b/registry/registry.go index 44c0edf5a53..18698f5bf7f 100644 --- a/registry/registry.go +++ b/registry/registry.go @@ -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. diff --git a/vendor.conf b/vendor.conf index 7aec1d05b79..a249caf269d 100644 --- a/vendor.conf +++ b/vendor.conf @@ -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 diff --git a/vendor/github.com/Shopify/logrus-bugsnag/LICENSE b/vendor/github.com/Shopify/logrus-bugsnag/LICENSE new file mode 100644 index 00000000000..a3f09f7fcf4 --- /dev/null +++ b/vendor/github.com/Shopify/logrus-bugsnag/LICENSE @@ -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. diff --git a/vendor/github.com/Shopify/logrus-bugsnag/README.md b/vendor/github.com/Shopify/logrus-bugsnag/README.md new file mode 100644 index 00000000000..6cbc79d44a3 --- /dev/null +++ b/vendor/github.com/Shopify/logrus-bugsnag/README.md @@ -0,0 +1,24 @@ +## 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) +} +``` + diff --git a/vendor/github.com/Shopify/logrus-bugsnag/bugsnag.go b/vendor/github.com/Shopify/logrus-bugsnag/bugsnag.go new file mode 100644 index 00000000000..31ee5a08529 --- /dev/null +++ b/vendor/github.com/Shopify/logrus-bugsnag/bugsnag.go @@ -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, + } +}