Merge pull request #400 from carolynvs/disable-color-timestamp

Allow disabling timestamps with colored output
diff --git a/.travis.yml b/.travis.yml
index dee4eb2..804c569 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -1,9 +1,7 @@
 language: go
 go:
-  - 1.3
-  - 1.4
-  - 1.5
   - 1.6
+  - 1.7
   - tip
 install:
   - go get -t ./...
diff --git a/README.md b/README.md
index ab48929..05d678a 100644
--- a/README.md
+++ b/README.md
@@ -1,5 +1,11 @@
 # 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)
 
+**Seeing weird case-sensitive problems?** See [this
+issue](https://github.com/sirupsen/logrus/issues/451#issuecomment-264332021).
+This change has been reverted. I apologize for causing this. I greatly
+underestimated the impact this would have. Logrus strives for stability and
+backwards compatibility and failed to provide that.
+
 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
 yet stable (pre 1.0). Logrus itself is completely stable and has been used in
@@ -228,8 +234,12 @@
 | [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|
 | [Sumorus](https://github.com/doublefree/sumorus) | Hook for logging to [SumoLogic](https://www.sumologic.com/)|
+| [Scribe](https://github.com/sagar8192/logrus-scribe-hook) | Hook for logging to [Scribe](https://github.com/facebookarchive/scribe)|
 | [Logstash](https://github.com/bshuster-repo/logrus-logstash-hook) | Hook for logging to [Logstash](https://www.elastic.co/products/logstash) |
+| [logz.io](https://github.com/ripcurld00d/logrus-logzio-hook) | Hook for logging to [logz.io](https://logz.io), a Log as a Service using Logstash |
 | [Logmatic.io](https://github.com/logmatic/logmatic-go) | Hook for logging to [Logmatic.io](http://logmatic.io/) |
+| [Pushover](https://github.com/toorop/logrus_pushover) | Send error via [Pushover](https://pushover.net) |
+| [PostgreSQL](https://github.com/gemnasium/logrus-postgresql-hook) | Send logs to [PostgreSQL](http://postgresql.org) |
 
 
 #### Level logging
@@ -367,6 +377,7 @@
 | Tool | Description |
 | ---- | ----------- |
 |[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.|
+|[Logrus Viper Helper](https://github.com/heirko/go-contrib/tree/master/logrusHelper)|An Helper arround Logrus to wrap with spf13/Viper to load configuration with fangs! And to simplify Logrus configuration use some behavior of [Logrus Mate](https://github.com/gogap/logrus_mate). [sample](https://github.com/heirko/iris-contrib/blob/master/middleware/logrus-logger/example) |
 
 #### Testing
 
@@ -403,7 +414,7 @@
 ...
 ```
 
-#### Thread safty
+#### Thread safety
 
 By default Logger is protected by mutex for concurrent writes, this mutex is invoked when calling hooks and writing logs.
 If you are sure such locking is not needed, you can call logger.SetNoLock() to disable the locking.
diff --git a/json_formatter.go b/json_formatter.go
index 2ad6dc5..266554e 100644
--- a/json_formatter.go
+++ b/json_formatter.go
@@ -5,9 +5,40 @@
 	"fmt"
 )
 
+type fieldKey string
+type FieldMap map[fieldKey]string
+
+const (
+	FieldKeyMsg   = "msg"
+	FieldKeyLevel = "level"
+	FieldKeyTime  = "time"
+)
+
+func (f FieldMap) resolve(key fieldKey) string {
+	if k, ok := f[key]; ok {
+		return k
+	}
+
+	return string(key)
+}
+
 type JSONFormatter struct {
 	// TimestampFormat sets the format used for marshaling timestamps.
 	TimestampFormat string
+
+	// DisableTimestamp allows disabling automatic timestamps in output
+	DisableTimestamp bool
+
+	// FieldMap allows users to customize the names of keys for various fields.
+	// As an example:
+	// formatter := &JSONFormatter{
+	//   	FieldMap: FieldMap{
+	// 		 FieldKeyTime: "@timestamp",
+	// 		 FieldKeyLevel: "@level",
+	// 		 FieldKeyLevel: "@message",
+	//    },
+	// }
+	FieldMap FieldMap
 }
 
 func (f *JSONFormatter) Format(entry *Entry) ([]byte, error) {
@@ -29,9 +60,11 @@
 		timestampFormat = DefaultTimestampFormat
 	}
 
-	data["time"] = entry.Time.Format(timestampFormat)
-	data["msg"] = entry.Message
-	data["level"] = entry.Level.String()
+	if !f.DisableTimestamp {
+		data[f.FieldMap.resolve(FieldKeyTime)] = entry.Time.Format(timestampFormat)
+	}
+	data[f.FieldMap.resolve(FieldKeyMsg)] = entry.Message
+	data[f.FieldMap.resolve(FieldKeyLevel)] = entry.Level.String()
 
 	serialized, err := json.Marshal(data)
 	if err != nil {
diff --git a/json_formatter_test.go b/json_formatter_test.go
index 1d70873..51093a7 100644
--- a/json_formatter_test.go
+++ b/json_formatter_test.go
@@ -3,7 +3,7 @@
 import (
 	"encoding/json"
 	"errors"
-
+	"strings"
 	"testing"
 )
 
@@ -118,3 +118,82 @@
 		t.Fatal("Expected JSON log entry to end with a newline")
 	}
 }
+
+func TestJSONMessageKey(t *testing.T) {
+	formatter := &JSONFormatter{
+		FieldMap: FieldMap{
+			FieldKeyMsg: "message",
+		},
+	}
+
+	b, err := formatter.Format(&Entry{Message: "oh hai"})
+	if err != nil {
+		t.Fatal("Unable to format entry: ", err)
+	}
+	s := string(b)
+	if !(strings.Contains(s, "message") && strings.Contains(s, "oh hai")) {
+		t.Fatal("Expected JSON to format message key")
+	}
+}
+
+func TestJSONLevelKey(t *testing.T) {
+	formatter := &JSONFormatter{
+		FieldMap: FieldMap{
+			FieldKeyLevel: "somelevel",
+		},
+	}
+
+	b, err := formatter.Format(WithField("level", "something"))
+	if err != nil {
+		t.Fatal("Unable to format entry: ", err)
+	}
+	s := string(b)
+	if !strings.Contains(s, "somelevel") {
+		t.Fatal("Expected JSON to format level key")
+	}
+}
+
+func TestJSONTimeKey(t *testing.T) {
+	formatter := &JSONFormatter{
+		FieldMap: FieldMap{
+			FieldKeyTime: "timeywimey",
+		},
+	}
+
+	b, err := formatter.Format(WithField("level", "something"))
+	if err != nil {
+		t.Fatal("Unable to format entry: ", err)
+	}
+	s := string(b)
+	if !strings.Contains(s, "timeywimey") {
+		t.Fatal("Expected JSON to format time key")
+	}
+}
+
+func TestJSONDisableTimestamp(t *testing.T) {
+	formatter := &JSONFormatter{
+		DisableTimestamp: true,
+	}
+
+	b, err := formatter.Format(WithField("level", "something"))
+	if err != nil {
+		t.Fatal("Unable to format entry: ", err)
+	}
+	s := string(b)
+	if strings.Contains(s, FieldKeyTime) {
+		t.Error("Did not prevent timestamp", s)
+	}
+}
+
+func TestJSONEnableTimestamp(t *testing.T) {
+	formatter := &JSONFormatter{}
+
+	b, err := formatter.Format(WithField("level", "something"))
+	if err != nil {
+		t.Fatal("Unable to format entry: ", err)
+	}
+	s := string(b)
+	if !strings.Contains(s, FieldKeyTime) {
+		t.Error("Timestamp not present", s)
+	}
+}
diff --git a/terminal_appengine.go b/terminal_appengine.go
new file mode 100644
index 0000000..1960169
--- /dev/null
+++ b/terminal_appengine.go
@@ -0,0 +1,8 @@
+// +build appengine
+
+package logrus
+
+// IsTerminal returns true if stderr's file descriptor is a terminal.
+func IsTerminal() bool {
+	return true
+}
diff --git a/terminal_bsd.go b/terminal_bsd.go
index 71f8d67..5f6be4d 100644
--- a/terminal_bsd.go
+++ b/terminal_bsd.go
@@ -1,4 +1,5 @@
 // +build darwin freebsd openbsd netbsd dragonfly
+// +build !appengine
 
 package logrus
 
diff --git a/terminal_linux.go b/terminal_linux.go
index a2c0b40..308160c 100644
--- a/terminal_linux.go
+++ b/terminal_linux.go
@@ -3,6 +3,8 @@
 // Use of this source code is governed by a BSD-style
 // license that can be found in the LICENSE file.
 
+// +build !appengine
+
 package logrus
 
 import "syscall"
diff --git a/terminal_notwindows.go b/terminal_notwindows.go
index b343b3a..329038f 100644
--- a/terminal_notwindows.go
+++ b/terminal_notwindows.go
@@ -4,6 +4,7 @@
 // license that can be found in the LICENSE file.
 
 // +build linux darwin freebsd openbsd netbsd dragonfly
+// +build !appengine
 
 package logrus
 
diff --git a/terminal_solaris.go b/terminal_solaris.go
index 3e70bf7..a3c6f6e 100644
--- a/terminal_solaris.go
+++ b/terminal_solaris.go
@@ -1,4 +1,4 @@
-// +build solaris
+// +build solaris,!appengine
 
 package logrus
 
diff --git a/terminal_windows.go b/terminal_windows.go
index 0146845..3727e8a 100644
--- a/terminal_windows.go
+++ b/terminal_windows.go
@@ -3,7 +3,7 @@
 // Use of this source code is governed by a BSD-style
 // license that can be found in the LICENSE file.
 
-// +build windows
+// +build windows,!appengine
 
 package logrus
 
diff --git a/text_formatter.go b/text_formatter.go
index b282470..20f2d7e 100644
--- a/text_formatter.go
+++ b/text_formatter.go
@@ -124,7 +124,8 @@
 	}
 	for _, k := range keys {
 		v := entry.Data[k]
-		fmt.Fprintf(b, " \x1b[%dm%s\x1b[0m=%+v", levelColor, k, v)
+		fmt.Fprintf(b, " \x1b[%dm%s\x1b[0m=", levelColor, k)
+		f.appendValue(b, v)
 	}
 }
 
@@ -144,7 +145,11 @@
 
 	b.WriteString(key)
 	b.WriteByte('=')
+	f.appendValue(b, value)
+	b.WriteByte(' ')
+}
 
+func (f *TextFormatter) appendValue(b *bytes.Buffer, value interface{}) {
 	switch value := value.(type) {
 	case string:
 		if !needsQuoting(value) {
@@ -157,11 +162,9 @@
 		if !needsQuoting(errmsg) {
 			b.WriteString(errmsg)
 		} else {
-			fmt.Fprintf(b, "%q", value)
+			fmt.Fprintf(b, "%q", errmsg)
 		}
 	default:
 		fmt.Fprint(b, value)
 	}
-
-	b.WriteByte(' ')
 }