From 89e2e5fc55620634df3f2c9403594b0f701b0c5c Mon Sep 17 00:00:00 2001 From: Jessica Frazelle Date: Wed, 11 Mar 2015 10:43:56 -0700 Subject: [PATCH] Update logrus to 0.6.6 Docker-DCO-1.1-Signed-off-by: Jessie Frazelle (github: jfrazelle) Docker-DCO-1.1-Signed-off-by: Jessie Frazelle (github: jfrazelle) Docker-DCO-1.1-Signed-off-by: Jessie Frazelle (github: jfrazelle) --- integration-cli/docker_cli_build_test.go | 12 +- integration-cli/docker_cli_daemon_test.go | 10 +- project/vendor.sh | 4 +- .../github.com/Sirupsen/logrus/.travis.yml | 5 +- .../src/github.com/Sirupsen/logrus/README.md | 51 ++++++-- .../src/github.com/Sirupsen/logrus/entry.go | 6 +- .../github.com/Sirupsen/logrus/entry_test.go | 53 ++++++++ .../Sirupsen/logrus/examples/basic/basic.go | 27 +++- .../github.com/Sirupsen/logrus/exported.go | 11 ++ .../github.com/Sirupsen/logrus/formatter.go | 14 +- .../logrus/hooks/airbrake/airbrake.go | 2 +- .../logrus/hooks/papertrail/papertrail.go | 3 +- .../Sirupsen/logrus/hooks/sentry/README.md | 61 +++++++++ .../Sirupsen/logrus/hooks/sentry/sentry.go | 100 +++++++++++++++ .../logrus/hooks/sentry/sentry_test.go | 97 ++++++++++++++ .../Sirupsen/logrus/hooks/syslog/README.md | 4 +- .../Sirupsen/logrus/hooks/syslog/syslog.go | 14 +- .../Sirupsen/logrus/json_formatter.go | 20 ++- .../Sirupsen/logrus/json_formatter_test.go | 120 ++++++++++++++++++ .../src/github.com/Sirupsen/logrus/logger.go | 2 +- .../github.com/Sirupsen/logrus/logrus_test.go | 58 ++++++++- .../Sirupsen/logrus/terminal_notwindows.go | 2 +- .../Sirupsen/logrus/terminal_openbsd.go | 8 ++ .../Sirupsen/logrus/text_formatter.go | 72 +++++++++-- .../Sirupsen/logrus/text_formatter_test.go | 37 ++++++ .../src/github.com/Sirupsen/logrus/writer.go | 31 +++++ 26 files changed, 758 insertions(+), 66 deletions(-) create mode 100644 vendor/src/github.com/Sirupsen/logrus/entry_test.go create mode 100644 vendor/src/github.com/Sirupsen/logrus/hooks/sentry/README.md create mode 100644 vendor/src/github.com/Sirupsen/logrus/hooks/sentry/sentry.go create mode 100644 vendor/src/github.com/Sirupsen/logrus/hooks/sentry/sentry_test.go create mode 100644 vendor/src/github.com/Sirupsen/logrus/json_formatter_test.go create mode 100644 vendor/src/github.com/Sirupsen/logrus/terminal_openbsd.go create mode 100644 vendor/src/github.com/Sirupsen/logrus/text_formatter_test.go create mode 100644 vendor/src/github.com/Sirupsen/logrus/writer.go diff --git a/integration-cli/docker_cli_build_test.go b/integration-cli/docker_cli_build_test.go index de8b55c4cc..01df51dcb6 100644 --- a/integration-cli/docker_cli_build_test.go +++ b/integration-cli/docker_cli_build_test.go @@ -5042,8 +5042,8 @@ func TestBuildSpaces(t *testing.T) { } // 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 if strings.Replace(e1, " ", "", -1) != strings.Replace(e2, " ", "", -1) { @@ -5056,8 +5056,8 @@ func TestBuildSpaces(t *testing.T) { } // 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 if strings.Replace(e1, " ", "", -1) != strings.Replace(e2, " ", "", -1) { @@ -5070,8 +5070,8 @@ func TestBuildSpaces(t *testing.T) { } // 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 if strings.Replace(e1, " ", "", -1) != strings.Replace(e2, " ", "", -1) { diff --git a/integration-cli/docker_cli_daemon_test.go b/integration-cli/docker_cli_daemon_test.go index 31f55296a4..671e57b0db 100644 --- a/integration-cli/docker_cli_daemon_test.go +++ b/integration-cli/docker_cli_daemon_test.go @@ -244,7 +244,7 @@ func TestDaemonLoggingLevel(t *testing.T) { } d.Stop() 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)) } @@ -254,7 +254,7 @@ func TestDaemonLoggingLevel(t *testing.T) { } d.Stop() 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)) } @@ -264,7 +264,7 @@ func TestDaemonLoggingLevel(t *testing.T) { } d.Stop() 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)) } @@ -274,7 +274,7 @@ func TestDaemonLoggingLevel(t *testing.T) { } d.Stop() 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)) } @@ -284,7 +284,7 @@ func TestDaemonLoggingLevel(t *testing.T) { } d.Stop() 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)) } diff --git a/project/vendor.sh b/project/vendor.sh index 0849bc00c2..ed3765aa6f 100755 --- a/project/vendor.sh +++ b/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/Sirupsen/logrus v0.6.0 +clone git github.com/Sirupsen/logrus v0.6.6 clone git github.com/go-fsnotify/fsnotify v1.0.4 @@ -71,5 +71,5 @@ fi clone git github.com/docker/libcontainer dd3cb8822352fd4acc0b8b426bd86e47e98f6853 # 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 -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 diff --git a/vendor/src/github.com/Sirupsen/logrus/.travis.yml b/vendor/src/github.com/Sirupsen/logrus/.travis.yml index d5a559f840..2d8c086617 100644 --- a/vendor/src/github.com/Sirupsen/logrus/.travis.yml +++ b/vendor/src/github.com/Sirupsen/logrus/.travis.yml @@ -2,8 +2,7 @@ language: go go: - 1.2 - 1.3 + - 1.4 - tip 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 ./... diff --git a/vendor/src/github.com/Sirupsen/logrus/README.md b/vendor/src/github.com/Sirupsen/logrus/README.md index 01769c723f..e755e7c180 100644 --- a/vendor/src/github.com/Sirupsen/logrus/README.md +++ b/vendor/src/github.com/Sirupsen/logrus/README.md @@ -1,10 +1,11 @@ -# Logrus :walrus: [![Build Status](https://travis-ci.org/Sirupsen/logrus.svg?branch=master)](https://travis-ci.org/Sirupsen/logrus) +# Logrus :walrus: [![Build Status](https://travis-ci.org/Sirupsen/logrus.svg?branch=master)](https://travis-ci.org/Sirupsen/logrus) [![godoc reference](https://godoc.org/github.com/Sirupsen/logrus?status.png)][godoc] 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), 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 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 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 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" "github.com/Sirupsen/logrus/hooks/airbrake" "github.com/Sirupsen/logrus/hooks/syslog" + "log/syslog" ) func init() { 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) 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 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: -* [`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, 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 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. diff --git a/vendor/src/github.com/Sirupsen/logrus/entry.go b/vendor/src/github.com/Sirupsen/logrus/entry.go index a77c4b0ed1..17fe6f707b 100644 --- a/vendor/src/github.com/Sirupsen/logrus/entry.go +++ b/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 // directly here. 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{}) { if entry.Logger.Level >= ErrorLevel { entry.log(ErrorLevel, fmt.Sprint(args...)) diff --git a/vendor/src/github.com/Sirupsen/logrus/entry_test.go b/vendor/src/github.com/Sirupsen/logrus/entry_test.go new file mode 100644 index 0000000000..98717df490 --- /dev/null +++ b/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) +} diff --git a/vendor/src/github.com/Sirupsen/logrus/examples/basic/basic.go b/vendor/src/github.com/Sirupsen/logrus/examples/basic/basic.go index 35945509c3..a1623ec003 100644 --- a/vendor/src/github.com/Sirupsen/logrus/examples/basic/basic.go +++ b/vendor/src/github.com/Sirupsen/logrus/examples/basic/basic.go @@ -9,9 +9,26 @@ var log = logrus.New() func init() { log.Formatter = new(logrus.JSONFormatter) log.Formatter = new(logrus.TextFormatter) // default + log.Level = logrus.DebugLevel } 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{ "animal": "walrus", "size": 10, @@ -23,7 +40,11 @@ func main() { }).Warn("The group's number increased tremendously!") 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!") } diff --git a/vendor/src/github.com/Sirupsen/logrus/exported.go b/vendor/src/github.com/Sirupsen/logrus/exported.go index 0e2d59f19a..a67e1b802d 100644 --- a/vendor/src/github.com/Sirupsen/logrus/exported.go +++ b/vendor/src/github.com/Sirupsen/logrus/exported.go @@ -9,6 +9,10 @@ var ( std = New() ) +func StandardLogger() *Logger { + return std +} + // SetOutput sets the standard logger output. func SetOutput(out io.Writer) { std.mu.Lock() @@ -30,6 +34,13 @@ func SetLevel(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. func AddHook(hook Hook) { std.mu.Lock() diff --git a/vendor/src/github.com/Sirupsen/logrus/formatter.go b/vendor/src/github.com/Sirupsen/logrus/formatter.go index 74c49a0e0e..038ce9fd29 100644 --- a/vendor/src/github.com/Sirupsen/logrus/formatter.go +++ b/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 // 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 { - entry.Data["fields.time"] = entry.Data["time"] + data["fields.time"] = data["time"] } - _, ok = entry.Data["msg"] + _, ok = data["msg"] if ok { - entry.Data["fields.msg"] = entry.Data["msg"] + data["fields.msg"] = data["msg"] } - _, ok = entry.Data["level"] + _, ok = data["level"] if ok { - entry.Data["fields.level"] = entry.Data["level"] + data["fields.level"] = data["level"] } } diff --git a/vendor/src/github.com/Sirupsen/logrus/hooks/airbrake/airbrake.go b/vendor/src/github.com/Sirupsen/logrus/hooks/airbrake/airbrake.go index 880d21ecdc..75f4db1513 100644 --- a/vendor/src/github.com/Sirupsen/logrus/hooks/airbrake/airbrake.go +++ b/vendor/src/github.com/Sirupsen/logrus/hooks/airbrake/airbrake.go @@ -9,7 +9,7 @@ import ( // with the Airbrake API. You must set: // * airbrake.Endpoint // * 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, // Fatal or Panic should now include an "error" field to send to Airbrake. diff --git a/vendor/src/github.com/Sirupsen/logrus/hooks/papertrail/papertrail.go b/vendor/src/github.com/Sirupsen/logrus/hooks/papertrail/papertrail.go index 48e2feaeb5..c0f10c1bda 100644 --- a/vendor/src/github.com/Sirupsen/logrus/hooks/papertrail/papertrail.go +++ b/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. func (hook *PapertrailHook) Fire(entry *logrus.Entry) error { 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)) if err != nil { diff --git a/vendor/src/github.com/Sirupsen/logrus/hooks/sentry/README.md b/vendor/src/github.com/Sirupsen/logrus/hooks/sentry/README.md new file mode 100644 index 0000000000..19e58bb457 --- /dev/null +++ b/vendor/src/github.com/Sirupsen/logrus/hooks/sentry/README.md @@ -0,0 +1,61 @@ +# Sentry Hook for Logrus :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 +``` diff --git a/vendor/src/github.com/Sirupsen/logrus/hooks/sentry/sentry.go b/vendor/src/github.com/Sirupsen/logrus/hooks/sentry/sentry.go new file mode 100644 index 0000000000..379f281c53 --- /dev/null +++ b/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 +} diff --git a/vendor/src/github.com/Sirupsen/logrus/hooks/sentry/sentry_test.go b/vendor/src/github.com/Sirupsen/logrus/hooks/sentry/sentry_test.go new file mode 100644 index 0000000000..45f18d1704 --- /dev/null +++ b/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) + } + }) +} diff --git a/vendor/src/github.com/Sirupsen/logrus/hooks/syslog/README.md b/vendor/src/github.com/Sirupsen/logrus/hooks/syslog/README.md index cd706bc1b1..4dbb8e7290 100644 --- a/vendor/src/github.com/Sirupsen/logrus/hooks/syslog/README.md +++ b/vendor/src/github.com/Sirupsen/logrus/hooks/syslog/README.md @@ -6,7 +6,7 @@ import ( "log/syslog" "github.com/Sirupsen/logrus" - "github.com/Sirupsen/logrus/hooks/syslog" + logrus_syslog "github.com/Sirupsen/logrus/hooks/syslog" ) func main() { @@ -17,4 +17,4 @@ func main() { log.Hooks.Add(hook) } } -``` \ No newline at end of file +``` diff --git a/vendor/src/github.com/Sirupsen/logrus/hooks/syslog/syslog.go b/vendor/src/github.com/Sirupsen/logrus/hooks/syslog/syslog.go index 2a18ce6130..b6fa374628 100644 --- a/vendor/src/github.com/Sirupsen/logrus/hooks/syslog/syslog.go +++ b/vendor/src/github.com/Sirupsen/logrus/hooks/syslog/syslog.go @@ -29,18 +29,18 @@ func (hook *SyslogHook) Fire(entry *logrus.Entry) error { return err } - switch entry.Data["level"] { - case "panic": + switch entry.Level { + case logrus.PanicLevel: return hook.Writer.Crit(line) - case "fatal": + case logrus.FatalLevel: return hook.Writer.Crit(line) - case "error": + case logrus.ErrorLevel: return hook.Writer.Err(line) - case "warn": + case logrus.WarnLevel: return hook.Writer.Warning(line) - case "info": + case logrus.InfoLevel: return hook.Writer.Info(line) - case "debug": + case logrus.DebugLevel: return hook.Writer.Debug(line) default: return nil diff --git a/vendor/src/github.com/Sirupsen/logrus/json_formatter.go b/vendor/src/github.com/Sirupsen/logrus/json_formatter.go index 9d11b642d4..0e38a61919 100644 --- a/vendor/src/github.com/Sirupsen/logrus/json_formatter.go +++ b/vendor/src/github.com/Sirupsen/logrus/json_formatter.go @@ -9,12 +9,22 @@ import ( type JSONFormatter struct{} 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 { return nil, fmt.Errorf("Failed to marshal fields to JSON, %v", err) } diff --git a/vendor/src/github.com/Sirupsen/logrus/json_formatter_test.go b/vendor/src/github.com/Sirupsen/logrus/json_formatter_test.go new file mode 100644 index 0000000000..1d70873254 --- /dev/null +++ b/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") + } +} diff --git a/vendor/src/github.com/Sirupsen/logrus/logger.go b/vendor/src/github.com/Sirupsen/logrus/logger.go index 7374fe365d..b392e547a7 100644 --- a/vendor/src/github.com/Sirupsen/logrus/logger.go +++ b/vendor/src/github.com/Sirupsen/logrus/logger.go @@ -38,7 +38,7 @@ type Logger struct { // Out: os.Stderr, // Formatter: new(JSONFormatter), // Hooks: make(levelHooks), -// Level: logrus.Debug, +// Level: logrus.DebugLevel, // } // // It's recommended to make this a global instance called `log`. diff --git a/vendor/src/github.com/Sirupsen/logrus/logrus_test.go b/vendor/src/github.com/Sirupsen/logrus/logrus_test.go index 15157d172d..d85dba4dcb 100644 --- a/vendor/src/github.com/Sirupsen/logrus/logrus_test.go +++ b/vendor/src/github.com/Sirupsen/logrus/logrus_test.go @@ -5,6 +5,7 @@ import ( "encoding/json" "strconv" "strings" + "sync" "testing" "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, "=") 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 } 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) { assert.Equal(t, "debug", DebugLevel.String()) assert.Equal(t, "info", InfoLevel.String()) @@ -245,3 +282,20 @@ func TestParseLevel(t *testing.T) { l, err = ParseLevel("invalid") 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() +} diff --git a/vendor/src/github.com/Sirupsen/logrus/terminal_notwindows.go b/vendor/src/github.com/Sirupsen/logrus/terminal_notwindows.go index 276447bd5c..b8bebc13ee 100644 --- a/vendor/src/github.com/Sirupsen/logrus/terminal_notwindows.go +++ b/vendor/src/github.com/Sirupsen/logrus/terminal_notwindows.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 linux,!appengine darwin freebsd +// +build linux darwin freebsd openbsd package logrus diff --git a/vendor/src/github.com/Sirupsen/logrus/terminal_openbsd.go b/vendor/src/github.com/Sirupsen/logrus/terminal_openbsd.go new file mode 100644 index 0000000000..d238bfa0b4 --- /dev/null +++ b/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 diff --git a/vendor/src/github.com/Sirupsen/logrus/text_formatter.go b/vendor/src/github.com/Sirupsen/logrus/text_formatter.go index fc0a4082a7..71dcb6617a 100644 --- a/vendor/src/github.com/Sirupsen/logrus/text_formatter.go +++ b/vendor/src/github.com/Sirupsen/logrus/text_formatter.go @@ -3,6 +3,7 @@ package logrus import ( "bytes" "fmt" + "regexp" "sort" "strings" "time" @@ -14,11 +15,13 @@ const ( green = 32 yellow = 33 blue = 34 + gray = 37 ) var ( baseTimestamp time.Time isTerminal bool + noQuoteNeeded *regexp.Regexp ) func init() { @@ -32,28 +35,47 @@ func miniTS() int { type TextFormatter struct { // Set to true to bypass checking for a TTY before outputting colors. - ForceColors bool + ForceColors bool + + // Force disabling colors. 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) { - - var keys []string + var keys []string = make([]string, 0, len(entry.Data)) for k := range entry.Data { keys = append(keys, k) } - sort.Strings(keys) + + if !f.DisableSorting { + sort.Strings(keys) + } b := &bytes.Buffer{} - prefixFieldClashes(entry) + prefixFieldClashes(entry.Data) isColored := (f.ForceColors || isTerminal) && !f.DisableColors if isColored { - printColored(b, entry, keys) + f.printColored(b, entry, keys) } 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, "msg", entry.Message) for _, key := range keys { @@ -65,9 +87,11 @@ func (f *TextFormatter) Format(entry *Entry) ([]byte, error) { 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 switch entry.Level { + case DebugLevel: + levelColor = gray case WarnLevel: levelColor = yellow 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] - 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 { v := entry.Data[k] 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{}) { 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: fmt.Fprintf(b, "%v=%v ", key, value) } diff --git a/vendor/src/github.com/Sirupsen/logrus/text_formatter_test.go b/vendor/src/github.com/Sirupsen/logrus/text_formatter_test.go new file mode 100644 index 0000000000..28a9499079 --- /dev/null +++ b/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. diff --git a/vendor/src/github.com/Sirupsen/logrus/writer.go b/vendor/src/github.com/Sirupsen/logrus/writer.go new file mode 100644 index 0000000000..90d3e01b45 --- /dev/null +++ b/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() +}