Merge upstream

This commit is contained in:
Didip Kerabat 2014-07-27 22:19:58 -07:00
commit 6d5b58f3d6
12 changed files with 472 additions and 102 deletions

1
.gitignore vendored Normal file
View File

@ -0,0 +1 @@
logrus

152
README.md
View File

@ -1,7 +1,10 @@
# 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)
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]. 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.**
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):
@ -42,9 +45,71 @@ time="2014-04-20 15:36:23.830626464 -0400 EDT" level="fatal" msg="The ice breaks
#### Example #### Example
Note again that Logrus is API compatible with the stdlib logger, so if you The simplest way to use Logrus is simply the package-level exported logger:
remove the `log` import and create a global `log` variable as below it will just
work. ```go
package main
import (
log "github.com/Sirupsen/logrus"
)
func main() {
log.WithFields(log.Fields{
"animal": "walrus",
}).Info("A walrus appears")
}
```
Note that it's completely api-compatible with the stdlib logger, so you can
replace your `log` imports everywhere with `log "github.com/Sirupsen/logrus"`
and you'll now have the flexibility of Logrus. You can customize it all you
want:
```go
package main
import (
"os"
log "github.com/Sirupsen/logrus"
"github.com/Sirupsen/logrus/hooks/airbrake"
)
func init() {
// Log as JSON instead of the default ASCII formatter.
log.SetFormatter(logrus.JSONFormatter)
// Use the Airbrake hook to report errors that have Error severity or above to
// an exception tracker. You can create custom hooks, see the Hooks section.
log.AddHook(logrus_airbrake.AirbrakeHook)
// Output to stderr instead of stdout, could also be a file.
log.SetOuput(os.Stderr)
// Only log the warning severity or above.
log.SetLevel(logrus.WarnLevel)
}
func main() {
log.WithFields(log.Fields{
"animal": "walrus",
"size": 10,
}).Info("A group of walrus emerges from the ocean")
log.WithFields(log.Fields{
"omg": true,
"number": 122,
}).Warn("The group's number increased tremendously!")
log.WithFields(log.Fields{
"omg": true,
"number": 100,
}).Fatal("The ice breaks!")
}
```
For more advanced usage such as logging to multiple locations from the same
application, you can also create an instance of the `logrus` Logger:
```go ```go
package main package main
@ -53,28 +118,18 @@ import (
"github.com/Sirupsen/logrus" "github.com/Sirupsen/logrus"
) )
// Create a new instance of the logger. You can have any number of instances.
var log = logrus.New() var log = logrus.New()
func init() {
log.Formatter = new(logrus.JSONFormatter)
log.Formatter = new(logrus.TextFormatter) // default
}
func main() { func main() {
log.WithFields(logrus.Fields{ // The API for setting attributes is a little different than the package level
// exported logger. See Godoc.
log.Out = os.Stderr
log.WithFields(log.Fields{
"animal": "walrus", "animal": "walrus",
"size": 10, "size": 10,
}).Info("A group of walrus emerges from the ocean") }).Info("A group of walrus emerges from the ocean")
log.WithFields(logrus.Fields{
"omg": true,
"number": 122,
}).Warn("The group's number increased tremendously!")
log.WithFields(logrus.Fields{
"omg": true,
"number": 100,
}).Fatal("The ice breaks!")
} }
``` ```
@ -86,12 +141,10 @@ to send event %s to topic %s with key %d")`, you should log the much more
discoverable: discoverable:
```go ```go
log = logrus.New() log.WithFields(log.Fields{
log.WithFields(logrus.Fields{
"event": event, "event": event,
"topic": topic, "topic": topic,
"key": key "key": key,
}).Fatal("Failed to send event") }).Fatal("Failed to send event")
``` ```
@ -112,10 +165,12 @@ multiple places simultaneously, e.g. syslog.
```go ```go
// Not the real implementation of the Airbrake hook. Just a simple sample. // Not the real implementation of the Airbrake hook. Just a simple sample.
var log = logrus.New() import (
log "github.com/Sirupsen/logrus"
)
func init() { func init() {
log.Hooks.Add(new(AirbrakeHook)) log.AddHook(new(AirbrakeHook))
} }
type AirbrakeHook struct{} type AirbrakeHook struct{}
@ -125,7 +180,7 @@ type AirbrakeHook struct{}
func (hook *AirbrakeHook) Fire(entry *logrus.Entry) error { func (hook *AirbrakeHook) Fire(entry *logrus.Entry) error {
err := airbrake.Notify(entry.Data["error"].(error)) err := airbrake.Notify(entry.Data["error"].(error))
if err != nil { if err != nil {
log.WithFields(logrus.Fields{ log.WithFields(log.Fields{
"source": "airbrake", "source": "airbrake",
"endpoint": airbrake.Endpoint, "endpoint": airbrake.Endpoint,
}).Info("Failed to send error to Airbrake") }).Info("Failed to send error to Airbrake")
@ -135,11 +190,11 @@ func (hook *AirbrakeHook) Fire(entry *logrus.Entry) error {
} }
// `Levels()` returns a slice of `Levels` the hook is fired for. // `Levels()` returns a slice of `Levels` the hook is fired for.
func (hook *AirbrakeHook) Levels() []logrus.Level { func (hook *AirbrakeHook) Levels() []log.Level {
return []logrus.Level{ return []log.Level{
logrus.Error, log.ErrorLevel,
logrus.Fatal, log.FatalLevel,
logrus.Panic, log.PanicLevel,
} }
} }
``` ```
@ -148,15 +203,14 @@ Logrus comes with built-in hooks. Add those, or your custom hook, in `init`:
```go ```go
import ( import (
"log/syslog" log "github.com/Sirupsen/logrus"
"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"
) )
func init() { func init() {
log.Hooks.Add(new(logrus_airbrake.AirbrakeHook)) log.AddHook(new(logrus_airbrake.AirbrakeHook))
log.Hooks.Add(logrus_syslog.NewSyslogHook("udp", "localhost:514", syslog.LOG_INFO, "")) log.AddHook(logrus_syslog.NewSyslogHook("udp", "localhost:514", syslog.LOG_INFO, ""))
} }
``` ```
@ -188,10 +242,10 @@ that severity or anything above it:
```go ```go
// Will log anything that is info or above (warn, error, fatal, panic). Default. // Will log anything that is info or above (warn, error, fatal, panic). Default.
log.Level = logrus.Info log.SetLevel(log.InfoLevel)
``` ```
It may be useful to set `log.Level = logrus.Debug` in a debug or verbose It may be useful to set `log.Level = logrus.DebugLevel` in a debug or verbose
environment if your application has that. environment if your application has that.
#### Entries #### Entries
@ -214,16 +268,18 @@ variable `Environment`, which is a string representation of the environment you
could do: could do:
```go ```go
import (
log "github.com/Sirupsen/logrus"
)
init() { init() {
// do something here to set environment depending on an environment variable // do something here to set environment depending on an environment variable
// or command-line flag // or command-line flag
log := logrus.New()
if Environment == "production" { if Environment == "production" {
log.Formatter = new(logrus.JSONFormatter) log.SetFormatter(logrus.JSONFormatter)
} else { } else {
// The TextFormatter is default, you don't actually have to do this. // The TextFormatter is default, you don't actually have to do this.
log.Formatter = new(logrus.TextFormatter) log.SetFormatter(logrus.TextFormatter)
} }
} }
``` ```
@ -255,9 +311,12 @@ default ones (see Entries section above):
type MyJSONFormatter struct { type MyJSONFormatter struct {
} }
log.Formatter = new(MyJSONFormatter) log.SetFormatter(new(MyJSONFormatter))
func (f *JSONFormatter) Format(entry *Entry) ([]byte, error) { func (f *JSONFormatter) Format(entry *Entry) ([]byte, error) {
// Note this doesn't include Time, Level and Message which are available on
// the Entry. Consult `godoc` on information about those fields or read the
// source of the official loggers.
serialized, err := json.Marshal(entry.Data) serialized, err := json.Marshal(entry.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)
@ -266,4 +325,11 @@ func (f *JSONFormatter) Format(entry *Entry) ([]byte, error) {
} }
``` ```
#### 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
entries. It should not be a feature of the application-level logger.
[godoc]: https://godoc.org/github.com/Sirupsen/logrus [godoc]: https://godoc.org/github.com/Sirupsen/logrus

View File

@ -8,9 +8,24 @@ import (
"time" "time"
) )
// An entry is the final or intermediate Logrus logging entry. It containts all
// the fields passed with WithField{,s}. It's finally logged when Debug, Info,
// Warn, Error, Fatal or Panic is called on it. These objects can be reused and
// passed around as much as you wish to avoid field duplication.
type Entry struct { type Entry struct {
Logger *Logger Logger *Logger
// Contains all the fields set by the user.
Data Fields Data Fields
// Time at which the log entry was created
Time time.Time
// Level the log entry was logged at: Debug, Info, Warn, Error, Fatal or Panic
Level Level
// Message passed to Debug, Info, Warn, Error, Fatal or Panic
Message string
} }
var baseTimestamp time.Time var baseTimestamp time.Time
@ -23,11 +38,14 @@ func NewEntry(logger *Logger) *Entry {
} }
} }
// Returns a reader for the entry, which is a proxy to the formatter.
func (entry *Entry) Reader() (*bytes.Buffer, error) { func (entry *Entry) Reader() (*bytes.Buffer, error) {
serialized, err := entry.Logger.Formatter.Format(entry) serialized, err := entry.Logger.Formatter.Format(entry)
return bytes.NewBuffer(serialized), err return bytes.NewBuffer(serialized), err
} }
// Returns the string representation from the reader and ultimately the
// formatter.
func (entry *Entry) String() (string, error) { func (entry *Entry) String() (string, error) {
reader, err := entry.Reader() reader, err := entry.Reader()
if err != nil { if err != nil {
@ -37,10 +55,12 @@ func (entry *Entry) String() (string, error) {
return reader.String(), err return reader.String(), err
} }
// Add a single field to the Entry.
func (entry *Entry) WithField(key string, value interface{}) *Entry { func (entry *Entry) WithField(key string, value interface{}) *Entry {
return entry.WithFields(Fields{key: value}) return entry.WithFields(Fields{key: value})
} }
// Add a map of fields to the Entry.
func (entry *Entry) WithFields(fields Fields) *Entry { func (entry *Entry) WithFields(fields Fields) *Entry {
data := Fields{} data := Fields{}
for k, v := range entry.Data { for k, v := range entry.Data {
@ -52,12 +72,12 @@ func (entry *Entry) WithFields(fields Fields) *Entry {
return &Entry{Logger: entry.Logger, Data: data} return &Entry{Logger: entry.Logger, Data: data}
} }
func (entry *Entry) log(level string, levelInt Level, msg string) string { func (entry *Entry) log(level Level, msg string) string {
entry.Data["time"] = time.Now().String() entry.Time = time.Now()
entry.Data["level"] = level entry.Level = level
entry.Data["msg"] = msg entry.Message = msg
if err := entry.Logger.Hooks.Fire(levelInt, entry); err != nil { if err := entry.Logger.Hooks.Fire(level, entry); err != nil {
fmt.Fprintf(os.Stderr, "Failed to fire hook", err) fmt.Fprintf(os.Stderr, "Failed to fire hook", err)
} }
@ -78,8 +98,8 @@ func (entry *Entry) log(level string, levelInt Level, msg string) string {
} }
func (entry *Entry) Debug(args ...interface{}) { func (entry *Entry) Debug(args ...interface{}) {
if entry.Logger.Level >= Debug { if entry.Logger.Level >= DebugLevel {
entry.log("debug", Debug, fmt.Sprint(args...)) entry.log(DebugLevel, fmt.Sprint(args...))
} }
} }
@ -88,33 +108,33 @@ func (entry *Entry) Print(args ...interface{}) {
} }
func (entry *Entry) Info(args ...interface{}) { func (entry *Entry) Info(args ...interface{}) {
if entry.Logger.Level >= Info { if entry.Logger.Level >= InfoLevel {
entry.log("info", Info, fmt.Sprint(args...)) entry.log(InfoLevel, fmt.Sprint(args...))
} }
} }
func (entry *Entry) Warn(args ...interface{}) { func (entry *Entry) Warn(args ...interface{}) {
if entry.Logger.Level >= Warn { if entry.Logger.Level >= WarnLevel {
entry.log("warning", Warn, fmt.Sprint(args...)) entry.log(WarnLevel, fmt.Sprint(args...))
} }
} }
func (entry *Entry) Error(args ...interface{}) { func (entry *Entry) Error(args ...interface{}) {
if entry.Logger.Level >= Error { if entry.Logger.Level >= ErrorLevel {
entry.log("error", Error, fmt.Sprint(args...)) entry.log(ErrorLevel, fmt.Sprint(args...))
} }
} }
func (entry *Entry) Fatal(args ...interface{}) { func (entry *Entry) Fatal(args ...interface{}) {
if entry.Logger.Level >= Fatal { if entry.Logger.Level >= FatalLevel {
entry.log("fatal", Fatal, fmt.Sprint(args...)) entry.log(FatalLevel, fmt.Sprint(args...))
} }
os.Exit(1) os.Exit(1)
} }
func (entry *Entry) Panic(args ...interface{}) { func (entry *Entry) Panic(args ...interface{}) {
if entry.Logger.Level >= Panic { if entry.Logger.Level >= PanicLevel {
msg := entry.log("panic", Panic, fmt.Sprint(args...)) msg := entry.log(PanicLevel, fmt.Sprint(args...))
panic(msg) panic(msg)
} }
panic(fmt.Sprint(args...)) panic(fmt.Sprint(args...))
@ -123,13 +143,13 @@ func (entry *Entry) Panic(args ...interface{}) {
// Entry Printf family functions // Entry Printf family functions
func (entry *Entry) Debugf(format string, args ...interface{}) { func (entry *Entry) Debugf(format string, args ...interface{}) {
if entry.Logger.Level >= Debug { if entry.Logger.Level >= DebugLevel {
entry.Debug(fmt.Sprintf(format, args...)) entry.Debug(fmt.Sprintf(format, args...))
} }
} }
func (entry *Entry) Infof(format string, args ...interface{}) { func (entry *Entry) Infof(format string, args ...interface{}) {
if entry.Logger.Level >= Info { if entry.Logger.Level >= InfoLevel {
entry.Info(fmt.Sprintf(format, args...)) entry.Info(fmt.Sprintf(format, args...))
} }
} }
@ -139,7 +159,7 @@ func (entry *Entry) Printf(format string, args ...interface{}) {
} }
func (entry *Entry) Warnf(format string, args ...interface{}) { func (entry *Entry) Warnf(format string, args ...interface{}) {
if entry.Logger.Level >= Warn { if entry.Logger.Level >= WarnLevel {
entry.Warn(fmt.Sprintf(format, args...)) entry.Warn(fmt.Sprintf(format, args...))
} }
} }
@ -149,19 +169,19 @@ func (entry *Entry) Warningf(format string, args ...interface{}) {
} }
func (entry *Entry) Errorf(format string, args ...interface{}) { func (entry *Entry) Errorf(format string, args ...interface{}) {
if entry.Logger.Level >= Error { if entry.Logger.Level >= ErrorLevel {
entry.Error(fmt.Sprintf(format, args...)) entry.Error(fmt.Sprintf(format, args...))
} }
} }
func (entry *Entry) Fatalf(format string, args ...interface{}) { func (entry *Entry) Fatalf(format string, args ...interface{}) {
if entry.Logger.Level >= Fatal { if entry.Logger.Level >= FatalLevel {
entry.Fatal(fmt.Sprintf(format, args...)) entry.Fatal(fmt.Sprintf(format, args...))
} }
} }
func (entry *Entry) Panicf(format string, args ...interface{}) { func (entry *Entry) Panicf(format string, args ...interface{}) {
if entry.Logger.Level >= Panic { if entry.Logger.Level >= PanicLevel {
entry.Panic(fmt.Sprintf(format, args...)) entry.Panic(fmt.Sprintf(format, args...))
} }
} }
@ -169,13 +189,13 @@ func (entry *Entry) Panicf(format string, args ...interface{}) {
// Entry Println family functions // Entry Println family functions
func (entry *Entry) Debugln(args ...interface{}) { func (entry *Entry) Debugln(args ...interface{}) {
if entry.Logger.Level >= Debug { if entry.Logger.Level >= DebugLevel {
entry.Debug(entry.sprintlnn(args...)) entry.Debug(entry.sprintlnn(args...))
} }
} }
func (entry *Entry) Infoln(args ...interface{}) { func (entry *Entry) Infoln(args ...interface{}) {
if entry.Logger.Level >= Info { if entry.Logger.Level >= InfoLevel {
entry.Info(entry.sprintlnn(args...)) entry.Info(entry.sprintlnn(args...))
} }
} }
@ -185,7 +205,7 @@ func (entry *Entry) Println(args ...interface{}) {
} }
func (entry *Entry) Warnln(args ...interface{}) { func (entry *Entry) Warnln(args ...interface{}) {
if entry.Logger.Level >= Warn { if entry.Logger.Level >= WarnLevel {
entry.Warn(entry.sprintlnn(args...)) entry.Warn(entry.sprintlnn(args...))
} }
} }
@ -195,19 +215,19 @@ func (entry *Entry) Warningln(args ...interface{}) {
} }
func (entry *Entry) Errorln(args ...interface{}) { func (entry *Entry) Errorln(args ...interface{}) {
if entry.Logger.Level >= Error { if entry.Logger.Level >= ErrorLevel {
entry.Error(entry.sprintlnn(args...)) entry.Error(entry.sprintlnn(args...))
} }
} }
func (entry *Entry) Fatalln(args ...interface{}) { func (entry *Entry) Fatalln(args ...interface{}) {
if entry.Logger.Level >= Fatal { if entry.Logger.Level >= FatalLevel {
entry.Fatal(entry.sprintlnn(args...)) entry.Fatal(entry.sprintlnn(args...))
} }
} }
func (entry *Entry) Panicln(args ...interface{}) { func (entry *Entry) Panicln(args ...interface{}) {
if entry.Logger.Level >= Panic { if entry.Logger.Level >= PanicLevel {
entry.Panic(entry.sprintlnn(args...)) entry.Panic(entry.sprintlnn(args...))
} }
} }

177
exported.go Normal file
View File

@ -0,0 +1,177 @@
package logrus
import (
"io"
)
var (
// std is the name of the standard logger in stdlib `log`
std = New()
)
// SetOutput sets the standard logger output.
func SetOutput(out io.Writer) {
std.mu.Lock()
defer std.mu.Unlock()
std.Out = out
}
// SetFormatter sets the standard logger formatter.
func SetFormatter(formatter Formatter) {
std.mu.Lock()
defer std.mu.Unlock()
std.Formatter = formatter
}
// SetLevel sets the standard logger level.
func SetLevel(level Level) {
std.mu.Lock()
defer std.mu.Unlock()
std.Level = level
}
// AddHook adds a hook to the standard logger hooks.
func AddHook(hook Hook) {
std.mu.Lock()
defer std.mu.Unlock()
std.Hooks.Add(hook)
}
// WithField creates an entry from the standard logger and adds a field to
// it. If you want multiple fields, use `WithFields`.
//
// Note that it doesn't log until you call Debug, Print, Info, Warn, Fatal
// or Panic on the Entry it returns.
func WithField(key string, value interface{}) *Entry {
return std.WithField(key, value)
}
// WithFields creates an entry from the standard logger and adds multiple
// fields to it. This is simply a helper for `WithField`, invoking it
// once for each field.
//
// Note that it doesn't log until you call Debug, Print, Info, Warn, Fatal
// or Panic on the Entry it returns.
func WithFields(fields Fields) *Entry {
return std.WithFields(fields)
}
// Debug logs a message at level Debug on the standard logger.
func Debug(args ...interface{}) {
std.Debug(args...)
}
// Print logs a message at level Info on the standard logger.
func Print(args ...interface{}) {
std.Print(args...)
}
// Info logs a message at level Info on the standard logger.
func Info(args ...interface{}) {
std.Info(args...)
}
// Warn logs a message at level Warn on the standard logger.
func Warn(args ...interface{}) {
std.Warn(args...)
}
// Warning logs a message at level Warn on the standard logger.
func Warning(args ...interface{}) {
std.Warning(args...)
}
// Error logs a message at level Error on the standard logger.
func Error(args ...interface{}) {
std.Error(args...)
}
// Panic logs a message at level Panic on the standard logger.
func Panic(args ...interface{}) {
std.Panic(args...)
}
// Fatal logs a message at level Fatal on the standard logger.
func Fatal(args ...interface{}) {
std.Fatal(args...)
}
// Debugf logs a message at level Debugf on the standard logger.
func Debugf(format string, args ...interface{}) {
std.Debugf(format, args...)
}
// Printf logs a message at level Info on the standard logger.
func Printf(format string, args ...interface{}) {
std.Printf(format, args...)
}
// Infof logs a message at level Info on the standard logger.
func Infof(format string, args ...interface{}) {
std.Infof(format, args...)
}
// Warnf logs a message at level Warn on the standard logger.
func Warnf(format string, args ...interface{}) {
std.Warnf(format, args...)
}
// Warningf logs a message at level Warn on the standard logger.
func Warningf(format string, args ...interface{}) {
std.Warningf(format, args...)
}
// Errorf logs a message at level Error on the standard logger.
func Errorf(format string, args ...interface{}) {
std.Errorf(format, args...)
}
// Panicf logs a message at level Pancf on the standard logger.
func Panicf(format string, args ...interface{}) {
std.Panicf(format, args...)
}
// Fatalf logs a message at level Fatal on the standard logger.
func Fatalf(format string, args ...interface{}) {
std.Fatalf(format, args...)
}
// Debugln logs a message at level Debug on the standard logger.
func Debugln(args ...interface{}) {
std.Debugln(args...)
}
// Println logs a message at level Info on the standard logger.
func Println(args ...interface{}) {
std.Println(args...)
}
// Infoln logs a message at level Info on the standard logger.
func Infoln(args ...interface{}) {
std.Infoln(args...)
}
// Warnln logs a message at level Warn on the standard logger.
func Warnln(args ...interface{}) {
std.Warnln(args...)
}
// Warningln logs a message at level Warn on the standard logger.
func Warningln(args ...interface{}) {
std.Warningln(args...)
}
// Errorln logs a message at level Error on the standard logger.
func Errorln(args ...interface{}) {
std.Errorln(args...)
}
// Panicln logs a message at level Panic on the standard logger.
func Panicln(args ...interface{}) {
std.Panicln(args...)
}
// Fatalln logs a message at level Fatal on the standard logger.
func Fatalln(args ...interface{}) {
std.Fatalln(args...)
}

View File

@ -1,5 +1,9 @@
package logrus package logrus
import (
"time"
)
// The Formatter interface is used to implement a custom Formatter. It takes an // The Formatter interface is used to implement a custom Formatter. It takes an
// `Entry`. It exposes all the fields, including the default ones: // `Entry`. It exposes all the fields, including the default ones:
// //
@ -13,3 +17,38 @@ package logrus
type Formatter interface { type Formatter interface {
Format(*Entry) ([]byte, error) Format(*Entry) ([]byte, error)
} }
// This is to not silently overwrite `time`, `msg` and `level` fields when
// dumping it. If this code wasn't there doing:
//
// logrus.WithField("level", 1).Info("hello")
//
// Would just silently drop the user provided level. Instead with this code
// it'll logged as:
//
// {"level": "info", "fields.level": 1, "msg": "hello", "time": "..."}
//
// It's not exported because it's still using Data in an opionated way. It's to
// avoid code duplication between the two default formatters.
func prefixFieldClashes(entry *Entry) {
_, ok := entry.Data["time"]
if ok {
entry.Data["fields.time"] = entry.Data["time"]
}
entry.Data["time"] = entry.Time.Format(time.RFC3339)
_, ok = entry.Data["msg"]
if ok {
entry.Data["fields.msg"] = entry.Data["msg"]
}
entry.Data["msg"] = entry.Message
_, ok = entry.Data["level"]
if ok {
entry.Data["fields.level"] = entry.Data["level"]
}
entry.Data["level"] = entry.Level.String()
}

View File

@ -17,12 +17,12 @@ func (hook *TestHook) Fire(entry *Entry) error {
func (hook *TestHook) Levels() []Level { func (hook *TestHook) Levels() []Level {
return []Level{ return []Level{
Debug, DebugLevel,
Info, InfoLevel,
Warn, WarnLevel,
Error, ErrorLevel,
Fatal, FatalLevel,
Panic, PanicLevel,
} }
} }
@ -49,12 +49,12 @@ func (hook *ModifyHook) Fire(entry *Entry) error {
func (hook *ModifyHook) Levels() []Level { func (hook *ModifyHook) Levels() []Level {
return []Level{ return []Level{
Debug, DebugLevel,
Info, InfoLevel,
Warn, WarnLevel,
Error, ErrorLevel,
Fatal, FatalLevel,
Panic, PanicLevel,
} }
} }
@ -95,7 +95,7 @@ func (hook *ErrorHook) Fire(entry *Entry) error {
func (hook *ErrorHook) Levels() []Level { func (hook *ErrorHook) Levels() []Level {
return []Level{ return []Level{
Error, ErrorLevel,
} }
} }

View File

@ -47,8 +47,8 @@ func (hook *AirbrakeHook) Fire(entry *logrus.Entry) error {
func (hook *AirbrakeHook) Levels() []logrus.Level { func (hook *AirbrakeHook) Levels() []logrus.Level {
return []logrus.Level{ return []logrus.Level{
logrus.Error, logrus.ErrorLevel,
logrus.Fatal, logrus.FatalLevel,
logrus.Panic, logrus.PanicLevel,
} }
} }

View File

@ -9,6 +9,8 @@ type JSONFormatter struct {
} }
func (f *JSONFormatter) Format(entry *Entry) ([]byte, error) { func (f *JSONFormatter) Format(entry *Entry) ([]byte, error) {
prefixFieldClashes(entry)
serialized, err := json.Marshal(entry.Data) serialized, err := json.Marshal(entry.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

@ -47,7 +47,7 @@ func New() *Logger {
Out: os.Stdout, Out: os.Stdout,
Formatter: new(TextFormatter), Formatter: new(TextFormatter),
Hooks: make(levelHooks), Hooks: make(levelHooks),
Level: Info, Level: InfoLevel,
} }
} }

View File

@ -10,25 +10,45 @@ type Fields map[string]interface{}
// Level type // Level type
type Level uint8 type Level uint8
// Convert the Level to a string. E.g. PanicLevel becomes "panic".
func (level Level) String() string {
switch level {
case DebugLevel:
return "debug"
case InfoLevel:
return "info"
case WarnLevel:
return "warning"
case ErrorLevel:
return "error"
case FatalLevel:
return "fatal"
case PanicLevel:
return "panic"
}
return "unknown"
}
// These are the different logging levels. You can set the logging level to log // These are the different logging levels. You can set the logging level to log
// on your instance of logger, obtained with `logrus.New()`. // on your instance of logger, obtained with `logrus.New()`.
const ( const (
// Panic level, highest level of severity. Logs and then calls panic with the // PanicLevel level, highest level of severity. Logs and then calls panic with the
// message passed to Debug, Info, ... // message passed to Debug, Info, ...
Panic Level = iota PanicLevel Level = iota
// Fatal level. Logs and then calls `os.Exit(1)`. It will exit even if the // FatalLevel level. Logs and then calls `os.Exit(1)`. It will exit even if the
// logging level is set to Panic. // logging level is set to Panic.
Fatal FatalLevel
// Error level. Logs. Used for errors that should definitely be noted. // ErrorLevel level. Logs. Used for errors that should definitely be noted.
// Commonly used for hooks to send errors to an error tracking service. // Commonly used for hooks to send errors to an error tracking service.
Error ErrorLevel
// Warn level. Non-critical entries that deserve eyes. // WarnLevel level. Non-critical entries that deserve eyes.
Warn WarnLevel
// Info level. General operational entries about what's going on inside the // InfoLevel level. General operational entries about what's going on inside the
// application. // application.
Info InfoLevel
// Debug level. Usually only enabled when debugging. Very verbose logging. // DebugLevel level. Usually only enabled when debugging. Very verbose logging.
Debug DebugLevel
) )
// Won't compile if StdLogger can't be realized by a log.Logger // Won't compile if StdLogger can't be realized by a log.Logger

View File

@ -128,3 +128,46 @@ func TestWithFieldsShouldAllowAssignments(t *testing.T) {
assert.Equal(t, false, ok) assert.Equal(t, false, ok)
assert.Equal(t, "value1", fields["key1"]) assert.Equal(t, "value1", fields["key1"])
} }
func TestUserSuppliedFieldDoesNotOverwriteDefaults(t *testing.T) {
LogAndAssertJSON(t, func(log *Logger) {
log.WithField("msg", "hello").Info("test")
}, func(fields Fields) {
assert.Equal(t, fields["msg"], "test")
})
}
func TestUserSuppliedMsgFieldHasPrefix(t *testing.T) {
LogAndAssertJSON(t, func(log *Logger) {
log.WithField("msg", "hello").Info("test")
}, func(fields Fields) {
assert.Equal(t, fields["msg"], "test")
assert.Equal(t, fields["fields.msg"], "hello")
})
}
func TestUserSuppliedTimeFieldHasPrefix(t *testing.T) {
LogAndAssertJSON(t, func(log *Logger) {
log.WithField("time", "hello").Info("test")
}, func(fields Fields) {
assert.Equal(t, fields["fields.time"], "hello")
})
}
func TestUserSuppliedLevelFieldHasPrefix(t *testing.T) {
LogAndAssertJSON(t, func(log *Logger) {
log.WithField("level", 1).Info("test")
}, func(fields Fields) {
assert.Equal(t, fields["level"], "info")
assert.Equal(t, fields["fields.level"], 1)
})
}
func TestConvertLevelToString(t *testing.T) {
assert.Equal(t, "debug", DebugLevel.String())
assert.Equal(t, "info", InfoLevel.String())
assert.Equal(t, "warning", WarnLevel.String())
assert.Equal(t, "error", ErrorLevel.String())
assert.Equal(t, "fatal", FatalLevel.String())
assert.Equal(t, "panic", PanicLevel.String())
}

View File

@ -32,6 +32,8 @@ type TextFormatter struct {
func (f *TextFormatter) Format(entry *Entry) ([]byte, error) { func (f *TextFormatter) Format(entry *Entry) ([]byte, error) {
b := &bytes.Buffer{} b := &bytes.Buffer{}
prefixFieldClashes(entry)
if f.ForceColors || IsTerminal() { if f.ForceColors || IsTerminal() {
levelText := strings.ToUpper(entry.Data["level"].(string))[0:4] levelText := strings.ToUpper(entry.Data["level"].(string))[0:4]