Merge pull request #48 from Sirupsen/break-out-specials

entry: break out time, level and message from data
diff --git a/README.md b/README.md
index baf1af5..6070ab4 100644
--- a/README.md
+++ b/README.md
@@ -191,10 +191,10 @@
 
 ```go
 // Will log anything that is info or above (warn, error, fatal, panic). Default.
-log.Level = logrus.Info
+log.Level = logrus.InfoLevel
 ```
 
-It may be useful to set `log.Level = logrus.Debug` in a debug or verbose
+It may be useful to set `log.Level = logrus.DebugLevel` in a debug or verbose
 environment if your application has that.
 
 #### Entries
@@ -261,6 +261,9 @@
 log.Formatter = new(MyJSONFormatter)
 
 func (f *JSONFormatter) Format(entry *Entry) ([]byte, error) {
+  // Note this doesn't include Time, Level and Message which are available on
+  // the Entry. Consult `godoc` on information about those fields or read the
+  // source of the official loggers.
   serialized, err := json.Marshal(entry.Data)
     if err != nil {
       return nil, fmt.Errorf("Failed to marshal fields to JSON, %v", err)
diff --git a/entry.go b/entry.go
index 8292ded..44ff056 100644
--- a/entry.go
+++ b/entry.go
@@ -8,9 +8,24 @@
 	"time"
 )
 
+// An entry is the final or intermediate Logrus logging entry. It containts all
+// the fields passed with WithField{,s}. It's finally logged when Debug, Info,
+// Warn, Error, Fatal or Panic is called on it. These objects can be reused and
+// passed around as much as you wish to avoid field duplication.
 type Entry struct {
 	Logger *Logger
-	Data   Fields
+
+	// Contains all the fields set by the user.
+	Data Fields
+
+	// Time at which the log entry was created
+	Time time.Time
+
+	// Level the log entry was logged at: Debug, Info, Warn, Error, Fatal or Panic
+	Level Level
+
+	// Message passed to Debug, Info, Warn, Error, Fatal or Panic
+	Message string
 }
 
 var baseTimestamp time.Time
@@ -23,11 +38,14 @@
 	}
 }
 
+// Returns a reader for the entry, which is a proxy to the formatter.
 func (entry *Entry) Reader() (*bytes.Buffer, error) {
 	serialized, err := entry.Logger.Formatter.Format(entry)
 	return bytes.NewBuffer(serialized), err
 }
 
