فهرست منبع

Merge pull request #11331 from jfrazelle/update-logrus

Update logrus to 0.6.6
Michael Crosby 10 سال پیش
والد
کامیت
5494432f91
26فایلهای تغییر یافته به همراه758 افزوده شده و 66 حذف شده
  1. 6 6
      integration-cli/docker_cli_build_test.go
  2. 5 5
      integration-cli/docker_cli_daemon_test.go
  3. 2 2
      project/vendor.sh
  4. 2 3
      vendor/src/github.com/Sirupsen/logrus/.travis.yml
  5. 43 8
      vendor/src/github.com/Sirupsen/logrus/README.md
  6. 5 1
      vendor/src/github.com/Sirupsen/logrus/entry.go
  7. 53 0
      vendor/src/github.com/Sirupsen/logrus/entry_test.go
  8. 24 3
      vendor/src/github.com/Sirupsen/logrus/examples/basic/basic.go
  9. 11 0
      vendor/src/github.com/Sirupsen/logrus/exported.go
  10. 7 7
      vendor/src/github.com/Sirupsen/logrus/formatter.go
  11. 1 1
      vendor/src/github.com/Sirupsen/logrus/hooks/airbrake/airbrake.go
  12. 2 1
      vendor/src/github.com/Sirupsen/logrus/hooks/papertrail/papertrail.go
  13. 61 0
      vendor/src/github.com/Sirupsen/logrus/hooks/sentry/README.md
  14. 100 0
      vendor/src/github.com/Sirupsen/logrus/hooks/sentry/sentry.go
  15. 97 0
      vendor/src/github.com/Sirupsen/logrus/hooks/sentry/sentry_test.go
  16. 2 2
      vendor/src/github.com/Sirupsen/logrus/hooks/syslog/README.md
  17. 7 7
      vendor/src/github.com/Sirupsen/logrus/hooks/syslog/syslog.go
  18. 15 5
      vendor/src/github.com/Sirupsen/logrus/json_formatter.go
  19. 120 0
      vendor/src/github.com/Sirupsen/logrus/json_formatter_test.go
  20. 1 1
      vendor/src/github.com/Sirupsen/logrus/logger.go
  21. 56 2
      vendor/src/github.com/Sirupsen/logrus/logrus_test.go
  22. 1 1
      vendor/src/github.com/Sirupsen/logrus/terminal_notwindows.go
  23. 8 0
      vendor/src/github.com/Sirupsen/logrus/terminal_openbsd.go
  24. 61 11
      vendor/src/github.com/Sirupsen/logrus/text_formatter.go
  25. 37 0
      vendor/src/github.com/Sirupsen/logrus/text_formatter_test.go
  26. 31 0
      vendor/src/github.com/Sirupsen/logrus/writer.go

+ 6 - 6
integration-cli/docker_cli_build_test.go

@@ -5042,8 +5042,8 @@ func TestBuildSpaces(t *testing.T) {
 	}
 	}
 
 
 	// Skip over the times
 	// Skip over the times
-	e1 := err1.Error()[strings.Index(err1.Error(), `level="`):]
-	e2 := err2.Error()[strings.Index(err1.Error(), `level="`):]
+	e1 := err1.Error()[strings.Index(err1.Error(), `level=`):]
+	e2 := err2.Error()[strings.Index(err1.Error(), `level=`):]
 
 
 	// Ignore whitespace since that's what were verifying doesn't change stuff
 	// Ignore whitespace since that's what were verifying doesn't change stuff
 	if strings.Replace(e1, " ", "", -1) != strings.Replace(e2, " ", "", -1) {
 	if strings.Replace(e1, " ", "", -1) != strings.Replace(e2, " ", "", -1) {
@@ -5056,8 +5056,8 @@ func TestBuildSpaces(t *testing.T) {
 	}
 	}
 
 
 	// Skip over the times
 	// Skip over the times
-	e1 = err1.Error()[strings.Index(err1.Error(), `level="`):]
-	e2 = err2.Error()[strings.Index(err1.Error(), `level="`):]
+	e1 = err1.Error()[strings.Index(err1.Error(), `level=`):]
+	e2 = err2.Error()[strings.Index(err1.Error(), `level=`):]
 
 
 	// Ignore whitespace since that's what were verifying doesn't change stuff
 	// Ignore whitespace since that's what were verifying doesn't change stuff
 	if strings.Replace(e1, " ", "", -1) != strings.Replace(e2, " ", "", -1) {
 	if strings.Replace(e1, " ", "", -1) != strings.Replace(e2, " ", "", -1) {
@@ -5070,8 +5070,8 @@ func TestBuildSpaces(t *testing.T) {
 	}
 	}
 
 
 	// Skip over the times
 	// Skip over the times
-	e1 = err1.Error()[strings.Index(err1.Error(), `level="`):]
-	e2 = err2.Error()[strings.Index(err1.Error(), `level="`):]
+	e1 = err1.Error()[strings.Index(err1.Error(), `level=`):]
+	e2 = err2.Error()[strings.Index(err1.Error(), `level=`):]
 
 
 	// Ignore whitespace since that's what were verifying doesn't change stuff
 	// Ignore whitespace since that's what were verifying doesn't change stuff
 	if strings.Replace(e1, " ", "", -1) != strings.Replace(e2, " ", "", -1) {
 	if strings.Replace(e1, " ", "", -1) != strings.Replace(e2, " ", "", -1) {

+ 5 - 5
integration-cli/docker_cli_daemon_test.go

@@ -244,7 +244,7 @@ func TestDaemonLoggingLevel(t *testing.T) {
 	}
 	}
 	d.Stop()
 	d.Stop()
 	content, _ := ioutil.ReadFile(d.logFile.Name())
 	content, _ := ioutil.ReadFile(d.logFile.Name())
-	if !strings.Contains(string(content), `level="debug"`) {
+	if !strings.Contains(string(content), `level=debug`) {
 		t.Fatalf(`Missing level="debug" in log file:\n%s`, string(content))
 		t.Fatalf(`Missing level="debug" in log file:\n%s`, string(content))
 	}
 	}
 
 
@@ -254,7 +254,7 @@ func TestDaemonLoggingLevel(t *testing.T) {
 	}
 	}
 	d.Stop()
 	d.Stop()
 	content, _ = ioutil.ReadFile(d.logFile.Name())
 	content, _ = ioutil.ReadFile(d.logFile.Name())
-	if strings.Contains(string(content), `level="debug"`) {
+	if strings.Contains(string(content), `level=debug`) {
 		t.Fatalf(`Should not have level="debug" in log file:\n%s`, string(content))
 		t.Fatalf(`Should not have level="debug" in log file:\n%s`, string(content))
 	}
 	}
 
 
@@ -264,7 +264,7 @@ func TestDaemonLoggingLevel(t *testing.T) {
 	}
 	}
 	d.Stop()
 	d.Stop()
 	content, _ = ioutil.ReadFile(d.logFile.Name())
 	content, _ = ioutil.ReadFile(d.logFile.Name())
-	if !strings.Contains(string(content), `level="debug"`) {
+	if !strings.Contains(string(content), `level=debug`) {
 		t.Fatalf(`Missing level="debug" in log file using -D:\n%s`, string(content))
 		t.Fatalf(`Missing level="debug" in log file using -D:\n%s`, string(content))
 	}
 	}
 
 
@@ -274,7 +274,7 @@ func TestDaemonLoggingLevel(t *testing.T) {
 	}
 	}
 	d.Stop()
 	d.Stop()
 	content, _ = ioutil.ReadFile(d.logFile.Name())
 	content, _ = ioutil.ReadFile(d.logFile.Name())
