diff --git a/hooks/sentry/README.md b/hooks/sentry/README.md new file mode 100644 index 0000000..bf0e950 --- /dev/null +++ b/hooks/sentry/README.md @@ -0,0 +1,29 @@ +# Sentry Hook for Logrus :walrus: + +[Sentry](https://getsentry.com) provides both self-hosted and hostes solutions for exception tracking. +Both client and server are [open source](https://github.com/getsentry/sentry). + +## Usage + +Every sentry application defined on the server gets a different [DNS](https://www.getsentry.com/docs/). In the example below replace `YOUR_DSN` with the one created for your application. + + +```go +import ( + "github.com/Sirupsen/logrus" + "github.com/Sirupsen/logrus/hooks/sentry" +) + +func main() { + log := logrus.New() + hook, err := logrus_sentry.NewSentryHook(YOUR_DSN, []logrus.Level{ + logrus.PanicLevel, + logrus.FatalLevel, + logrus.ErrorLevel, + }) + + if err == nil { + log.Hooks.Add(hook) + } +} +``` diff --git a/hooks/sentry/sentry.go b/hooks/sentry/sentry.go new file mode 100644 index 0000000..5c07eb2 --- /dev/null +++ b/hooks/sentry/sentry.go @@ -0,0 +1,84 @@ +package sentry_hook + +import ( + "github.com/Sirupsen/logrus" + "github.com/getsentry/raven-go" +) + +var ( + severityMap = map[logrus.Level]raven.Severity{ + logrus.DebugLevel: raven.DEBUG, + logrus.InfoLevel: raven.INFO, + logrus.WarnLevel: raven.WARNING, + logrus.ErrorLevel: raven.ERROR, + logrus.FatalLevel: raven.FATAL, + logrus.PanicLevel: raven.FATAL, + } +) + +func getAndDel(d logrus.Fields, key string) (string, bool) { + var ( + ok bool + v interface{} + val string + ) + if v, ok = d[key]; !ok { + return "", false + } + + if val, ok = v.(string); !ok { + return "", false + } + delete(d, key) + return val, true +} + +// SentryHook delivers logs to a sentry server. +type SentryHook struct { + // DSN for this application + // Modifications to this field after the call to NewSentryHook have no effect + DSN string + + client *raven.Client + levels []logrus.Level +} + +// NewSentryHook creates a hook to be added to an instance of logger and initializes the raven client. +func NewSentryHook(DSN string, levels []logrus.Level) (*SentryHook, error) { + client, err := raven.NewClient(DSN, nil) + if err != nil { + return nil, err + } + return &SentryHook{DSN, client, levels}, nil +} + +// Called when an event should be sent to sentry +// Special fields that sentry uses to give more information to the server +// are extracted from entry.Data (if they are found) +// These fields are: logger and server_name +func (hook *SentryHook) Fire(entry *logrus.Entry) error { + packet := &raven.Packet{ + Message: entry.Message, + Timestamp: raven.Timestamp(entry.Time), + Level: severityMap[entry.Level], + Platform: "go", + } + + d := entry.Data + + if logger, ok := getAndDel(d, "logger"); ok { + packet.Logger = logger + } + if serverName, ok := getAndDel(d, "server_name"); ok { + packet.ServerName = serverName + } + packet.Extra = map[string]interface{}(d) + + _, errCh := hook.client.Capture(packet, nil) + return <-errCh +} + +// Levels returns the available logging levels. +func (hook *SentryHook) Levels() []logrus.Level { + return hook.levels +}