forked from mirror/logrus
Merge pull request #269 from evalphobia/feature/move-sentry-hook
Move sentry hook to external repository
This commit is contained in:
commit
b2fcfe237c
|
@ -205,7 +205,7 @@ Note: Syslog hook also support connecting to local syslog (Ex. "/dev/log" or "/v
|
||||||
| [Papertrail](https://github.com/Sirupsen/logrus/blob/master/hooks/papertrail/papertrail.go) | Send errors to the Papertrail hosted logging service via UDP. |
|
| [Papertrail](https://github.com/Sirupsen/logrus/blob/master/hooks/papertrail/papertrail.go) | Send errors to the Papertrail hosted logging service via UDP. |
|
||||||
| [Syslog](https://github.com/Sirupsen/logrus/blob/master/hooks/syslog/syslog.go) | Send errors to remote syslog server. Uses standard library `log/syslog` behind the scenes. |
|
| [Syslog](https://github.com/Sirupsen/logrus/blob/master/hooks/syslog/syslog.go) | Send errors to remote syslog server. Uses standard library `log/syslog` behind the scenes. |
|
||||||
| [BugSnag](https://github.com/Sirupsen/logrus/blob/master/hooks/bugsnag/bugsnag.go) | Send errors to the Bugsnag exception tracking service. |
|
| [BugSnag](https://github.com/Sirupsen/logrus/blob/master/hooks/bugsnag/bugsnag.go) | Send errors to the Bugsnag exception tracking service. |
|
||||||
| [Sentry](https://github.com/Sirupsen/logrus/blob/master/hooks/sentry/sentry.go) | Send errors to the Sentry error logging and aggregation service. |
|
| [Sentry](https://github.com/evalphobia/logrus_sentry) | Send errors to the Sentry error logging and aggregation service. |
|
||||||
| [Hiprus](https://github.com/nubo/hiprus) | Send errors to a channel in hipchat. |
|
| [Hiprus](https://github.com/nubo/hiprus) | Send errors to a channel in hipchat. |
|
||||||
| [Logrusly](https://github.com/sebest/logrusly) | Send logs to [Loggly](https://www.loggly.com/) |
|
| [Logrusly](https://github.com/sebest/logrusly) | Send logs to [Loggly](https://www.loggly.com/) |
|
||||||
| [Slackrus](https://github.com/johntdyer/slackrus) | Hook for Slack chat. |
|
| [Slackrus](https://github.com/johntdyer/slackrus) | Hook for Slack chat. |
|
||||||
|
|
|
@ -1,111 +0,0 @@
|
||||||
# Sentry Hook for Logrus <img src="http://i.imgur.com/hTeVwmJ.png" width="40" height="40" alt=":walrus:" class="emoji" title=":walrus:" />
|
|
||||||
|
|
||||||
[Sentry](https://getsentry.com) provides both self-hosted and hosted
|
|
||||||
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
|
|
||||||
[DSN](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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
If you wish to initialize a SentryHook with tags, you can use the `NewWithTagsSentryHook` constructor to provide default tags:
|
|
||||||
|
|
||||||
```go
|
|
||||||
tags := map[string]string{
|
|
||||||
"site": "example.com",
|
|
||||||
}
|
|
||||||
levels := []logrus.Level{
|
|
||||||
logrus.PanicLevel,
|
|
||||||
logrus.FatalLevel,
|
|
||||||
logrus.ErrorLevel,
|
|
||||||
}
|
|
||||||
hook, err := logrus_sentry.NewWithTagsSentryHook(YOUR_DSN, tags, levels)
|
|
||||||
|
|
||||||
```
|
|
||||||
|
|
||||||
If you wish to initialize a SentryHook with an already initialized raven client, you can use
|
|
||||||
the `NewWithClientSentryHook` constructor:
|
|
||||||
|
|
||||||
```go
|
|
||||||
import (
|
|
||||||
"github.com/Sirupsen/logrus"
|
|
||||||
"github.com/Sirupsen/logrus/hooks/sentry"
|
|
||||||
"github.com/getsentry/raven-go"
|
|
||||||
)
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
log := logrus.New()
|
|
||||||
|
|
||||||
client, err := raven.New(YOUR_DSN)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
hook, err := logrus_sentry.NewWithClientSentryHook(client, []logrus.Level{
|
|
||||||
logrus.PanicLevel,
|
|
||||||
logrus.FatalLevel,
|
|
||||||
logrus.ErrorLevel,
|
|
||||||
})
|
|
||||||
|
|
||||||
if err == nil {
|
|
||||||
log.Hooks.Add(hook)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
hook, err := NewWithClientSentryHook(client, []logrus.Level{
|
|
||||||
logrus.ErrorLevel,
|
|
||||||
})
|
|
||||||
```
|
|
||||||
|
|
||||||
## Special fields
|
|
||||||
|
|
||||||
Some logrus fields have a special meaning in this hook,
|
|
||||||
these are `server_name`, `logger` and `http_request`.
|
|
||||||
When logs are sent to sentry these fields are treated differently.
|
|
||||||
- `server_name` (also known as hostname) is the name of the server which
|
|
||||||
is logging the event (hostname.example.com)
|
|
||||||
- `logger` is the part of the application which is logging the event.
|
|
||||||
In go this usually means setting it to the name of the package.
|
|
||||||
- `http_request` is the in-coming request(*http.Request). The detailed request data are sent to Sentry.
|
|
||||||
|
|
||||||
## Timeout
|
|
||||||
|
|
||||||
`Timeout` is the time the sentry hook will wait for a response
|
|
||||||
from the sentry server.
|
|
||||||
|
|
||||||
If this time elapses with no response from
|
|
||||||
the server an error will be returned.
|
|
||||||
|
|
||||||
If `Timeout` is set to 0 the SentryHook will not wait for a reply
|
|
||||||
and will assume a correct delivery.
|
|
||||||
|
|
||||||
The SentryHook has a default timeout of `100 milliseconds` when created
|
|
||||||
with a call to `NewSentryHook`. This can be changed by assigning a value to the `Timeout` field:
|
|
||||||
|
|
||||||
```go
|
|
||||||
hook, _ := logrus_sentry.NewSentryHook(...)
|
|
||||||
hook.Timeout = 20*time.Second
|
|
||||||
```
|
|
|
@ -1,137 +0,0 @@
|
||||||
package logrus_sentry
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"net/http"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"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
|
|
||||||
}
|
|
||||||
|
|
||||||
func getAndDelRequest(d logrus.Fields, key string) (*http.Request, bool) {
|
|
||||||
var (
|
|
||||||
ok bool
|
|
||||||
v interface{}
|
|
||||||
req *http.Request
|
|
||||||
)
|
|
||||||
if v, ok = d[key]; !ok {
|
|
||||||
return nil, false
|
|
||||||
}
|
|
||||||
if req, ok = v.(*http.Request); !ok || req == nil {
|
|
||||||
return nil, false
|
|
||||||
}
|
|
||||||
delete(d, key)
|
|
||||||
return req, true
|
|
||||||
}
|
|
||||||
|
|
||||||
// SentryHook delivers logs to a sentry server.
|
|
||||||
type SentryHook struct {
|
|
||||||
// Timeout sets the time to wait for a delivery error from the sentry server.
|
|
||||||
// If this is set to zero the server will not wait for any response and will
|
|
||||||
// consider the message correctly sent
|
|
||||||
Timeout time.Duration
|
|
||||||
|
|
||||||
client *raven.Client
|
|
||||||
levels []logrus.Level
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewSentryHook creates a hook to be added to an instance of logger
|
|
||||||
// and initializes the raven client.
|
|
||||||
// This method sets the timeout to 100 milliseconds.
|
|
||||||
func NewSentryHook(DSN string, levels []logrus.Level) (*SentryHook, error) {
|
|
||||||
client, err := raven.New(DSN)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return &SentryHook{100 * time.Millisecond, client, levels}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewWithTagsSentryHook creates a hook with tags to be added to an instance
|
|
||||||
// of logger and initializes the raven client. This method sets the timeout to
|
|
||||||
// 100 milliseconds.
|
|
||||||
func NewWithTagsSentryHook(DSN string, tags map[string]string, levels []logrus.Level) (*SentryHook, error) {
|
|
||||||
client, err := raven.NewWithTags(DSN, tags)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return &SentryHook{100 * time.Millisecond, client, levels}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewWithClientSentryHook creates a hook using an initialized raven client.
|
|
||||||
// This method sets the timeout to 100 milliseconds.
|
|
||||||
func NewWithClientSentryHook(client *raven.Client, levels []logrus.Level) (*SentryHook, error) {
|
|
||||||
return &SentryHook{100 * time.Millisecond, 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, server_name and http_request
|
|
||||||
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
|
|
||||||
}
|
|
||||||
if req, ok := getAndDelRequest(d, "http_request"); ok {
|
|
||||||
packet.Interfaces = append(packet.Interfaces, raven.NewHttp(req))
|
|
||||||
}
|
|
||||||
packet.Extra = map[string]interface{}(d)
|
|
||||||
|
|
||||||
_, errCh := hook.client.Capture(packet, nil)
|
|
||||||
timeout := hook.Timeout
|
|
||||||
if timeout != 0 {
|
|
||||||
timeoutCh := time.After(timeout)
|
|
||||||
select {
|
|
||||||
case err := <-errCh:
|
|
||||||
return err
|
|
||||||
case <-timeoutCh:
|
|
||||||
return fmt.Errorf("no response from sentry server in %s", timeout)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Levels returns the available logging levels.
|
|
||||||
func (hook *SentryHook) Levels() []logrus.Level {
|
|
||||||
return hook.levels
|
|
||||||
}
|
|
|
@ -1,154 +0,0 @@
|
||||||
package logrus_sentry
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
|
||||||
"io/ioutil"
|
|
||||||
"net/http"
|
|
||||||
"net/http/httptest"
|
|
||||||
"reflect"
|
|
||||||
"strings"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/Sirupsen/logrus"
|
|
||||||
"github.com/getsentry/raven-go"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
message = "error message"
|
|
||||||
server_name = "testserver.internal"
|
|
||||||
logger_name = "test.logger"
|
|
||||||
)
|
|
||||||
|
|
||||||
func getTestLogger() *logrus.Logger {
|
|
||||||
l := logrus.New()
|
|
||||||
l.Out = ioutil.Discard
|
|
||||||
return l
|
|
||||||
}
|
|
||||||
|
|
||||||
func WithTestDSN(t *testing.T, tf func(string, <-chan *raven.Packet)) {
|
|
||||||
pch := make(chan *raven.Packet, 1)
|
|
||||||
s := httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
|
|
||||||
defer req.Body.Close()
|
|
||||||
d := json.NewDecoder(req.Body)
|
|
||||||
p := &raven.Packet{}
|
|
||||||
err := d.Decode(p)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err.Error())
|
|
||||||
}
|
|
||||||
|
|
||||||
pch <- p
|
|
||||||
}))
|
|
||||||
defer s.Close()
|
|
||||||
|
|
||||||
fragments := strings.SplitN(s.URL, "://", 2)
|
|
||||||
dsn := fmt.Sprintf(
|
|
||||||
"%s://public:secret@%s/sentry/project-id",
|
|
||||||
fragments[0],
|
|
||||||
fragments[1],
|
|
||||||
)
|
|
||||||
tf(dsn, pch)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestSpecialFields(t *testing.T) {
|
|
||||||
WithTestDSN(t, func(dsn string, pch <-chan *raven.Packet) {
|
|
||||||
logger := getTestLogger()
|
|
||||||
|
|
||||||
hook, err := NewSentryHook(dsn, []logrus.Level{
|
|
||||||
logrus.ErrorLevel,
|
|
||||||
})
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err.Error())
|
|
||||||
}
|
|
||||||
logger.Hooks.Add(hook)
|
|
||||||
|
|
||||||
req, _ := http.NewRequest("GET", "url", nil)
|
|
||||||
logger.WithFields(logrus.Fields{
|
|
||||||
"server_name": server_name,
|
|
||||||
"logger": logger_name,
|
|
||||||
"http_request": req,
|
|
||||||
}).Error(message)
|
|
||||||
|
|
||||||
packet := <-pch
|
|
||||||
if packet.Logger != logger_name {
|
|
||||||
t.Errorf("logger should have been %s, was %s", logger_name, packet.Logger)
|
|
||||||
}
|
|
||||||
|
|
||||||
if packet.ServerName != server_name {
|
|
||||||
t.Errorf("server_name should have been %s, was %s", server_name, packet.ServerName)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestSentryHandler(t *testing.T) {
|
|
||||||
WithTestDSN(t, func(dsn string, pch <-chan *raven.Packet) {
|
|
||||||
logger := getTestLogger()
|
|
||||||
hook, err := NewSentryHook(dsn, []logrus.Level{
|
|
||||||
logrus.ErrorLevel,
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err.Error())
|
|
||||||
}
|
|
||||||
logger.Hooks.Add(hook)
|
|
||||||
|
|
||||||
logger.Error(message)
|
|
||||||
packet := <-pch
|
|
||||||
if packet.Message != message {
|
|
||||||
t.Errorf("message should have been %s, was %s", message, packet.Message)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestSentryWithClient(t *testing.T) {
|
|
||||||
WithTestDSN(t, func(dsn string, pch <-chan *raven.Packet) {
|
|
||||||
logger := getTestLogger()
|
|
||||||
|
|
||||||
client, _ := raven.New(dsn)
|
|
||||||
|
|
||||||
hook, err := NewWithClientSentryHook(client, []logrus.Level{
|
|
||||||
logrus.ErrorLevel,
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err.Error())
|
|
||||||
}
|
|
||||||
logger.Hooks.Add(hook)
|
|
||||||
|
|
||||||
logger.Error(message)
|
|
||||||
packet := <-pch
|
|
||||||
if packet.Message != message {
|
|
||||||
t.Errorf("message should have been %s, was %s", message, packet.Message)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestSentryTags(t *testing.T) {
|
|
||||||
WithTestDSN(t, func(dsn string, pch <-chan *raven.Packet) {
|
|
||||||
logger := getTestLogger()
|
|
||||||
tags := map[string]string{
|
|
||||||
"site": "test",
|
|
||||||
}
|
|
||||||
levels := []logrus.Level{
|
|
||||||
logrus.ErrorLevel,
|
|
||||||
}
|
|
||||||
|
|
||||||
hook, err := NewWithTagsSentryHook(dsn, tags, levels)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err.Error())
|
|
||||||
}
|
|
||||||
|
|
||||||
logger.Hooks.Add(hook)
|
|
||||||
|
|
||||||
logger.Error(message)
|
|
||||||
packet := <-pch
|
|
||||||
expected := raven.Tags{
|
|
||||||
raven.Tag{
|
|
||||||
Key: "site",
|
|
||||||
Value: "test",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
if !reflect.DeepEqual(packet.Tags, expected) {
|
|
||||||
t.Errorf("message should have been %s, was %s", message, packet.Message)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
Loading…
Reference in New Issue