+// Returns the string representation from the reader and ultimately the
+// formatter.
 func (entry *Entry) String() (string, error) {
 	reader, err := entry.Reader()
 	if err != nil {
@@ -37,10 +55,12 @@
 	return reader.String(), err
 }
 
+// Add a single field to the Entry.
 func (entry *Entry) WithField(key string, value interface{}) *Entry {
 	return entry.WithFields(Fields{key: value})
 }
 
+// Add a map of fields to the Entry.
 func (entry *Entry) WithFields(fields Fields) *Entry {
 	data := Fields{}
 	for k, v := range entry.Data {
@@ -53,9 +73,9 @@
 }
 
 func (entry *Entry) log(level Level, msg string) string {
-	entry.Data["time"] = time.Now().String()
-	entry.Data["level"] = level.String()
-	entry.Data["msg"] = msg
+	entry.Time = time.Now()
+	entry.Level = level
+	entry.Message = msg
 
 	if err := entry.Logger.Hooks.Fire(level, entry); err != nil {
 		fmt.Fprintf(os.Stderr, "Failed to fire hook", err)
diff --git a/formatter.go b/formatter.go
index 3a2eff5..fc0ebd7 100644
--- a/formatter.go
+++ b/formatter.go
@@ -1,5 +1,9 @@
 package logrus
 
+import (
+	"time"
+)
+
 // The Formatter interface is used to implement a custom Formatter. It takes an
 // `Entry`. It exposes all the fields, including the default ones:
 //
@@ -13,3 +17,38 @@
 type Formatter interface {
 	Format(*Entry) ([]byte, error)
 }
+
+// This is to not silently overwrite `time`, `msg` and `level` fields when
+// dumping it. If this code wasn't there doing:
+//
+//  logrus.WithField("level", 1).Info("hello")
+//
+// Would just silently drop the user provided level. Instead with this code
+// it'll logged as:
+//
+//  {"level": "info", "fields.level": 1, "msg": "hello", "time": "..."}
+//
+// It's not exported because it's still using Data in an opionated way. It's to
+// avoid code duplication between the two default formatters.
+func prefixFieldClashes(entry *Entry) {
+	_, ok := entry.Data["time"]
+	if ok {
+		entry.Data["fields.time"] = entry.Data["time"]
+	}
+
+	entry.Data["time"] = entry.Time.Format(time.RFC3339)
+
+	_, ok = entry.Data["msg"]
+	if ok {
+		entry.Data["fields.msg"] = entry.Data["msg"]
+	}
+
+	entry.Data["msg"] = entry.Message
+
+	_, ok = entry.Data["level"]
+	if ok {
+		entry.Data["fields.level"] = entry.Data["level"]
+	}
+
+	entry.Data["level"] = entry.Level.String()
+}
diff --git a/json_formatter.go b/json_formatter.go
index cb3489e..c0e2d18 100644
--- a/json_formatter.go
+++ b/json_formatter.go
@@ -9,6 +9,8 @@
 }
 
 func (f *JSONFormatter) Format(entry *Entry) ([]byte, error) {
+	prefixFieldClashes(entry)
+
 	serialized, err := json.Marshal(entry.Data)
 	if err != nil {
 		return nil, fmt.Errorf("Failed to marshal fields to JSON, %v", err)
diff --git a/logrus_test.go b/logrus_test.go
index f14445c..6202300 100644
--- a/logrus_test.go
+++ b/logrus_test.go
@@ -129,6 +129,40 @@
 	assert.Equal(t, "value1", fields["key1"])
 }
 
+func TestUserSuppliedFieldDoesNotOverwriteDefaults(t *testing.T) {
+	LogAndAssertJSON(t, func(log *Logger) {
+		log.WithField("msg", "hello").Info("test")
+	}, func(fields Fields) {
+		assert.Equal(t, fields["msg"], "test")
+	})
+}
+
+func TestUserSuppliedMsgFieldHasPrefix(t *testing.T) {
+	LogAndAssertJSON(t, func(log *Logger) {
+		log.WithField("msg", "hello").Info("test")
+	}, func(fields Fields) {
+		assert.Equal(t, fields["msg"], "test")
+		assert.Equal(t, fields["fields.msg"], "hello")
+	})
+}
+
+func TestUserSuppliedTimeFieldHasPrefix(t *testing.T) {
+	LogAndAssertJSON(t, func(log *Logger) {
+		log.WithField("time", "hello").Info("test")
+	}, func(fields Fields) {
+		assert.Equal(t, fields["fields.time"], "hello")
+	})
+}
+
+func TestUserSuppliedLevelFieldHasPrefix(t *testing.T) {
+	LogAndAssertJSON(t, func(log *Logger) {
+		log.WithField("level", 1).Info("test")
+	}, func(fields Fields) {
+		assert.Equal(t, fields["level"], "info")
+		assert.Equal(t, fields["fields.level"], 1)
+	})
+}
+
 func TestConvertLevelToString(t *testing.T) {
 	assert.Equal(t, "debug", DebugLevel.String())
 	assert.Equal(t, "info", InfoLevel.String())
diff --git a/text_formatter.go b/text_formatter.go
index 06b4970..d71eba1 100644
--- a/text_formatter.go
+++ b/text_formatter.go
@@ -32,6 +32,8 @@
 func (f *TextFormatter) Format(entry *Entry) ([]byte, error) {
 	b := &bytes.Buffer{}
 
+	prefixFieldClashes(entry)
+
 	if f.ForceColors || IsTerminal() {
 		levelText := strings.ToUpper(entry.Data["level"].(string))[0:4]