Merge branch 'logstash'
diff --git a/README.md b/README.md
index 50faff6..178123d 100644
--- a/README.md
+++ b/README.md
@@ -286,6 +286,11 @@
     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_logstash.LogstashFormatter`. Logs fields as Logstash Events (http://logstash.net).
+
+    ```go
+      logrus.SetFormatter(&logrus_logstash.LogstashFormatter{Type: “application_name"})
+    ```
 
 Third party logging formatters:
 
diff --git a/formatters/logstash/logstash.go b/formatters/logstash/logstash.go
new file mode 100644
index 0000000..34b1ccb
--- /dev/null
+++ b/formatters/logstash/logstash.go
@@ -0,0 +1,48 @@
+package logstash
+
+import (
+	"encoding/json"
+	"fmt"
+	"github.com/Sirupsen/logrus"
+	"time"
+)
+
+// Formatter generates json in logstash format.
+// Logstash site: http://logstash.net/
+type LogstashFormatter struct {
+	Type string // if not empty use for logstash type field.
+}
+
+func (f *LogstashFormatter) Format(entry *logrus.Entry) ([]byte, error) {
+	entry.Data["@version"] = 1
+	entry.Data["@timestamp"] = entry.Time.Format(time.RFC3339)
+
+	// set message field
+	v, ok := entry.Data["message"]
+	if ok {
+		entry.Data["fields.message"] = v
+	}
+	entry.Data["message"] = entry.Message
+
+	// set level field
+	v, ok = entry.Data["level"]
+	if ok {
+		entry.Data["fields.level"] = v
+	}
+	entry.Data["level"] = entry.Level.String()
+
+	// set type field
+	if f.Type != "" {
+		v, ok = entry.Data["type"]
+		if ok {
+			entry.Data["fields.type"] = v
+		}
+		entry.Data["type"] = f.Type
+	}
+
+	serialized, err := json.Marshal(entry.Data)
+	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
new file mode 100644
index 0000000..d8814a0
--- /dev/null
+++ b/formatters/logstash/logstash_test.go
@@ -0,0 +1,52 @@
+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"])
+}