Merge pull request #320 from little-arhat/feature-logrus-interface

Add LogrusLogger interface for Entry and Logger
diff --git a/CHANGELOG.md b/CHANGELOG.md
index ecc8432..9e9e600 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,10 +1,14 @@
-# 0.9.0 (Unreleased)
+# 0.9.0
 
 * logrus/text_formatter: don't emit empty msg
 * logrus/hooks/airbrake: move out of main repository
 * logrus/hooks/sentry: move out of main repository
 * logrus/hooks/papertrail: move out of main repository
 * logrus/hooks/bugsnag: move out of main repository
+* logrus/core: run tests with `-race`
+* logrus/core: detect TTY based on `stderr`
+* logrus/core: support `WithError` on logger
+* logrus/core: Solaris support
 
 # 0.8.7
 
diff --git a/README.md b/README.md
index f8720c9..6e1721a 100644
--- a/README.md
+++ b/README.md
@@ -1,4 +1,4 @@
-# Logrus <img src="http://i.imgur.com/hTeVwmJ.png" width="40" height="40" alt=":walrus:" class="emoji" title=":walrus:"/>&nbsp;[![Build Status](https://travis-ci.org/Sirupsen/logrus.svg?branch=master)](https://travis-ci.org/Sirupsen/logrus)&nbsp;[![godoc reference](https://godoc.org/github.com/Sirupsen/logrus?status.png)][godoc]
+# Logrus <img src="http://i.imgur.com/hTeVwmJ.png" width="40" height="40" alt=":walrus:" class="emoji" title=":walrus:"/>&nbsp;[![Build Status](https://travis-ci.org/Sirupsen/logrus.svg?branch=master)](https://travis-ci.org/Sirupsen/logrus)&nbsp;[![GoDoc](https://godoc.org/github.com/Sirupsen/logrus?status.svg)](https://godoc.org/github.com/Sirupsen/logrus)
 
 Logrus is a structured logger for Go (golang), completely API compatible with
 the standard library logger. [Godoc][godoc]. **Please note the Logrus API is not