-	if !strings.Contains(string(content), `level="debug"`) {
+	if !strings.Contains(string(content), `level=debug`) {
 		t.Fatalf(`Missing level="debug" in log file using --debug:\n%s`, string(content))
 		t.Fatalf(`Missing level="debug" in log file using --debug:\n%s`, string(content))
 	}
 	}
 
 
@@ -284,7 +284,7 @@ func TestDaemonLoggingLevel(t *testing.T) {
 	}
 	}
 	d.Stop()
 	d.Stop()
 	content, _ = ioutil.ReadFile(d.logFile.Name())
 	content, _ = ioutil.ReadFile(d.logFile.Name())
-	if !strings.Contains(string(content), `level="debug"`) {
+	if !strings.Contains(string(content), `level=debug`) {
 		t.Fatalf(`Missing level="debug" in log file when using both --debug and --log-level=fatal:\n%s`, string(content))
 		t.Fatalf(`Missing level="debug" in log file when using both --debug and --log-level=fatal:\n%s`, string(content))
 	}
 	}
 
 

+ 2 - 2
project/vendor.sh

@@ -53,7 +53,7 @@ clone hg code.google.com/p/gosqlite 74691fb6f837
 
 
 clone git github.com/docker/libtrust 230dfd18c232
 clone git github.com/docker/libtrust 230dfd18c232
 
 
-clone git github.com/Sirupsen/logrus v0.6.0
+clone git github.com/Sirupsen/logrus v0.6.6
 
 
 clone git github.com/go-fsnotify/fsnotify v1.0.4
 clone git github.com/go-fsnotify/fsnotify v1.0.4
 
 
@@ -71,5 +71,5 @@ fi
 clone git github.com/docker/libcontainer aa10040b570386c1ae311c6245b9e21295b2b83a
 clone git github.com/docker/libcontainer aa10040b570386c1ae311c6245b9e21295b2b83a
 # see src/github.com/docker/libcontainer/update-vendor.sh which is the "source of truth" for libcontainer deps (just like this file)
 # see src/github.com/docker/libcontainer/update-vendor.sh which is the "source of truth" for libcontainer deps (just like this file)
 rm -rf src/github.com/docker/libcontainer/vendor
 rm -rf src/github.com/docker/libcontainer/vendor
-eval "$(grep '^clone ' src/github.com/docker/libcontainer/update-vendor.sh | grep -v 'github.com/codegangsta/cli')"
+eval "$(grep '^clone ' src/github.com/docker/libcontainer/update-vendor.sh | grep -v 'github.com/codegangsta/cli' | grep -v 'github.com/Sirupsen/logrus')"
 # we exclude "github.com/codegangsta/cli" here because it's only needed for "nsinit", which Docker doesn't include
 # we exclude "github.com/codegangsta/cli" here because it's only needed for "nsinit", which Docker doesn't include

+ 2 - 3
vendor/src/github.com/Sirupsen/logrus/.travis.yml

@@ -2,8 +2,7 @@ language: go
 go:
 go:
   - 1.2
   - 1.2
   - 1.3
   - 1.3
+  - 1.4
   - tip
   - tip
 install:
 install:
-  - go get github.com/stretchr/testify
-  - go get github.com/stvp/go-udp-testing
-  - go get github.com/tobi/airbrake-go
+  - go get -t ./...

+ 43 - 8
vendor/src/github.com/Sirupsen/logrus/README.md

@@ -1,10 +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)
+# 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 is a structured logger for Go (golang), completely API compatible with
 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
 the standard library logger. [Godoc][godoc]. **Please note the Logrus API is not
-yet stable (pre 1.0), the core API is unlikely change much but please version
-control your Logrus to make sure you aren't fetching latest `master` on every
-build.**
+yet stable (pre 1.0). Logrus itself is completely stable and has been used in
+many large deployments. The core API is unlikely to change much but please
+version control your Logrus to make sure you aren't fetching latest `master` on
+every build.**
 
 
 Nicely color-coded in development (when a TTY is attached, otherwise just
 Nicely color-coded in development (when a TTY is attached, otherwise just
 plain text):
 plain text):
@@ -33,7 +34,7 @@ ocean","size":10,"time":"2014-03-10 19:57:38.562264131 -0400 EDT"}
 
 
 With the default `log.Formatter = new(logrus.TextFormatter)` when a TTY is not
 With the default `log.Formatter = new(logrus.TextFormatter)` when a TTY is not
 attached, the output is compatible with the
 attached, the output is compatible with the
