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)
-		}
-	})
-}