@@ -12,7 +12,7 @@
 
 ![Colored](http://i.imgur.com/PY7qMwd.png)
 
-With `log.Formatter = new(logrus.JSONFormatter)`, for easy parsing by logstash
+With `log.SetFormatter(&log.JSONFormatter{})`, for easy parsing by logstash
 or Splunk:
 
 ```json
@@ -32,7 +32,7 @@
 "time":"2014-03-10 19:57:38.562543128 -0400 EDT"}
 ```
 
-With the default `log.Formatter = new(&log.TextFormatter{})` when a TTY is not
+With the default `log.SetFormatter(&log.TextFormatter{})` when a TTY is not
 attached, the output is compatible with the
 [logfmt](http://godoc.org/github.com/kr/logfmt) format:
 
@@ -222,6 +222,11 @@
 | [Octokit](https://github.com/dorajistyle/logrus-octokit-hook) | Hook for logging to github via octokit |
 | [DeferPanic](https://github.com/deferpanic/dp-logrus) | Hook for logging to DeferPanic |
 | [Redis-Hook](https://github.com/rogierlommers/logrus-redis-hook) | Hook for logging to a ELK stack (through Redis) |
+| [Amqp-Hook](https://github.com/vladoatanasov/logrus_amqp) | Hook for logging to Amqp broker (Like RabbitMQ) |
+| [KafkaLogrus](https://github.com/goibibo/KafkaLogrus) | Hook for logging to kafka |
+| [Typetalk](https://github.com/dragon3/logrus-typetalk-hook) | Hook for logging to [Typetalk](https://www.typetalk.in/) |
+| [ElasticSearch](https://github.com/sohlich/elogrus) | Hook for logging to ElasticSearch|
+
 
 #### Level logging
 
@@ -363,4 +368,21 @@
 | ---- | ----------- |
 |[Logrus Mate](https://github.com/gogap/logrus_mate)|Logrus mate is a tool for Logrus to manage loggers, you can initial logger's level, hook and formatter by config file, the logger will generated with different config at different environment.|
 
-[godoc]: https://godoc.org/github.com/Sirupsen/logrus
+#### Testing
+
+Logrus has a built in facility for asserting the presence of log messages. This is implemented through the `test` hook and provides:
+
+* decorators for existing logger (`test.NewLocal` and `test.NewGlobal`) which basically just add the `test` hook
+* a test logger (`test.NewNullLogger`) that just records log messages (and does not output any):
+
+```go
+logger, hook := NewNullLogger()
+logger.Error("Hello error")
+
+assert.Equal(1, len(hook.Entries))
+assert.Equal(logrus.ErrorLevel, hook.LastEntry().Level)
+assert.Equal("Hello error", hook.LastEntry().Message)
+
+hook.Reset()
+assert.Nil(hook.LastEntry())
+```
diff --git a/entry.go b/entry.go
index 9ae900b..89e966e 100644
--- a/entry.go
+++ b/entry.go
@@ -68,7 +68,7 @@
 
 // Add a map of fields to the Entry.
 func (entry *Entry) WithFields(fields Fields) *Entry {
-	data := Fields{}
+	data := make(Fields, len(entry.Data)+len(fields))
 	for k, v := range entry.Data {
 		data[k] = v
 	}
diff --git a/hooks/syslog/syslog.go b/hooks/syslog/syslog.go
index c59f331..a36e200 100644
--- a/hooks/syslog/syslog.go
+++ b/hooks/syslog/syslog.go
@@ -50,12 +50,5 @@
 }
 
 func (hook *SyslogHook) Levels() []logrus.Level {
-	return []logrus.Level{
-		logrus.PanicLevel,
-		logrus.FatalLevel,
-		logrus.ErrorLevel,
-		logrus.WarnLevel,
-		logrus.InfoLevel,
-		logrus.DebugLevel,
-	}
+	return logrus.AllLevels
 }
diff --git a/hooks/test/test.go b/hooks/test/test.go
new file mode 100644
index 0000000..0688125
--- /dev/null
+++ b/hooks/test/test.go
@@ -0,0 +1,67 @@
+package test
+
+import (
+	"io/ioutil"
+
+	"github.com/Sirupsen/logrus"
+)
+
+// test.Hook is a hook designed for dealing with logs in test scenarios.
+type Hook struct {
+	Entries []*logrus.Entry
+}
+
+// Installs a test hook for the global logger.
+func NewGlobal() *Hook {
+
+	hook := new(Hook)
+	logrus.AddHook(hook)
+
+	return hook
+
+}
+
+// Installs a test hook for a given local logger.
+func NewLocal(logger *logrus.Logger) *Hook {
+
+	hook := new(Hook)
+	logger.Hooks.Add(hook)
+
+	return hook
+
+}
+
+// Creates a discarding logger and installs the test hook.
+func NewNullLogger() (*logrus.Logger, *Hook) {
+
+	logger := logrus.New()
+	logger.Out = ioutil.Discard
+
+	return logger, NewLocal(logger)
+
+}
+
+func (t *Hook) Fire(e *logrus.Entry) error {
+	t.Entries = append(t.Entries, e)
+	return nil
+}
+
+func (t *Hook) Levels() []logrus.Level {
+	return logrus.AllLevels
+}
+
+// LastEntry returns the last entry that was logged or nil.
+func (t *Hook) LastEntry() (l *logrus.Entry) {
+
+	if i := len(t.Entries) - 1; i < 0 {
+		return nil
+	} else {
+		return t.Entries[i]
+	}
+
+}
+
+// Reset removes all Entries from this test hook.
+func (t *Hook) Reset() {
+	t.Entries = make([]*logrus.Entry, 0)
+}
diff --git a/hooks/test/test_test.go b/hooks/test/test_test.go
new file mode 100644
index 0000000..d69455b
--- /dev/null
+++ b/hooks/test/test_test.go
@@ -0,0 +1,39 @@
+package test
+
+import (
+	"testing"
+
+	"github.com/Sirupsen/logrus"
+	"github.com/stretchr/testify/assert"
+)
+
+func TestAllHooks(t *testing.T) {
+
+	assert := assert.New(t)
+
+	logger, hook := NewNullLogger()
+	assert.Nil(hook.LastEntry())
+	assert.Equal(0, len(hook.Entries))
+
+	logger.Error("Hello error")
+	assert.Equal(logrus.ErrorLevel, hook.LastEntry().Level)
+	assert.Equal("Hello error", hook.LastEntry().Message)
+	assert.Equal(1, len(hook.Entries))
+
+	logger.Warn("Hello warning")
+	assert.Equal(logrus.WarnLevel, hook.LastEntry().Level)
+	assert.Equal("Hello warning", hook.LastEntry().Message)
+	assert.Equal(2, len(hook.Entries))
+
+	hook.Reset()
+	assert.Nil(hook.LastEntry())
+	assert.Equal(0, len(hook.Entries))
+
+	hook = NewGlobal()
+
+	logrus.Error("Hello error")
+	assert.Equal(logrus.ErrorLevel, hook.LastEntry().Level)
+	assert.Equal("Hello error", hook.LastEntry().Message)
+	assert.Equal(1, len(hook.Entries))
+
+}
diff --git a/logrus.go b/logrus.go
index aa5fa7e..7121996 100644
--- a/logrus.go
+++ b/logrus.go
@@ -3,6 +3,7 @@
 import (
 	"fmt"
 	"log"
+	"strings"
 )
 
 // Fields type, used to pass to `WithFields`.
@@ -33,7 +34,7 @@
 
 // ParseLevel takes a string level and returns the Logrus log level constant.
 func ParseLevel(lvl string) (Level, error) {
-	switch lvl {
+	switch strings.ToLower(lvl) {
 	case "panic":
 		return PanicLevel, nil
 	case "fatal":
@@ -52,6 +53,16 @@
 	return l, fmt.Errorf("not a valid logrus Level: %q", lvl)
 }
 
+// A constant exposing all logging levels
+var AllLevels = []Level{
+	PanicLevel,
+	FatalLevel,
+	ErrorLevel,
+	WarnLevel,
+	InfoLevel,
+	DebugLevel,
+}
+
 // These are the different logging levels. You can set the logging level to log
 // on your instance of logger, obtained with `logrus.New()`.
 const (
diff --git a/logrus_test.go b/logrus_test.go
index 4bc1268..b4e9ded 100644
--- a/logrus_test.go
+++ b/logrus_test.go
@@ -255,30 +255,58 @@
 	assert.Nil(t, err)
 	assert.Equal(t, PanicLevel, l)
 
+	l, err = ParseLevel("PANIC")
+	assert.Nil(t, err)
+	assert.Equal(t, PanicLevel, l)
+
 	l, err = ParseLevel("fatal")
 	assert.Nil(t, err)
 	assert.Equal(t, FatalLevel, l)
 
+	l, err = ParseLevel("FATAL")
+	assert.Nil(t, err)
+	assert.Equal(t, FatalLevel, l)
+
 	l, err = ParseLevel("error")
 	assert.Nil(t, err)
 	assert.Equal(t, ErrorLevel, l)
 
+	l, err = ParseLevel("ERROR")
+	assert.Nil(t, err)
+	assert.Equal(t, ErrorLevel, l)
+
 	l, err = ParseLevel("warn")
 	assert.Nil(t, err)
 	assert.Equal(t, WarnLevel, l)
 
+	l, err = ParseLevel("WARN")
+	assert.Nil(t, err)
+	assert.Equal(t, WarnLevel, l)
+
 	l, err = ParseLevel("warning")
 	assert.Nil(t, err)
 	assert.Equal(t, WarnLevel, l)
 
+	l, err = ParseLevel("WARNING")
+	assert.Nil(t, err)
+	assert.Equal(t, WarnLevel, l)
+
 	l, err = ParseLevel("info")
 	assert.Nil(t, err)
 	assert.Equal(t, InfoLevel, l)
 
+	l, err = ParseLevel("INFO")
+	assert.Nil(t, err)
+	assert.Equal(t, InfoLevel, l)
+
 	l, err = ParseLevel("debug")
 	assert.Nil(t, err)
 	assert.Equal(t, DebugLevel, l)
 
+	l, err = ParseLevel("DEBUG")
+	assert.Nil(t, err)
+	assert.Equal(t, DebugLevel, l)
+
 	l, err = ParseLevel("invalid")
 	assert.Equal(t, "not a valid logrus Level: \"invalid\"", err.Error())
 }