Merge pull request #358 from gpolaert/patch-1

Add new hook for Logmatic.io
diff --git a/README.md b/README.md
index 555fdce..76cfdea 100644
--- a/README.md
+++ b/README.md
@@ -308,14 +308,10 @@
     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.
-* `logrus/formatters/logstash.LogstashFormatter`. Logs fields as [Logstash](http://logstash.net) Events.
-
-    ```go
-      logrus.SetFormatter(&logstash.LogstashFormatter{Type: "application_name"})
-    ```
 
 Third party logging formatters:
 
+* [`logstash`](https://github.com/bshuster-repo/logrus-logstash-hook). Logs fields as [Logstash](http://logstash.net) Events.
 * [`prefixed`](https://github.com/x-cray/logrus-prefixed-formatter). Displays log entry source along with alternative layout.
 * [`zalgo`](https://github.com/aybabtme/logzalgo). Invoking the P͉̫o̳̼̊w̖͈̰͎e̬͔̭͂r͚̼̹̲ ̫͓͉̳͈ō̠͕͖̚f̝͍̠ ͕̲̞͖͑Z̖̫̤̫ͪa͉̬͈̗l͖͎g̳̥o̰̥̅!̣͔̲̻͊̄ ̙̘̦̹̦.
 
@@ -390,3 +386,19 @@
 hook.Reset()
 assert.Nil(hook.LastEntry())
 ```
+
+#### Fatal handlers
+
+Logrus can register one or more functions that will be called when any `fatal`
+level message is logged. The registered handlers will be executed before
+logrus performs a `os.Exit(1)`. This behavior may be helpful if callers need
+to gracefully shutdown. Unlike a `panic("Something went wrong...")` call which can be intercepted with a deferred `recover` a call to `os.Exit(1)` can not be intercepted.
+
+```
+...
+handler := func() {
+  // gracefully shutdown something...
+}
+logrus.RegisterExitHandler(handler)
+...
+```
diff --git a/alt_exit.go b/alt_exit.go
new file mode 100644
index 0000000..b4c9e84
--- /dev/null
+++ b/alt_exit.go
@@ -0,0 +1,64 @@
+package logrus
+
+// The following code was sourced and modified from the
+// https://bitbucket.org/tebeka/atexit package governed by the following license:
+//
+// Copyright (c) 2012 Miki Tebeka <miki.tebeka@gmail.com>.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy of
+// this software and associated documentation files (the "Software"), to deal in
+// the Software without restriction, including without limitation the rights to
+// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
+// the Software, and to permit persons to whom the Software is furnished to do so,
+// subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all
+// copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
+// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
+// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
+// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+import (
+	"fmt"
+	"os"
+)
+
+var handlers = []func(){}
+
+func runHandler(handler func()) {
+	defer func() {
+		if err := recover(); err != nil {
+			fmt.Fprintln(os.Stderr, "Error: Logrus exit handler error:", err)
+		}
+	}()
+
+	handler()
+}
+
+func runHandlers() {
+	for _, handler := range handlers {
+		runHandler(handler)
+	}
+}
+
+// Exit runs all the Logrus atexit handlers and then terminates the program using os.Exit(code)
+func Exit(code int) {
+	runHandlers()
+	os.Exit(code)
+}
+
+// RegisterExitHandler adds a Logrus Exit handler, call logrus.Exit to invoke
+// all handlers. The handlers will also be invoked when any Fatal log entry is
+// made.
+//
+// This method is useful when a caller wishes to use logrus to log a fatal
+// message but also needs to gracefully shutdown. An example usecase could be
+// closing database connections, or sending a alert that the application is
+// closing.
+func RegisterExitHandler(handler func()) {
+	handlers = append(handlers, handler)
+}
diff --git a/alt_exit_test.go b/alt_exit_test.go
new file mode 100644
index 0000000..022b778
--- /dev/null
+++ b/alt_exit_test.go
@@ -0,0 +1,74 @@
+package logrus
+
+import (
+	"io/ioutil"
+	"os/exec"
+	"testing"
+	"time"
+)
+
+func TestRegister(t *testing.T) {
+	current := len(handlers)
+	RegisterExitHandler(func() {})
+	if len(handlers) != current+1 {
+		t.Fatalf("can't add handler")
+	}
+}
+
+func TestHandler(t *testing.T) {
+	gofile := "/tmp/testprog.go"
+	if err := ioutil.WriteFile(gofile, testprog, 0666); err != nil {
+		t.Fatalf("can't create go file")
+	}
+
+	outfile := "/tmp/testprog.out"
+	arg := time.Now().UTC().String()
+	err := exec.Command("go", "run", gofile, outfile, arg).Run()
+	if err == nil {
+		t.Fatalf("completed normally, should have failed")
+	}
+
+	data, err := ioutil.ReadFile(outfile)
+	if err != nil {
+		t.Fatalf("can't read output file %s", outfile)
+	}
+
+	if string(data) != arg {
+		t.Fatalf("bad data")
+	}
+}
+
+var testprog = []byte(`
+// Test program for atexit, gets output file and data as arguments and writes
+// data to output file in atexit handler.
+package main
+
+import (
+	"github.com/Sirupsen/logrus"
+	"flag"
+	"fmt"
+	"io/ioutil"
+)
+
+var outfile = ""
+var data = ""
+
+func handler() {
+	ioutil.WriteFile(outfile, []byte(data), 0666)
+}
+
+func badHandler() {
+	n := 0
+	fmt.Println(1/n)
+}
+
+func main() {
+	flag.Parse()
+	outfile = flag.Arg(0)
+	data = flag.Arg(1)
+
+	logrus.RegisterExitHandler(handler)
+	logrus.RegisterExitHandler(badHandler)
+	logrus.Fatal("Bye bye")
+}
+`)
diff --git a/entry.go b/entry.go
index 89e966e..54bfc57 100644
--- a/entry.go
+++ b/entry.go
@@ -150,7 +150,7 @@
 	if entry.Logger.Level >= FatalLevel {
 		entry.log(FatalLevel, fmt.Sprint(args...))
 	}
-	os.Exit(1)
+	Exit(1)
 }
 
 func (entry *Entry) Panic(args ...interface{}) {
@@ -198,7 +198,7 @@
 	if entry.Logger.Level >= FatalLevel {
 		entry.Fatal(fmt.Sprintf(format, args...))
 	}
-	os.Exit(1)
+	Exit(1)
 }
 
 func (entry *Entry) Panicf(format string, args ...interface{}) {
@@ -245,7 +245,7 @@
 	if entry.Logger.Level >= FatalLevel {
 		entry.Fatal(entry.sprintlnn(args...))
 	}
-	os.Exit(1)
+	Exit(1)
 }
 
 func (entry *Entry) Panicln(args ...interface{}) {
diff --git a/formatters/logstash/logstash.go b/formatters/logstash/logstash.go
deleted file mode 100644
index 2793af8..0000000
--- a/formatters/logstash/logstash.go
+++ /dev/null
@@ -1,63 +0,0 @@
-package logstash
-
-import (
-	"encoding/json"
-	"fmt"
-
-	"github.com/Sirupsen/logrus"
-)
-
-// Formatter generates json in logstash format.
-// Logstash site: http://logstash.net/
-type LogstashFormatter struct {
-	Type string // if not empty use for logstash type field.
-
-	// TimestampFormat sets the format used for timestamps.
-	TimestampFormat string
-}
-
-func (f *LogstashFormatter) Format(entry *logrus.Entry) ([]byte, error) {
-	fields := make(logrus.Fields)
-	for k, v := range entry.Data {
-		fields[k] = v
-	}
-
-	fields["@version"] = 1
-
-	timeStampFormat := f.TimestampFormat
-
-	if timeStampFormat == "" {
-		timeStampFormat = logrus.DefaultTimestampFormat
-	}
-
-	fields["@timestamp"] = entry.Time.Format(timeStampFormat)
-
-	// set message field
-	v, ok := entry.Data["message"]
-	if ok {
-		fields["fields.message"] = v
-	}
-	fields["message"] = entry.Message
-
-	// set level field
-	v, ok = entry.Data["level"]
-	if ok {
-		fields["fields.level"] = v
-	}
-	fields["level"] = entry.Level.String()
-
-	// set type field
-	if f.Type != "" {
-		v, ok = entry.Data["type"]
-		if ok {
-			fields["fields.type"] = v
-		}
-		fields["type"] = f.Type
-	}
-
-	serialized, err := json.Marshal(fields)
-	if err != nil {
-		return nil, fmt.Errorf("Failed to marshal fields to JSON, %v", err)
-	}
-	return append(serialized, '\n'), nil
-}
diff --git a/formatters/logstash/logstash_test.go b/formatters/logstash/logstash_test.go
deleted file mode 100644
index d8814a0..0000000
--- a/formatters/logstash/logstash_test.go
+++ /dev/null
@@ -1,52 +0,0 @@
-package logstash
-
-import (
-	"bytes"
-	"encoding/json"
-	"github.com/Sirupsen/logrus"
-	"github.com/stretchr/testify/assert"
-	"testing"
-)
-
-func TestLogstashFormatter(t *testing.T) {
-	assert := assert.New(t)
-
-	lf := LogstashFormatter{Type: "abc"}
-
-	fields := logrus.Fields{
-		"message": "def",
-		"level":   "ijk",
-		"type":    "lmn",
-		"one":     1,
-		"pi":      3.14,
-		"bool":    true,
-	}
-
-	entry := logrus.WithFields(fields)
-	entry.Message = "msg"
-	entry.Level = logrus.InfoLevel
-
-	b, _ := lf.Format(entry)
-
-	var data map[string]interface{}
-	dec := json.NewDecoder(bytes.NewReader(b))
-	dec.UseNumber()
-	dec.Decode(&data)
-
-	// base fields
-	assert.Equal(json.Number("1"), data["@version"])
-	assert.NotEmpty(data["@timestamp"])
-	assert.Equal("abc", data["type"])
-	assert.Equal("msg", data["message"])
-	assert.Equal("info", data["level"])
-
-	// substituted fields
-	assert.Equal("def", data["fields.message"])
-	assert.Equal("ijk", data["fields.level"])
-	assert.Equal("lmn", data["fields.type"])
-
-	// formats
-	assert.Equal(json.Number("1"), data["one"])
-	assert.Equal(json.Number("3.14"), data["pi"])
-	assert.Equal(true, data["bool"])
-}
diff --git a/logger.go b/logger.go
index 2fdb231..9052a80 100644
--- a/logger.go
+++ b/logger.go
@@ -51,7 +51,7 @@
 	}
 }
 
-// Adds a field to the log entry, note that you it doesn't log until you call
+// Adds a field to the log entry, note that it doesn't log until you call
 // Debug, Print, Info, Warn, Fatal or Panic. It only creates a log entry.
 // If you want multiple fields, use `WithFields`.
 func (logger *Logger) WithField(key string, value interface{}) *Entry {
@@ -108,7 +108,7 @@
 	if logger.Level >= FatalLevel {
 		NewEntry(logger).Fatalf(format, args...)
 	}
-	os.Exit(1)
+	Exit(1)
 }
 
 func (logger *Logger) Panicf(format string, args ...interface{}) {
@@ -155,7 +155,7 @@
 	if logger.Level >= FatalLevel {
 		NewEntry(logger).Fatal(args...)
 	}
-	os.Exit(1)
+	Exit(1)
 }
 
 func (logger *Logger) Panic(args ...interface{}) {
@@ -202,7 +202,7 @@
 	if logger.Level >= FatalLevel {
 		NewEntry(logger).Fatalln(args...)
 	}
-	os.Exit(1)
+	Exit(1)
 }
 
 func (logger *Logger) Panicln(args ...interface{}) {