-[l2met](http://r.32k.io/l2met-introduction) format:
+[logfmt](http://godoc.org/github.com/kr/logfmt) format:
 
 
 ```text
 ```text
 time="2014-04-20 15:36:23.830442383 -0400 EDT" level="info" msg="A group of walrus emerges from the ocean" animal="walrus" size=10
 time="2014-04-20 15:36:23.830442383 -0400 EDT" level="info" msg="A group of walrus emerges from the ocean" animal="walrus" size=10
@@ -206,11 +207,18 @@ import (
   log "github.com/Sirupsen/logrus"
   log "github.com/Sirupsen/logrus"
   "github.com/Sirupsen/logrus/hooks/airbrake"
   "github.com/Sirupsen/logrus/hooks/airbrake"
   "github.com/Sirupsen/logrus/hooks/syslog"
   "github.com/Sirupsen/logrus/hooks/syslog"
+  "log/syslog"
 )
 )
 
 
 func init() {
 func init() {
   log.AddHook(new(logrus_airbrake.AirbrakeHook))
   log.AddHook(new(logrus_airbrake.AirbrakeHook))
-  log.AddHook(logrus_syslog.NewSyslogHook("udp", "localhost:514", syslog.LOG_INFO, ""))
+
+  hook, err := logrus_syslog.NewSyslogHook("udp", "localhost:514", syslog.LOG_INFO, "")
+  if err != nil {
+    log.Error("Unable to connect to local syslog daemon")
+  } else {
+    log.AddHook(hook)
+  }
 }
 }
 ```
 ```
 
 
@@ -228,6 +236,15 @@ func init() {
 * [`github.com/nubo/hiprus`](https://github.com/nubo/hiprus)
 * [`github.com/nubo/hiprus`](https://github.com/nubo/hiprus)
   Send errors to a channel in hipchat.
   Send errors to a channel in hipchat.
 
 
+* [`github.com/sebest/logrusly`](https://github.com/sebest/logrusly)
+  Send logs to Loggly (https://www.loggly.com/)
+
+* [`github.com/johntdyer/slackrus`](https://github.com/johntdyer/slackrus)
+  Hook for Slack chat.
+
+* [`github.com/wercker/journalhook`](https://github.com/wercker/journalhook).
+  Hook for logging to `systemd-journald`.
+
 #### Level logging
 #### Level logging
 
 
 Logrus has six logging levels: Debug, Info, Warning, Error, Fatal and Panic.
 Logrus has six logging levels: Debug, Info, Warning, Error, Fatal and Panic.
@@ -307,7 +324,7 @@ The built-in logging formatters are:
 
 
 Third party logging formatters:
 Third party logging formatters:
 
 
-* [`zalgo`](https://github.com/aybabtme/logzalgo): invoking the P͉̫o̳̼̊w̖͈̰͎e̬͔̭͂r͚̼̹̲ ̫͓͉̳͈ō̠͕͖̚f̝͍̠ ͕̲̞͖͑Z̖̫̤̫ͪa͉̬͈̗l͖͎g̳̥o̰̥̅!̣͔̲̻͊̄ ̙̘̦̹̦.
+* [`zalgo`](https://github.com/aybabtme/logzalgo): invoking the P͉̫o̳̼̊w̖͈̰͎e̬͔̭͂r͚̼̹̲ ̫͓͉̳͈ō̠͕͖̚f̝͍̠ ͕̲̞͖͑Z̖̫̤̫ͪa͉̬͈̗l͖͎g̳̥o̰̥̅!̣͔̲̻͊̄ ̙̘̦̹̦.
 
 
 You can define your formatter by implementing the `Formatter` interface,
 You can define your formatter by implementing the `Formatter` interface,
 requiring a `Format` method. `Format` takes an `*Entry`. `entry.Data` is a
 requiring a `Format` method. `Format` takes an `*Entry`. `entry.Data` is a
@@ -332,10 +349,28 @@ func (f *JSONFormatter) Format(entry *Entry) ([]byte, error) {
 }
 }
 ```
 ```
 
 
+#### Logger as an `io.Writer`
+
+Logrus can be transormed into an `io.Writer`. That writer is the end of an `io.Pipe` and it is your responsibility to close it.
+
+```go
+w := logger.Writer()
+defer w.Close()
+
+srv := http.Server{
+    // create a stdlib log.Logger that writes to
+    // logrus.Logger.
+    ErrorLog: log.New(w, "", 0),
+}
+```
+
+Each line written to that writer will be printed the usual way, using formatters
+and hooks. The level for those entries is `info`.
+
 #### Rotation
 #### Rotation
 
 
 Log rotation is not provided with Logrus. Log rotation should be done by an
 Log rotation is not provided with Logrus. Log rotation should be done by an
-external program (like `logrotated(8)`) that can compress and delete old log
+external program (like `logrotate(8)`) that can compress and delete old log
 entries. It should not be a feature of the application-level logger.
 entries. It should not be a feature of the application-level logger.
 
 
 
 

+ 5 - 1
vendor/src/github.com/Sirupsen/logrus/entry.go

@@ -100,7 +100,7 @@ func (entry *Entry) log(level Level, msg string) {
 	// panic() to use in Entry#Panic(), we avoid the allocation by checking
 	// panic() to use in Entry#Panic(), we avoid the allocation by checking
 	// directly here.
 	// directly here.
 	if level <= PanicLevel {
 	if level <= PanicLevel {
-		panic(reader.String())
+		panic(entry)
 	}
 	}
 }
 }
 
 
@@ -126,6 +126,10 @@ func (entry *Entry) Warn(args ...interface{}) {
 	}
 	}
 }
 }
 
 
+func (entry *Entry) Warning(args ...interface{}) {
+	entry.Warn(args...)
+}
+
 func (entry *Entry) Error(args ...interface{}) {
 func (entry *Entry) Error(args ...interface{}) {
 	if entry.Logger.Level >= ErrorLevel {
 	if entry.Logger.Level >= ErrorLevel {
 		entry.log(ErrorLevel, fmt.Sprint(args...))
 		entry.log(ErrorLevel, fmt.Sprint(args...))

+ 53 - 0
vendor/src/github.com/Sirupsen/logrus/entry_test.go

@@ -0,0 +1,53 @@
+package logrus
+
+import (
+	"bytes"
+	"fmt"
+	"testing"
+
+	"github.com/stretchr/testify/assert"
+)
+
+func TestEntryPanicln(t *testing.T) {
+	errBoom := fmt.Errorf("boom time")
+
+	defer func() {
+		p := recover()
+		assert.NotNil(t, p)
+
+		switch pVal := p.(type) {
+		case *Entry:
+			assert.Equal(t, "kaboom", pVal.Message)
+			assert.Equal(t, errBoom, pVal.Data["err"])
+		default:
+			t.Fatalf("want type *Entry, got %T: %#v", pVal, pVal)
+		}
+	}()
+
+	logger := New()
+	logger.Out = &bytes.Buffer{}
+	entry := NewEntry(logger)
+	entry.WithField("err", errBoom).Panicln("kaboom")
+}
+
+func TestEntryPanicf(t *testing.T) {
+	errBoom := fmt.Errorf("boom again")
+
+	defer func() {
+		p := recover()
+		assert.NotNil(t, p)
+
+		switch pVal := p.(type) {
+		case *Entry:
+			assert.Equal(t, "kaboom true", pVal.Message)
+			assert.Equal(t, errBoom, pVal.Data["err"])
+		default:
+			t.Fatalf("want type *Entry, got %T: %#v", pVal, pVal)
+		}
+	}()
+
+	logger := New()
+	logger.Out = &bytes.Buffer{}
+	entry := NewEntry(logger)
+	entry.WithField("err", errBoom).Panicf("kaboom %v", true)
+}

+ 24 - 3
vendor/src/github.com/Sirupsen/logrus/examples/basic/basic.go

@@ -9,9 +9,26 @@ var log = logrus.New()
 func init() {
 func init() {
 	log.Formatter = new(logrus.JSONFormatter)
 	log.Formatter = new(logrus.JSONFormatter)
 	log.Formatter = new(logrus.TextFormatter) // default
 	log.Formatter = new(logrus.TextFormatter) // default
+	log.Level = logrus.DebugLevel
 }
 }
 
 
 func main() {
 func main() {
+	defer func() {
+		err := recover()
+		if err != nil {
+			log.WithFields(logrus.Fields{
+				"omg":    true,
+				"err":    err,
+				"number": 100,
+			}).Fatal("The ice breaks!")
+		}
+	}()
+
+	log.WithFields(logrus.Fields{
+		"animal": "walrus",
+		"number": 8,
+	}).Debug("Started observing beach")
+
 	log.WithFields(logrus.Fields{
 	log.WithFields(logrus.Fields{
 		"animal": "walrus",
 		"animal": "walrus",
 		"size":   10,
 		"size":   10,
@@ -23,7 +40,11 @@ func main() {
 	}).Warn("The group's number increased tremendously!")
 	}).Warn("The group's number increased tremendously!")
 
 
 	log.WithFields(logrus.Fields{
 	log.WithFields(logrus.Fields{
-		"omg":    true,
-		"number": 100,
-	}).Fatal("The ice breaks!")
+		"temperature": -4,
+	}).Debug("Temperature changes")
+
+	log.WithFields(logrus.Fields{
+		"animal": "orca",
+		"size":   9009,
+	}).Panic("It's over 9000!")
 }
 }

+ 11 - 0
vendor/src/github.com/Sirupsen/logrus/exported.go

@@ -9,6 +9,10 @@ var (
 	std = New()
 	std = New()
 )
 )
 
 
+func StandardLogger() *Logger {
+	return std
+}
+
 // SetOutput sets the standard logger output.
 // SetOutput sets the standard logger output.
 func SetOutput(out io.Writer) {
 func SetOutput(out io.Writer) {
 	std.mu.Lock()
 	std.mu.Lock()
@@ -30,6 +34,13 @@ func SetLevel(level Level) {
 	std.Level = level
 	std.Level = level
 }
 }
 
 
+// GetLevel returns the standard logger level.
+func GetLevel() Level {
+	std.mu.Lock()
+	defer std.mu.Unlock()
+	return std.Level
+}
+
 // AddHook adds a hook to the standard logger hooks.
 // AddHook adds a hook to the standard logger hooks.
 func AddHook(hook Hook) {
 func AddHook(hook Hook) {
 	std.mu.Lock()
 	std.mu.Lock()

+ 7 - 7
vendor/src/github.com/Sirupsen/logrus/formatter.go

@@ -26,19 +26,19 @@ type Formatter interface {
 //
 //
 // It's not exported because it's still using Data in an opinionated way. It's to
 // It's not exported because it's still using Data in an opinionated way. It's to
 // avoid code duplication between the two default formatters.
 // avoid code duplication between the two default formatters.
-func prefixFieldClashes(entry *Entry) {
-	_, ok := entry.Data["time"]
+func prefixFieldClashes(data Fields) {
+	_, ok := data["time"]
 	if ok {
 	if ok {
-		entry.Data["fields.time"] = entry.Data["time"]
+		data["fields.time"] = data["time"]
 	}
 	}
 
 
-	_, ok = entry.Data["msg"]
+	_, ok = data["msg"]
 	if ok {
 	if ok {
-		entry.Data["fields.msg"] = entry.Data["msg"]
+		data["fields.msg"] = data["msg"]
 	}
 	}
 
 
-	_, ok = entry.Data["level"]
+	_, ok = data["level"]
 	if ok {
 	if ok {
-		entry.Data["fields.level"] = entry.Data["level"]
+		data["fields.level"] = data["level"]
 	}
 	}
 }
 }

+ 1 - 1
vendor/src/github.com/Sirupsen/logrus/hooks/airbrake/airbrake.go

@@ -9,7 +9,7 @@ import (
 // with the Airbrake API. You must set:
 // with the Airbrake API. You must set:
 // * airbrake.Endpoint
 // * airbrake.Endpoint
 // * airbrake.ApiKey
 // * airbrake.ApiKey
-// * airbrake.Environment (only sends exceptions when set to "production")
+// * airbrake.Environment
 //
 //
 // Before using this hook, to send an error. Entries that trigger an Error,
 // Before using this hook, to send an error. Entries that trigger an Error,
 // Fatal or Panic should now include an "error" field to send to Airbrake.
 // Fatal or Panic should now include an "error" field to send to Airbrake.

+ 2 - 1
vendor/src/github.com/Sirupsen/logrus/hooks/papertrail/papertrail.go

@@ -30,7 +30,8 @@ func NewPapertrailHook(host string, port int, appName string) (*PapertrailHook,
 // Fire is called when a log event is fired.
 // Fire is called when a log event is fired.
 func (hook *PapertrailHook) Fire(entry *logrus.Entry) error {
 func (hook *PapertrailHook) Fire(entry *logrus.Entry) error {
 	date := time.Now().Format(format)
 	date := time.Now().Format(format)
-	payload := fmt.Sprintf("<22> %s %s: [%s] %s", date, hook.AppName, entry.Data["level"], entry.Message)
+	msg, _ := entry.String()
+	payload := fmt.Sprintf("<22> %s %s: %s", date, hook.AppName, msg)
 
 
 	bytesWritten, err := hook.UDPConn.Write([]byte(payload))
 	bytesWritten, err := hook.UDPConn.Write([]byte(payload))
 	if err != nil {
 	if err != nil {

+ 61 - 0
vendor/src/github.com/Sirupsen/logrus/hooks/sentry/README.md

@@ -0,0 +1,61 @@
+# Sentry Hook for Logrus <img src="http://i.imgur.com/hTeVwmJ.png" width="40" height="40" alt=":walrus:" class="emoji" title=":walrus:" />
+
+[Sentry](https://getsentry.com) provides both self-hosted and hosted
+solutions for exception tracking.
+Both client and server are
+[open source](https://github.com/getsentry/sentry).
+
+## Usage
+
+Every sentry application defined on the server gets a different
+[DSN](https://www.getsentry.com/docs/). In the example below replace
+`YOUR_DSN` with the one created for your application.
+
+```go
+import (
+  "github.com/Sirupsen/logrus"
+  "github.com/Sirupsen/logrus/hooks/sentry"
+)
+
+func main() {
+  log       := logrus.New()
+  hook, err := logrus_sentry.NewSentryHook(YOUR_DSN, []logrus.Level{
+    logrus.PanicLevel,
+    logrus.FatalLevel,
+    logrus.ErrorLevel,
+  })
+
+  if err == nil {
+    log.Hooks.Add(hook)
+  }
+}
+```
+
+## Special fields
+
+Some logrus fields have a special meaning in this hook,
+these are server_name and logger.
+When logs are sent to sentry these fields are treated differently.
+- server_name (also known as hostname) is the name of the server which
+is logging the event (hostname.example.com)
+- logger is the part of the application which is logging the event.
+In go this usually means setting it to the name of the package.
+
+## Timeout
+
+`Timeout` is the time the sentry hook will wait for a response
+from the sentry server.
+
+If this time elapses with no response from
+the server an error will be returned.
+
+If `Timeout` is set to 0 the SentryHook will not wait for a reply
+and will assume a correct delivery.
+
+The SentryHook has a default timeout of `100 milliseconds` when created
+with a call to `NewSentryHook`. This can be changed by assigning a value to the `Timeout` field:
+
+```go
+hook, _ := logrus_sentry.NewSentryHook(...)
+hook.Timeout = 20*time.Second
+```

+ 100 - 0
vendor/src/github.com/Sirupsen/logrus/hooks/sentry/sentry.go

@@ -0,0 +1,100 @@
+package logrus_sentry
+
+import (
+	"fmt"
+	"time"
+
+	"github.com/Sirupsen/logrus"
+	"github.com/getsentry/raven-go"
+)
+
+var (
+	severityMap = map[logrus.Level]raven.Severity{
+		logrus.DebugLevel: raven.DEBUG,
+		logrus.InfoLevel:  raven.INFO,
+		logrus.WarnLevel:  raven.WARNING,
+		logrus.ErrorLevel: raven.ERROR,
+		logrus.FatalLevel: raven.FATAL,
+		logrus.PanicLevel: raven.FATAL,
+	}
+)
+
+func getAndDel(d logrus.Fields, key string) (string, bool) {
+	var (
+		ok  bool
+		v   interface{}
+		val string
+	)
+	if v, ok = d[key]; !ok {
+		return "", false
+	}
+
+	if val, ok = v.(string); !ok {
+		return "", false
+	}
+	delete(d, key)
+	return val, true
+}
+
+// SentryHook delivers logs to a sentry server.
+type SentryHook struct {
+	// Timeout sets the time to wait for a delivery error from the sentry server.
+	// If this is set to zero the server will not wait for any response and will
+	// consider the message correctly sent
+	Timeout time.Duration
+
+	client *raven.Client
+	levels []logrus.Level
+}
+
+// NewSentryHook creates a hook to be added to an instance of logger
+// and initializes the raven client.
+// This method sets the timeout to 100 milliseconds.
+func NewSentryHook(DSN string, levels []logrus.Level) (*SentryHook, error) {
+	client, err := raven.NewClient(DSN, nil)
+	if err != nil {
+		return nil, err
+	}
+	return &SentryHook{100 * time.Millisecond, client, levels}, nil
+}
+
+// Called when an event should be sent to sentry
+// Special fields that sentry uses to give more information to the server
+// are extracted from entry.Data (if they are found)
+// These fields are: logger and server_name
+func (hook *SentryHook) Fire(entry *logrus.Entry) error {
+	packet := &raven.Packet{
+		Message:   entry.Message,
+		Timestamp: raven.Timestamp(entry.Time),
+		Level:     severityMap[entry.Level],
+		Platform:  "go",
+	}
+
+	d := entry.Data
+
+	if logger, ok := getAndDel(d, "logger"); ok {
+		packet.Logger = logger
+	}
+	if serverName, ok := getAndDel(d, "server_name"); ok {
+		packet.ServerName = serverName
+	}
+	packet.Extra = map[string]interface{}(d)
+
+	_, errCh := hook.client.Capture(packet, nil)
+	timeout := hook.Timeout
+	if timeout != 0 {
+		timeoutCh := time.After(timeout)
+		select {
+		case err := <-errCh:
+			return err
+		case <-timeoutCh:
+			return fmt.Errorf("no response from sentry server in %s", timeout)
+		}
+	}
+	return nil
+}
+
+// Levels returns the available logging levels.
+func (hook *SentryHook) Levels() []logrus.Level {
+	return hook.levels
+}

+ 97 - 0
vendor/src/github.com/Sirupsen/logrus/hooks/sentry/sentry_test.go

@@ -0,0 +1,97 @@
+package logrus_sentry
+
+import (
+	"encoding/json"
+	"fmt"
+	"io/ioutil"
+	"net/http"
+	"net/http/httptest"
+	"strings"
+	"testing"
+
+	"github.com/Sirupsen/logrus"
+	"github.com/getsentry/raven-go"
+)
+
+const (
+	message     = "error message"
+	server_name = "testserver.internal"
+	logger_name = "test.logger"
+)
+
+func getTestLogger() *logrus.Logger {
+	l := logrus.New()
+	l.Out = ioutil.Discard
+	return l
+}
+
+func WithTestDSN(t *testing.T, tf func(string, <-chan *raven.Packet)) {
+	pch := make(chan *raven.Packet, 1)
+	s := httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
+		defer req.Body.Close()
+		d := json.NewDecoder(req.Body)
+		p := &raven.Packet{}
+		err := d.Decode(p)
+		if err != nil {
+			t.Fatal(err.Error())
+		}
+
+		pch <- p
+	}))
+	defer s.Close()
+
+	fragments := strings.SplitN(s.URL, "://", 2)
+	dsn := fmt.Sprintf(
+		"%s://public:secret@%s/sentry/project-id",
+		fragments[0],
+		fragments[1],
+	)
+	tf(dsn, pch)
+}
+
+func TestSpecialFields(t *testing.T) {
+	WithTestDSN(t, func(dsn string, pch <-chan *raven.Packet) {
+		logger := getTestLogger()
+
+		hook, err := NewSentryHook(dsn, []logrus.Level{
+			logrus.ErrorLevel,
+		})
+
+		if err != nil {
+			t.Fatal(err.Error())
+		}
+		logger.Hooks.Add(hook)
+		logger.WithFields(logrus.Fields{
+			"server_name": server_name,
+			"logger":      logger_name,
+		}).Error(message)
+
+		packet := <-pch
+		if packet.Logger != logger_name {
+			t.Errorf("logger should have been %s, was %s", logger_name, packet.Logger)
+		}
+
+		if packet.ServerName != server_name {
+			t.Errorf("server_name should have been %s, was %s", server_name, packet.ServerName)
+		}
+	})
+}
+
+func TestSentryHandler(t *testing.T) {
+	WithTestDSN(t, func(dsn string, pch <-chan *raven.Packet) {
+		logger := getTestLogger()
+		hook, err := NewSentryHook(dsn, []logrus.Level{
+			logrus.ErrorLevel,
+		})
+		if err != nil {
+			t.Fatal(err.Error())
+		}
+		logger.Hooks.Add(hook)
+
+		logger.Error(message)
+		packet := <-pch
+		if packet.Message != message {
+			t.Errorf("message should have been %s, was %s", message, packet.Message)
+		}
+	})
+}

+ 2 - 2
vendor/src/github.com/Sirupsen/logrus/hooks/syslog/README.md

@@ -6,7 +6,7 @@
 import (
 import (
   "log/syslog"
   "log/syslog"
   "github.com/Sirupsen/logrus"
   "github.com/Sirupsen/logrus"
-  "github.com/Sirupsen/logrus/hooks/syslog"
+  logrus_syslog "github.com/Sirupsen/logrus/hooks/syslog"
 )
 )
 
 
 func main() {
 func main() {
@@ -17,4 +17,4 @@ func main() {
     log.Hooks.Add(hook)
     log.Hooks.Add(hook)
   }
   }
 }
 }
-```
+```

+ 7 - 7
vendor/src/github.com/Sirupsen/logrus/hooks/syslog/syslog.go

@@ -29,18 +29,18 @@ func (hook *SyslogHook) Fire(entry *logrus.Entry) error {
 		return err
 		return err
 	}
 	}
 
 
-	switch entry.Data["level"] {
-	case "panic":
+	switch entry.Level {
+	case logrus.PanicLevel:
 		return hook.Writer.Crit(line)
 		return hook.Writer.Crit(line)
-	case "fatal":
+	case logrus.FatalLevel:
 		return hook.Writer.Crit(line)
 		return hook.Writer.Crit(line)
-	case "error":
+	case logrus.ErrorLevel:
 		return hook.Writer.Err(line)
 		return hook.Writer.Err(line)
-	case "warn":
+	case logrus.WarnLevel:
 		return hook.Writer.Warning(line)
 		return hook.Writer.Warning(line)
-	case "info":
+	case logrus.InfoLevel:
 		return hook.Writer.Info(line)
 		return hook.Writer.Info(line)
-	case "debug":
+	case logrus.DebugLevel:
 		return hook.Writer.Debug(line)
 		return hook.Writer.Debug(line)
 	default:
 	default:
 		return nil
 		return nil

+ 15 - 5
vendor/src/github.com/Sirupsen/logrus/json_formatter.go

@@ -9,12 +9,22 @@ import (
 type JSONFormatter struct{}
 type JSONFormatter struct{}
 
 
 func (f *JSONFormatter) Format(entry *Entry) ([]byte, error) {
 func (f *JSONFormatter) Format(entry *Entry) ([]byte, error) {
-	prefixFieldClashes(entry)
-	entry.Data["time"] = entry.Time.Format(time.RFC3339)
-	entry.Data["msg"] = entry.Message
-	entry.Data["level"] = entry.Level.String()
+	data := make(Fields, len(entry.Data)+3)
+	for k, v := range entry.Data {
+		// Otherwise errors are ignored by `encoding/json`
+		// https://github.com/Sirupsen/logrus/issues/137
+		if err, ok := v.(error); ok {
+			data[k] = err.Error()
+		} else {
+			data[k] = v
+		}
+	}
+	prefixFieldClashes(data)
+	data["time"] = entry.Time.Format(time.RFC3339)
+	data["msg"] = entry.Message
+	data["level"] = entry.Level.String()
 
 
-	serialized, err := json.Marshal(entry.Data)
+	serialized, err := json.Marshal(data)
 	if err != nil {
 	if err != nil {
 		return nil, fmt.Errorf("Failed to marshal fields to JSON, %v", err)
 		return nil, fmt.Errorf("Failed to marshal fields to JSON, %v", err)
 	}
 	}

+ 120 - 0
vendor/src/github.com/Sirupsen/logrus/json_formatter_test.go

@@ -0,0 +1,120 @@
+package logrus
+
+import (
+	"encoding/json"
+	"errors"
+
+	"testing"
+)
+
+func TestErrorNotLost(t *testing.T) {
+	formatter := &JSONFormatter{}
+
+	b, err := formatter.Format(WithField("error", errors.New("wild walrus")))
+	if err != nil {
+		t.Fatal("Unable to format entry: ", err)
+	}
+
+	entry := make(map[string]interface{})
+	err = json.Unmarshal(b, &entry)
+	if err != nil {
+		t.Fatal("Unable to unmarshal formatted entry: ", err)
+	}
+
+	if entry["error"] != "wild walrus" {
+		t.Fatal("Error field not set")
+	}
+}
+
+func TestErrorNotLostOnFieldNotNamedError(t *testing.T) {
+	formatter := &JSONFormatter{}
+
+	b, err := formatter.Format(WithField("omg", errors.New("wild walrus")))
+	if err != nil {
+		t.Fatal("Unable to format entry: ", err)
+	}
+
+	entry := make(map[string]interface{})
+	err = json.Unmarshal(b, &entry)
+	if err != nil {
+		t.Fatal("Unable to unmarshal formatted entry: ", err)
+	}
+
+	if entry["omg"] != "wild walrus" {
+		t.Fatal("Error field not set")
+	}
+}
+
+func TestFieldClashWithTime(t *testing.T) {
+	formatter := &JSONFormatter{}
+
+	b, err := formatter.Format(WithField("time", "right now!"))
+	if err != nil {
+		t.Fatal("Unable to format entry: ", err)
+	}
+
+	entry := make(map[string]interface{})
+	err = json.Unmarshal(b, &entry)
+	if err != nil {
+		t.Fatal("Unable to unmarshal formatted entry: ", err)
+	}
+
+	if entry["fields.time"] != "right now!" {
+		t.Fatal("fields.time not set to original time field")
+	}
+
+	if entry["time"] != "0001-01-01T00:00:00Z" {
+		t.Fatal("time field not set to current time, was: ", entry["time"])
+	}
+}
+
+func TestFieldClashWithMsg(t *testing.T) {
+	formatter := &JSONFormatter{}
+
+	b, err := formatter.Format(WithField("msg", "something"))
+	if err != nil {
+		t.Fatal("Unable to format entry: ", err)
+	}
+
+	entry := make(map[string]interface{})
+	err = json.Unmarshal(b, &entry)
+	if err != nil {
+		t.Fatal("Unable to unmarshal formatted entry: ", err)
+	}
+
+	if entry["fields.msg"] != "something" {
+		t.Fatal("fields.msg not set to original msg field")
+	}
+}
+
+func TestFieldClashWithLevel(t *testing.T) {
+	formatter := &JSONFormatter{}
+
+	b, err := formatter.Format(WithField("level", "something"))
+	if err != nil {
+		t.Fatal("Unable to format entry: ", err)
+	}
+
+	entry := make(map[string]interface{})
+	err = json.Unmarshal(b, &entry)
+	if err != nil {
+		t.Fatal("Unable to unmarshal formatted entry: ", err)
+	}
+
+	if entry["fields.level"] != "something" {
+		t.Fatal("fields.level not set to original level field")
+	}
+}
+
+func TestJSONEntryEndsWithNewline(t *testing.T) {
+	formatter := &JSONFormatter{}
+
+	b, err := formatter.Format(WithField("level", "something"))
+	if err != nil {
+		t.Fatal("Unable to format entry: ", err)
+	}
+
+	if b[len(b)-1] != '\n' {
+		t.Fatal("Expected JSON log entry to end with a newline")
+	}
+}

+ 1 - 1
vendor/src/github.com/Sirupsen/logrus/logger.go

@@ -38,7 +38,7 @@ type Logger struct {
 //      Out: os.Stderr,
 //      Out: os.Stderr,
 //      Formatter: new(JSONFormatter),
 //      Formatter: new(JSONFormatter),
 //      Hooks: make(levelHooks),
 //      Hooks: make(levelHooks),
-//      Level: logrus.Debug,
+//      Level: logrus.DebugLevel,
 //    }
 //    }
 //
 //
 // It's recommended to make this a global instance called `log`.
 // It's recommended to make this a global instance called `log`.

+ 56 - 2
vendor/src/github.com/Sirupsen/logrus/logrus_test.go

@@ -5,6 +5,7 @@ import (
 	"encoding/json"
 	"encoding/json"
 	"strconv"
 	"strconv"
 	"strings"
 	"strings"
+	"sync"
 	"testing"
 	"testing"
 
 
 	"github.com/stretchr/testify/assert"
 	"github.com/stretchr/testify/assert"
@@ -44,8 +45,12 @@ func LogAndAssertText(t *testing.T, log func(*Logger), assertions func(fields ma
 		}
 		}
 		kvArr := strings.Split(kv, "=")
 		kvArr := strings.Split(kv, "=")
 		key := strings.TrimSpace(kvArr[0])
 		key := strings.TrimSpace(kvArr[0])
-		val, err := strconv.Unquote(kvArr[1])
-		assert.NoError(t, err)
+		val := kvArr[1]
+		if kvArr[1][0] == '"' {
+			var err error
+			val, err = strconv.Unquote(val)
+			assert.NoError(t, err)
+		}
 		fields[key] = val
 		fields[key] = val
 	}
 	}
 	assertions(fields)
 	assertions(fields)
@@ -204,6 +209,38 @@ func TestDefaultFieldsAreNotPrefixed(t *testing.T) {
 	})
 	})
 }
 }
 
 
+func TestDoubleLoggingDoesntPrefixPreviousFields(t *testing.T) {
+
+	var buffer bytes.Buffer
+	var fields Fields
+
+	logger := New()
+	logger.Out = &buffer
+	logger.Formatter = new(JSONFormatter)
+
+	llog := logger.WithField("context", "eating raw fish")
+
+	llog.Info("looks delicious")
+
+	err := json.Unmarshal(buffer.Bytes(), &fields)
+	assert.NoError(t, err, "should have decoded first message")
+	assert.Equal(t, len(fields), 4, "should only have msg/time/level/context fields")
+	assert.Equal(t, fields["msg"], "looks delicious")
+	assert.Equal(t, fields["context"], "eating raw fish")
+
+	buffer.Reset()
+
+	llog.Warn("omg it is!")
+
+	err = json.Unmarshal(buffer.Bytes(), &fields)
+	assert.NoError(t, err, "should have decoded second message")
+	assert.Equal(t, len(fields), 4, "should only have msg/time/level/context fields")
+	assert.Equal(t, fields["msg"], "omg it is!")
+	assert.Equal(t, fields["context"], "eating raw fish")
+	assert.Nil(t, fields["fields.msg"], "should not have prefixed previous `msg` entry")
+
+}
+
 func TestConvertLevelToString(t *testing.T) {
 func TestConvertLevelToString(t *testing.T) {
 	assert.Equal(t, "debug", DebugLevel.String())
 	assert.Equal(t, "debug", DebugLevel.String())
 	assert.Equal(t, "info", InfoLevel.String())
 	assert.Equal(t, "info", InfoLevel.String())
@@ -245,3 +282,20 @@ func TestParseLevel(t *testing.T) {
 	l, err = ParseLevel("invalid")
 	l, err = ParseLevel("invalid")
 	assert.Equal(t, "not a valid logrus Level: \"invalid\"", err.Error())
 	assert.Equal(t, "not a valid logrus Level: \"invalid\"", err.Error())
 }
 }
+
+func TestGetSetLevelRace(t *testing.T) {
+	wg := sync.WaitGroup{}
+	for i := 0; i < 100; i++ {
+		wg.Add(1)
+		go func(i int) {
+			defer wg.Done()
+			if i%2 == 0 {
+				SetLevel(InfoLevel)
+			} else {
+				GetLevel()
+			}
+		}(i)
+
+	}
+	wg.Wait()
+}

+ 1 - 1
vendor/src/github.com/Sirupsen/logrus/terminal_notwindows.go

@@ -3,7 +3,7 @@
 // Use of this source code is governed by a BSD-style
 // Use of this source code is governed by a BSD-style
 // license that can be found in the LICENSE file.
 // license that can be found in the LICENSE file.
 
 
-// +build linux,!appengine darwin freebsd
+// +build linux darwin freebsd openbsd
 
 
 package logrus
 package logrus
 
 

+ 8 - 0
vendor/src/github.com/Sirupsen/logrus/terminal_openbsd.go

@@ -0,0 +1,8 @@
+
+package logrus
+
+import "syscall"
+
+const ioctlReadTermios = syscall.TIOCGETA
+
+type Termios syscall.Termios

+ 61 - 11
vendor/src/github.com/Sirupsen/logrus/text_formatter.go

@@ -3,6 +3,7 @@ package logrus
 import (
 import (
 	"bytes"
 	"bytes"
 	"fmt"
 	"fmt"
+	"regexp"
 	"sort"
 	"sort"
 	"strings"
 	"strings"
 	"time"
 	"time"
@@ -14,11 +15,13 @@ const (
 	green   = 32
 	green   = 32
 	yellow  = 33
 	yellow  = 33
 	blue    = 34
 	blue    = 34
+	gray    = 37
 )
 )
 
 
 var (
 var (
 	baseTimestamp time.Time
 	baseTimestamp time.Time
 	isTerminal    bool
 	isTerminal    bool
+	noQuoteNeeded *regexp.Regexp
 )
 )
 
 
 func init() {
 func init() {
@@ -32,28 +35,47 @@ func miniTS() int {
 
 
 type TextFormatter struct {
 type TextFormatter struct {
 	// Set to true to bypass checking for a TTY before outputting colors.
 	// Set to true to bypass checking for a TTY before outputting colors.
-	ForceColors   bool
+	ForceColors bool
+
+	// Force disabling colors.
 	DisableColors bool
 	DisableColors bool
+
+	// Disable timestamp logging. useful when output is redirected to logging
+	// system that already adds timestamps.
+	DisableTimestamp bool
+
+	// Enable logging the full timestamp when a TTY is attached instead of just
+	// the time passed since beginning of execution.
+	FullTimestamp bool
+
+	// The fields are sorted by default for a consistent output. For applications
+	// that log extremely frequently and don't use the JSON formatter this may not
+	// be desired.
+	DisableSorting bool
 }
 }
 
 
 func (f *TextFormatter) Format(entry *Entry) ([]byte, error) {
 func (f *TextFormatter) Format(entry *Entry) ([]byte, error) {
-
-	var keys []string
+	var keys []string = make([]string, 0, len(entry.Data))
 	for k := range entry.Data {
 	for k := range entry.Data {
 		keys = append(keys, k)
 		keys = append(keys, k)
 	}
 	}
-	sort.Strings(keys)
+
+	if !f.DisableSorting {
+		sort.Strings(keys)
+	}
 
 
 	b := &bytes.Buffer{}
 	b := &bytes.Buffer{}
 
 
-	prefixFieldClashes(entry)
+	prefixFieldClashes(entry.Data)
 
 
 	isColored := (f.ForceColors || isTerminal) && !f.DisableColors
 	isColored := (f.ForceColors || isTerminal) && !f.DisableColors
 
 
 	if isColored {
 	if isColored {
-		printColored(b, entry, keys)
+		f.printColored(b, entry, keys)
 	} else {
 	} else {
-		f.appendKeyValue(b, "time", entry.Time.Format(time.RFC3339))
+		if !f.DisableTimestamp {
+			f.appendKeyValue(b, "time", entry.Time.Format(time.RFC3339))
+		}
 		f.appendKeyValue(b, "level", entry.Level.String())
 		f.appendKeyValue(b, "level", entry.Level.String())
 		f.appendKeyValue(b, "msg", entry.Message)
 		f.appendKeyValue(b, "msg", entry.Message)
 		for _, key := range keys {
 		for _, key := range keys {
@@ -65,9 +87,11 @@ func (f *TextFormatter) Format(entry *Entry) ([]byte, error) {
 	return b.Bytes(), nil
 	return b.Bytes(), nil
 }
 }
 
 
-func printColored(b *bytes.Buffer, entry *Entry, keys []string) {
+func (f *TextFormatter) printColored(b *bytes.Buffer, entry *Entry, keys []string) {
 	var levelColor int
 	var levelColor int
 	switch entry.Level {
 	switch entry.Level {
+	case DebugLevel:
+		levelColor = gray
 	case WarnLevel:
 	case WarnLevel:
 		levelColor = yellow
 		levelColor = yellow
 	case ErrorLevel, FatalLevel, PanicLevel:
 	case ErrorLevel, FatalLevel, PanicLevel:
@@ -78,17 +102,43 @@ func printColored(b *bytes.Buffer, entry *Entry, keys []string) {
 
 
 	levelText := strings.ToUpper(entry.Level.String())[0:4]
 	levelText := strings.ToUpper(entry.Level.String())[0:4]
 
 
-	fmt.Fprintf(b, "\x1b[%dm%s\x1b[0m[%04d] %-44s ", levelColor, levelText, miniTS(), entry.Message)
+	if !f.FullTimestamp {
+		fmt.Fprintf(b, "\x1b[%dm%s\x1b[0m[%04d] %-44s ", levelColor, levelText, miniTS(), entry.Message)
+	} else {
+		fmt.Fprintf(b, "\x1b[%dm%s\x1b[0m[%s] %-44s ", levelColor, levelText, entry.Time.Format(time.RFC3339), entry.Message)
+	}
 	for _, k := range keys {
 	for _, k := range keys {
 		v := entry.Data[k]
 		v := entry.Data[k]
 		fmt.Fprintf(b, " \x1b[%dm%s\x1b[0m=%v", levelColor, k, v)
 		fmt.Fprintf(b, " \x1b[%dm%s\x1b[0m=%v", levelColor, k, v)
 	}
 	}
 }
 }
 
 
+func needsQuoting(text string) bool {
+	for _, ch := range text {
+		if !((ch >= 'a' && ch <= 'z') ||
+			(ch >= 'A' && ch <= 'Z') ||
+			(ch >= '0' && ch <= '9') ||
+			ch == '-' || ch == '.') {
+			return false
+		}
+	}
+	return true
+}
+
 func (f *TextFormatter) appendKeyValue(b *bytes.Buffer, key, value interface{}) {
 func (f *TextFormatter) appendKeyValue(b *bytes.Buffer, key, value interface{}) {
 	switch value.(type) {
 	switch value.(type) {
-	case string, error:
-		fmt.Fprintf(b, "%v=%q ", key, value)
+	case string:
+		if needsQuoting(value.(string)) {
+			fmt.Fprintf(b, "%v=%s ", key, value)
+		} else {
+			fmt.Fprintf(b, "%v=%q ", key, value)
+		}
+	case error:
+		if needsQuoting(value.(error).Error()) {
+			fmt.Fprintf(b, "%v=%s ", key, value)
+		} else {
+			fmt.Fprintf(b, "%v=%q ", key, value)
+		}
 	default:
 	default:
 		fmt.Fprintf(b, "%v=%v ", key, value)
 		fmt.Fprintf(b, "%v=%v ", key, value)
 	}
 	}

+ 37 - 0
vendor/src/github.com/Sirupsen/logrus/text_formatter_test.go

@@ -0,0 +1,37 @@
+package logrus
+
+import (
+	"bytes"
+	"errors"
+
+	"testing"
+)
+
+func TestQuoting(t *testing.T) {
+	tf := &TextFormatter{DisableColors: true}
+
+	checkQuoting := func(q bool, value interface{}) {
+		b, _ := tf.Format(WithField("test", value))
+		idx := bytes.Index(b, ([]byte)("test="))
+		cont := bytes.Contains(b[idx+5:], []byte{'"'})
+		if cont != q {
+			if q {
+				t.Errorf("quoting expected for: %#v", value)
+			} else {
+				t.Errorf("quoting not expected for: %#v", value)
+			}
+		}
+	}
+
+	checkQuoting(false, "abcd")
+	checkQuoting(false, "v1.0")
+	checkQuoting(false, "1234567890")
+	checkQuoting(true, "/foobar")
+	checkQuoting(true, "x y")
+	checkQuoting(true, "x,y")
+	checkQuoting(false, errors.New("invalid"))
+	checkQuoting(true, errors.New("invalid argument"))
+}
+
+// TODO add tests for sorting etc., this requires a parser for the text
+// formatter output.

+ 31 - 0
vendor/src/github.com/Sirupsen/logrus/writer.go

@@ -0,0 +1,31 @@
+package logrus
+
+import (
+	"bufio"
+	"io"
+	"runtime"
+)
+
+func (logger *Logger) Writer() (*io.PipeWriter) {
+	reader, writer := io.Pipe()
+
+	go logger.writerScanner(reader)
+	runtime.SetFinalizer(writer, writerFinalizer)
+
+	return writer
+}
+
+func (logger *Logger) writerScanner(reader *io.PipeReader) {
+	scanner := bufio.NewScanner(reader)
+	for scanner.Scan() {
+		logger.Print(scanner.Text())
+	}
+	if err := scanner.Err(); err != nil {
+		logger.Errorf("Error while reading from Writer: %s", err)
+	}
+	reader.Close()
+}
+
+func writerFinalizer(writer *io.PipeWriter) {
+	writer.Close()
+}