Merge pull request #58 from al3x/master

Add Papertrail hook
diff --git a/.travis.yml b/.travis.yml
index 2efbc54..d5a559f 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -1,7 +1,9 @@
 language: go
 go:
-  - 1.1
   - 1.2
+  - 1.3
   - tip
-before_script:
+install:
   - go get github.com/stretchr/testify
+  - go get github.com/stvp/go-udp-testing
+  - go get github.com/tobi/airbrake-go
diff --git a/README.md b/README.md
index 75a1baa..44df9ee 100644
--- a/README.md
+++ b/README.md
@@ -214,11 +214,14 @@
 }
 ```
 
-* [`github.com/Sirupsen/logrus/hooks/airbrake`](https://github.com/Sirupsen/logrus/blob/master/hooks/airbrake/airbrake.go).
+* [`github.com/Sirupsen/logrus/hooks/airbrake`](https://github.com/Sirupsen/logrus/blob/master/hooks/airbrake/airbrake.go)
   Send errors to an exception tracking service compatible with the Airbrake API.
   Uses [`airbrake-go`](https://github.com/tobi/airbrake-go) behind the scenes.
 
-* [`github.com/Sirupsen/logrus/hooks/syslog`](https://github.com/Sirupsen/logrus/blob/master/hooks/syslog/syslog.go).
+* [`github.com/Sirupsen/logrus/hooks/papertrail`](https://github.com/Sirupsen/logrus/blob/master/hooks/airbrake/airbrake.go)
+  Send errors to the Papertrail hosted logging service via UDP.
+
+* [`github.com/Sirupsen/logrus/hooks/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.
 
@@ -298,7 +301,7 @@
 * `logrus.TextFormatter`. Logs the event in colors if stdout is a tty, otherwise
   without colors.
   * *Note:* to force colored output when there is no TTY, set the `ForceColors`
-    field to `true`.  To force no colored output even if there is a TTY  set the 
+    field to `true`.  To force no colored output even if there is a TTY  set the
     `DisableColors` field to `true`
 * `logrus.JSONFormatter`. Logs fields as JSON.
 
diff --git a/hooks/papertrail/README.md b/hooks/papertrail/README.md
new file mode 100644
index 0000000..ae61e92
--- /dev/null
+++ b/hooks/papertrail/README.md
@@ -0,0 +1,28 @@
+# Papertrail Hook for Logrus <img src="http://i.imgur.com/hTeVwmJ.png" width="40" height="40" alt=":walrus:" class="emoji" title=":walrus:" />
+
+[Papertrail](https://papertrailapp.com) provides hosted log management. Once stored in Papertrail, you can [group](http://help.papertrailapp.com/kb/how-it-works/groups/) your logs on various dimensions, [search](http://help.papertrailapp.com/kb/how-it-works/search-syntax) them, and trigger [alerts](http://help.papertrailapp.com/kb/how-it-works/alerts).
+
+In most deployments, you'll want to send logs to Papertrail via their [remote_syslog](http://help.papertrailapp.com/kb/configuration/configuring-centralized-logging-from-text-log-files-in-unix/) daemon, which requires no application-specific configuration. This hook is intended for relatively low-volume logging, likely in managed cloud hosting deployments where installing `remote_syslog` is not possible.
+
+## Usage
+
+You can find your Papertrail UDP port on your [Papertrail account page](https://papertrailapp.com/account/destinations). Substitute it below for `YOUR_PAPERTRAIL_UDP_PORT`.
+
+For `YOUR_APP_NAME`, substitute a short string that will readily identify your application or service in the logs.
+
+```go
+import (
+  "log/syslog"
+  "github.com/Sirupsen/logrus"
+  "github.com/Sirupsen/logrus/hooks/papertrail"
+)
+
+func main() {
+  log       := logrus.New()
+  hook, err := logrus_papertrail.NewPapertrailHook("logs.papertrailapp.com", YOUR_PAPERTRAIL_UDP_PORT, YOUR_APP_NAME)
+
+  if err == nil {
+    log.Hooks.Add(hook)
+  }
+}
+```
diff --git a/hooks/papertrail/papertrail.go b/hooks/papertrail/papertrail.go
new file mode 100644
index 0000000..48e2fea
--- /dev/null
+++ b/hooks/papertrail/papertrail.go
@@ -0,0 +1,54 @@
+package logrus_papertrail
+
+import (
+	"fmt"
+	"net"
+	"os"
+	"time"
+
+	"github.com/Sirupsen/logrus"
+)
+
+const (
+	format = "Jan 2 15:04:05"
+)
+
+// PapertrailHook to send logs to a logging service compatible with the Papertrail API.
+type PapertrailHook struct {
+	Host    string
+	Port    int
+	AppName string
+	UDPConn net.Conn
+}
+
+// NewPapertrailHook creates a hook to be added to an instance of logger.
+func NewPapertrailHook(host string, port int, appName string) (*PapertrailHook, error) {
+	conn, err := net.Dial("udp", fmt.Sprintf("%s:%d", host, port))
+	return &PapertrailHook{host, port, appName, conn}, err
+}
+
+// Fire is called when a log event is fired.
+func (hook *PapertrailHook) Fire(entry *logrus.Entry) error {
+	date := time.Now().Format(format)
+	payload := fmt.Sprintf("<22> %s %s: [%s] %s", date, hook.AppName, entry.Data["level"], entry.Message)
+
+	bytesWritten, err := hook.UDPConn.Write([]byte(payload))
+	if err != nil {
+		fmt.Fprintf(os.Stderr, "Unable to send log line to Papertrail via UDP. Wrote %d bytes before error: %v", bytesWritten, err)
+		return err
+	}
+
+	return nil
+}
+
+// Levels returns the available logging levels.
+func (hook *PapertrailHook) Levels() []logrus.Level {
+	return []logrus.Level{
+		logrus.PanicLevel,
+		logrus.FatalLevel,
+		logrus.ErrorLevel,
+		logrus.WarnLevel,
+		logrus.InfoLevel,
+		logrus.DebugLevel,
+	}
+}
diff --git a/hooks/papertrail/papertrail_test.go b/hooks/papertrail/papertrail_test.go
new file mode 100644
index 0000000..96318d0
--- /dev/null
+++ b/hooks/papertrail/papertrail_test.go
@@ -0,0 +1,26 @@
+package logrus_papertrail
+
+import (
+	"fmt"
+	"testing"
+
+	"github.com/Sirupsen/logrus"
+	"github.com/stvp/go-udp-testing"
+)
+
+func TestWritingToUDP(t *testing.T) {
+	port := 16661
+	udp.SetAddr(fmt.Sprintf(":%d", port))
+
+	hook, err := NewPapertrailHook("localhost", port, "test")
+	if err != nil {
+		t.Errorf("Unable to connect to local UDP server.")
+	}
+
+	log := logrus.New()
+	log.Hooks.Add(hook)
+
+	udp.ShouldReceive(t, "foo", func() {
+		log.Info("foo")
+	})
+}