Merge pull request #11331 from jfrazelle/update-logrus

Update logrus to 0.6.6
This commit is contained in:
Michael Crosby 2015-03-11 16:18:22 -07:00
Родитель 254b6f8946 89e2e5fc55
Коммит 5494432f91
26 изменённых файлов: 758 добавлений и 66 удалений

Просмотреть файл

@ -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="`):] e1 := err1.Error()[strings.Index(err1.Error(), `level=`):]
e2 := err2.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="`):] e1 = err1.Error()[strings.Index(err1.Error(), `level=`):]
e2 = err2.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="`):] e1 = err1.Error()[strings.Index(err1.Error(), `level=`):]
e2 = err2.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) {

Просмотреть файл

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

Просмотреть файл

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

Просмотреть файл

@ -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 yet stable (pre 1.0). Logrus itself is completely stable and has been used in
control your Logrus to make sure you aren't fetching latest `master` on every many large deployments. The core API is unlikely to change much but please
build.** 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.

Просмотреть файл

@ -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
vendor/src/github.com/Sirupsen/logrus/entry_test.go поставляемый Normal file
Просмотреть файл

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

Просмотреть файл

@ -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, "temperature": -4,
"number": 100, }).Debug("Temperature changes")
}).Fatal("The ice breaks!")
log.WithFields(logrus.Fields{
"animal": "orca",
"size": 9009,
}).Panic("It's over 9000!")
} }

Просмотреть файл

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

Просмотреть файл

@ -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"]
} }
} }

Просмотреть файл

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

Просмотреть файл

@ -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
vendor/src/github.com/Sirupsen/logrus/hooks/sentry/README.md поставляемый Normal file
Просмотреть файл

@ -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
vendor/src/github.com/Sirupsen/logrus/hooks/sentry/sentry.go поставляемый Normal file
Просмотреть файл

@ -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
}

Просмотреть файл

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

Просмотреть файл

@ -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)
} }
} }
``` ```

Просмотреть файл

@ -29,18 +29,18 @@ func (hook *SyslogHook) Fire(entry *logrus.Entry) error {
return err return err
} }
switch entry.Data["level"] { switch entry.Level {
case "panic": 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

Просмотреть файл

@ -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) 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 // Otherwise errors are ignored by `encoding/json`
entry.Data["level"] = entry.Level.String() // 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
vendor/src/github.com/Sirupsen/logrus/json_formatter_test.go поставляемый Normal file
Просмотреть файл

@ -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")
}
}

Просмотреть файл

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

Просмотреть файл

@ -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]) 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 +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()
}

Просмотреть файл

@ -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
vendor/src/github.com/Sirupsen/logrus/terminal_openbsd.go поставляемый Normal file
Просмотреть файл

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

Просмотреть файл

@ -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 = make([]string, 0, len(entry.Data))
var keys []string
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: 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)
} }

37
vendor/src/github.com/Sirupsen/logrus/text_formatter_test.go поставляемый Normal file
Просмотреть файл

@ -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
vendor/src/github.com/Sirupsen/logrus/writer.go поставляемый Normal file
Просмотреть файл

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