Update logrus dependency

This dependency added a method to access the current standard logger. This is
required to properly configure the logger for context awareness. The plan is to
have all loggers descend from the standard logger.

Signed-off-by: Stephen J Day <stephen.day@docker.com>
This commit is contained in:
Stephen J Day 2015-02-05 12:00:07 -08:00
parent 29135602ec
commit 13382e45ba
13 changed files with 133 additions and 33 deletions

4
Godeps/Godeps.json generated
View File

@ -24,8 +24,8 @@
}, },
{ {
"ImportPath": "github.com/Sirupsen/logrus", "ImportPath": "github.com/Sirupsen/logrus",
"Comment": "v0.6.1-8-gcc09837", "Comment": "v0.6.4-12-g467d9d5",
"Rev": "cc09837bcd512ffe6bb2e3f635bed138c4cd6bc8" "Rev": "467d9d55c2d2c17248441a8fc661561161f40d5e"
}, },
{ {
"ImportPath": "github.com/bugsnag/bugsnag-go", "ImportPath": "github.com/bugsnag/bugsnag-go",

View File

@ -2,9 +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 -t ./...
- go get github.com/stvp/go-udp-testing
- go get github.com/tobi/airbrake-go
- go get github.com/getsentry/raven-go

View File

@ -1,8 +1,8 @@
# 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 yet stable (pre 1.0), 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 control your Logrus to make sure you aren't fetching latest `master` on every
build.** build.**
@ -33,7 +33,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
@ -235,6 +235,12 @@ 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.
#### 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.
@ -314,7 +320,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
@ -339,6 +345,24 @@ 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 responsability 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

View File

@ -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...))

View File

@ -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()

View File

@ -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) { func prefixFieldClashes(data Fields) {
_, ok := entry.Data["time"] _, 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"]
} }
} }

View File

@ -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.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 {

View File

@ -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() {

View File

@ -9,12 +9,16 @@ 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) data := make(Fields, len(entry.Data)+3)
entry.Data["time"] = entry.Time.Format(time.RFC3339) for k, v := range entry.Data {
entry.Data["msg"] = entry.Message data[k] = v
entry.Data["level"] = entry.Level.String() }
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)
} }

View File

@ -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`.

View File

@ -44,8 +44,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]) val := kvArr[1]
assert.NoError(t, err) 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 +208,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())

View File

@ -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,!appengine darwin freebsd openbsd
package logrus package logrus

View File

@ -3,6 +3,7 @@ package logrus
import ( import (
"bytes" "bytes"
"fmt" "fmt"
"regexp"
"sort" "sort"
"strings" "strings"
"time" "time"
@ -19,6 +20,7 @@ const (
var ( var (
baseTimestamp time.Time baseTimestamp time.Time
isTerminal bool isTerminal bool
noQuoteNeeded *regexp.Regexp
) )
func init() { func init() {
@ -34,6 +36,9 @@ 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
DisableColors bool DisableColors bool
// Set to true to disable timestamp logging (useful when the output
// is redirected to a logging system already adding a timestamp)
DisableTimestamp bool
} }
func (f *TextFormatter) Format(entry *Entry) ([]byte, error) { func (f *TextFormatter) Format(entry *Entry) ([]byte, error) {
@ -46,14 +51,16 @@ func (f *TextFormatter) Format(entry *Entry) ([]byte, error) {
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) 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 {
@ -85,10 +92,32 @@ func printColored(b *bytes.Buffer, entry *Entry, keys []string) {
} }
} }
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: case string:
fmt.Fprintf(b, "%v=%q ", key, value) 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)
} }