summaryrefslogtreecommitdiffstats
path: root/vendor/github.com/heroku/rollrus/rollrus.go
diff options
context:
space:
mode:
Diffstat (limited to 'vendor/github.com/heroku/rollrus/rollrus.go')
-rw-r--r--vendor/github.com/heroku/rollrus/rollrus.go287
1 files changed, 287 insertions, 0 deletions
diff --git a/vendor/github.com/heroku/rollrus/rollrus.go b/vendor/github.com/heroku/rollrus/rollrus.go
new file mode 100644
index 000000000..50dd225da
--- /dev/null
+++ b/vendor/github.com/heroku/rollrus/rollrus.go
@@ -0,0 +1,287 @@
+// Package rollrus combines github.com/stvp/roll with github.com/sirupsen/logrus
+// via logrus.Hook mechanism, so that whenever logrus' logger.Error/f(),
+// logger.Fatal/f() or logger.Panic/f() are used the messages are
+// intercepted and sent to rollbar.
+//
+// Using SetupLogging should suffice for basic use cases that use the logrus
+// singleton logger.
+//
+// More custom uses are supported by creating a new Hook with NewHook and
+// registering that hook with the logrus Logger of choice.
+//
+// The levels can be customized with the WithLevels OptionFunc.
+//
+// Specific errors can be ignored with the WithIgnoredErrors OptionFunc. This is
+// useful for ignoring errors such as context.Canceled.
+//
+// See the Examples in the tests for more usage.
+package rollrus
+
+import (
+ "fmt"
+ "os"
+ "time"
+
+ "github.com/pkg/errors"
+ "github.com/sirupsen/logrus"
+ "github.com/stvp/roll"
+)
+
+var defaultTriggerLevels = []logrus.Level{
+ logrus.ErrorLevel,
+ logrus.FatalLevel,
+ logrus.PanicLevel,
+}
+
+// Hook is a wrapper for the rollbar Client and is usable as a logrus.Hook.
+type Hook struct {
+ roll.Client
+ triggers []logrus.Level
+ ignoredErrors map[error]struct{}
+ ignoreErrorFunc func(error) bool
+ ignoreFunc func(error, map[string]string) bool
+
+ // only used for tests to verify whether or not a report happened.
+ reported bool
+}
+
+// OptionFunc that can be passed to NewHook.
+type OptionFunc func(*Hook)
+
+// wellKnownErrorFields are the names of the fields to be checked for values of
+// type `error`, in priority order.
+var wellKnownErrorFields = []string{
+ logrus.ErrorKey, "err",
+}
+
+// WithLevels is an OptionFunc that customizes the log.Levels the hook will
+// report on.
+func WithLevels(levels ...logrus.Level) OptionFunc {
+ return func(h *Hook) {
+ h.triggers = levels
+ }
+}
+
+// WithMinLevel is an OptionFunc that customizes the log.Levels the hook will
+// report on by selecting all levels more severe than the one provided.
+func WithMinLevel(level logrus.Level) OptionFunc {
+ var levels []logrus.Level
+ for _, l := range logrus.AllLevels {
+ if l <= level {
+ levels = append(levels, l)
+ }
+ }
+
+ return func(h *Hook) {
+ h.triggers = levels
+ }
+}
+
+// WithIgnoredErrors is an OptionFunc that whitelists certain errors to prevent
+// them from firing.
+func WithIgnoredErrors(errors ...error) OptionFunc {
+ return func(h *Hook) {
+ for _, e := range errors {
+ h.ignoredErrors[e] = struct{}{}
+ }
+ }
+}
+
+// WithIgnoreErrorFunc is an OptionFunc that receives the error that is about
+// to be logged and returns true/false if it wants to fire a rollbar alert for.
+func WithIgnoreErrorFunc(fn func(error) bool) OptionFunc {
+ return func(h *Hook) {
+ h.ignoreErrorFunc = fn
+ }
+}
+
+// WithIgnoreFunc is an OptionFunc that receives the error and custom fields that are about
+// to be logged and returns true/false if it wants to fire a rollbar alert for.
+func WithIgnoreFunc(fn func(err error, fields map[string]string) bool) OptionFunc {
+ return func(h *Hook) {
+ h.ignoreFunc = fn
+ }
+}
+
+// NewHook creates a hook that is intended for use with your own logrus.Logger
+// instance. Uses the defualt report levels defined in wellKnownErrorFields.
+func NewHook(token string, env string, opts ...OptionFunc) *Hook {
+ h := NewHookForLevels(token, env, defaultTriggerLevels)
+
+ for _, o := range opts {
+ o(h)
+ }
+
+ return h
+}
+
+// NewHookForLevels provided by the caller. Otherwise works like NewHook.
+func NewHookForLevels(token string, env string, levels []logrus.Level) *Hook {
+ return &Hook{
+ Client: roll.New(token, env),
+ triggers: levels,
+ ignoredErrors: make(map[error]struct{}),
+ ignoreErrorFunc: func(error) bool { return false },
+ ignoreFunc: func(error, map[string]string) bool { return false },
+ }
+}
+
+// SetupLogging for use on Heroku. If token is not an empty string a rollbar
+// hook is added with the environment set to env. The log formatter is set to a
+// TextFormatter with timestamps disabled.
+func SetupLogging(token, env string) {
+ setupLogging(token, env, defaultTriggerLevels)
+}
+
+// SetupLoggingForLevels works like SetupLogging, but allows you to
+// set the levels on which to trigger this hook.
+func SetupLoggingForLevels(token, env string, levels []logrus.Level) {
+ setupLogging(token, env, levels)
+}
+
+func setupLogging(token, env string, levels []logrus.Level) {
+ logrus.SetFormatter(&logrus.TextFormatter{DisableTimestamp: true})
+
+ if token != "" {
+ logrus.AddHook(NewHookForLevels(token, env, levels))
+ }
+}
+
+// ReportPanic attempts to report the panic to rollbar using the provided
+// client and then re-panic. If it can't report the panic it will print an
+// error to stderr.
+func (r *Hook) ReportPanic() {
+ if p := recover(); p != nil {
+ if _, err := r.Client.Critical(fmt.Errorf("panic: %q", p), nil); err != nil {
+ fmt.Fprintf(os.Stderr, "reporting_panic=false err=%q\n", err)
+ }
+ panic(p)
+ }
+}
+
+// ReportPanic attempts to report the panic to rollbar if the token is set
+func ReportPanic(token, env string) {
+ if token != "" {
+ h := &Hook{Client: roll.New(token, env)}
+ h.ReportPanic()
+ }
+}
+
+// Levels returns the logrus log.Levels that this hook handles
+func (r *Hook) Levels() []logrus.Level {
+ if r.triggers == nil {
+ return defaultTriggerLevels
+ }
+ return r.triggers
+}
+
+// Fire the hook. This is called by Logrus for entries that match the levels
+// returned by Levels().
+func (r *Hook) Fire(entry *logrus.Entry) error {
+ trace, cause := extractError(entry)
+ if _, ok := r.ignoredErrors[cause]; ok {
+ return nil
+ }
+
+ if r.ignoreErrorFunc(cause) {
+ return nil
+ }
+
+ m := convertFields(entry.Data)
+ if _, exists := m["time"]; !exists {
+ m["time"] = entry.Time.Format(time.RFC3339)
+ }
+
+ if r.ignoreFunc(cause, m) {
+ return nil
+ }
+
+ return r.report(entry, cause, m, trace)
+}
+
+func (r *Hook) report(entry *logrus.Entry, cause error, m map[string]string, trace []uintptr) (err error) {
+ hasTrace := len(trace) > 0
+ level := entry.Level
+
+ r.reported = true
+
+ switch {
+ case hasTrace && level == logrus.FatalLevel:
+ _, err = r.Client.CriticalStack(cause, trace, m)
+ case hasTrace && level == logrus.PanicLevel:
+ _, err = r.Client.CriticalStack(cause, trace, m)
+ case hasTrace && level == logrus.ErrorLevel:
+ _, err = r.Client.ErrorStack(cause, trace, m)
+ case hasTrace && level == logrus.WarnLevel:
+ _, err = r.Client.WarningStack(cause, trace, m)
+ case level == logrus.FatalLevel || level == logrus.PanicLevel:
+ _, err = r.Client.Critical(cause, m)
+ case level == logrus.ErrorLevel:
+ _, err = r.Client.Error(cause, m)
+ case level == logrus.WarnLevel:
+ _, err = r.Client.Warning(cause, m)
+ case level == logrus.InfoLevel:
+ _, err = r.Client.Info(entry.Message, m)
+ case level == logrus.DebugLevel:
+ _, err = r.Client.Debug(entry.Message, m)
+ }
+ return err
+}
+
+// convertFields converts from log.Fields to map[string]string so that we can
+// report extra fields to Rollbar
+func convertFields(fields logrus.Fields) map[string]string {
+ m := make(map[string]string)
+ for k, v := range fields {
+ switch t := v.(type) {
+ case time.Time:
+ m[k] = t.Format(time.RFC3339)
+ default:
+ if s, ok := v.(fmt.Stringer); ok {
+ m[k] = s.String()
+ } else {
+ m[k] = fmt.Sprintf("%+v", t)
+ }
+ }
+ }
+
+ return m
+}
+
+// extractError attempts to extract an error from a well known field, err or error
+func extractError(entry *logrus.Entry) ([]uintptr, error) {
+ var trace []uintptr
+ fields := entry.Data
+
+ type stackTracer interface {
+ StackTrace() errors.StackTrace
+ }
+
+ for _, f := range wellKnownErrorFields {
+ e, ok := fields[f]
+ if !ok {
+ continue
+ }
+ err, ok := e.(error)
+ if !ok {
+ continue
+ }
+
+ cause := errors.Cause(err)
+ tracer, ok := err.(stackTracer)
+ if ok {
+ return copyStackTrace(tracer.StackTrace()), cause
+ }
+ return trace, cause
+ }
+
+ // when no error found, default to the logged message.
+ return trace, fmt.Errorf(entry.Message)
+}
+
+func copyStackTrace(trace errors.StackTrace) (out []uintptr) {
+ for _, frame := range trace {
+ out = append(out, uintptr(frame))
+ }
+ return
+}