2014-11-02 21:31:15 +03:00
|
|
|
package logrus_sentry
|
2014-11-01 21:55:40 +03:00
|
|
|
|
|
|
|
import (
|
2014-11-06 23:17:29 +03:00
|
|
|
"fmt"
|
|
|
|
"time"
|
|
|
|
|
2014-11-01 21:55:40 +03:00
|
|
|
"github.com/Sirupsen/logrus"
|
|
|
|
"github.com/getsentry/raven-go"
|
|
|
|
)
|
|
|
|
|
2014-11-06 23:17:29 +03:00
|
|
|
const (
|
|
|
|
timeout = 100 * time.Millisecond
|
|
|
|
)
|
|
|
|
|
2014-11-01 21:55:40 +03:00
|
|
|
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 {
|
|
|
|
client *raven.Client
|
|
|
|
levels []logrus.Level
|
|
|
|
}
|
|
|
|
|
2014-11-06 12:07:41 +03:00
|
|
|
// NewSentryHook creates a hook to be added to an instance of logger
|
|
|
|
// and initializes the raven client.
|
2014-11-01 21:55:40 +03:00
|
|
|
func NewSentryHook(DSN string, levels []logrus.Level) (*SentryHook, error) {
|
|
|
|
client, err := raven.NewClient(DSN, nil)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2014-11-06 12:07:41 +03:00
|
|
|
return &SentryHook{client, levels}, nil
|
2014-11-01 21:55:40 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
// 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)
|
|
|
|
|
2014-11-06 23:17:29 +03:00
|
|
|
_, errCh := hook.client.Capture(packet, nil)
|
|
|
|
timeoutCh := time.After(timeout)
|
|
|
|
select {
|
|
|
|
case err := <-errCh:
|
|
|
|
return err
|
|
|
|
case <-timeoutCh:
|
|
|
|
return fmt.Errorf("no response from sentry server in %s", timeout)
|
|
|
|
}
|
2014-11-06 12:07:41 +03:00
|
|
|
return nil
|
2014-11-01 21:55:40 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
// Levels returns the available logging levels.
|
|
|
|
func (hook *SentryHook) Levels() []logrus.Level {
|
|
|
|
return hook.levels
|
|
|
|
}
|