Merge pull request #269 from evalphobia/feature/move-sentry-hook
Move sentry hook to external repository
diff --git a/README.md b/README.md
index 244a52d..3e526c1 100644
--- a/README.md
+++ b/README.md
@@ -205,7 +205,7 @@
| [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. |
| [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. |
| [Logrusly](https://github.com/sebest/logrusly) | Send logs to [Loggly](https://www.loggly.com/) |
| [Slackrus](https://github.com/johntdyer/slackrus) | Hook for Slack chat. |
diff --git a/hooks/sentry/README.md b/hooks/sentry/README.md
deleted file mode 100644
index 31de654..0000000
--- a/hooks/sentry/README.md
+++ /dev/null
@@ -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
-```
diff --git a/hooks/sentry/sentry.go b/hooks/sentry/sentry.go
deleted file mode 100644
index cf88098..0000000
--- a/hooks/sentry/sentry.go
+++ /dev/null
@@ -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
-}
diff --git a/hooks/sentry/sentry_test.go b/hooks/sentry/sentry_test.go
deleted file mode 100644
index 4a97bc6..0000000
--- a/hooks/sentry/sentry_test.go
+++ /dev/null
@@ -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)
- }
- })
-}