diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..66be63a --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +logrus diff --git a/README.md b/README.md index ef107e7..3a6aced 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,10 @@ # Logrus :walrus: [![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 -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 plain text): @@ -42,9 +45,71 @@ time="2014-04-20 15:36:23.830626464 -0400 EDT" level="fatal" msg="The ice breaks #### Example -Note again that Logrus is API compatible with the stdlib logger, so if you -remove the `log` import and create a global `log` variable as below it will just -work. +The simplest way to use Logrus is simply the package-level exported logger: + +```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 package main @@ -53,28 +118,18 @@ import ( "github.com/Sirupsen/logrus" ) +// Create a new instance of the logger. You can have any number of instances. var log = logrus.New() -func init() { - log.Formatter = new(logrus.JSONFormatter) - log.Formatter = new(logrus.TextFormatter) // default -} - 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", "size": 10, }).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: ```go -log = logrus.New() - -log.WithFields(logrus.Fields{ +log.WithFields(log.Fields{ "event": event, "topic": topic, - "key": key + "key": key, }).Fatal("Failed to send event") ``` @@ -112,10 +165,12 @@ multiple places simultaneously, e.g. syslog. ```go // Not the real implementation of the Airbrake hook. Just a simple sample. -var log = logrus.New() +import ( + log "github.com/Sirupsen/logrus" +) func init() { - log.Hooks.Add(new(AirbrakeHook)) + log.AddHook(new(AirbrakeHook)) } type AirbrakeHook struct{} @@ -125,7 +180,7 @@ type AirbrakeHook struct{} func (hook *AirbrakeHook) Fire(entry *logrus.Entry) error { err := airbrake.Notify(entry.Data["error"].(error)) if err != nil { - log.WithFields(logrus.Fields{ + log.WithFields(log.Fields{ "source": "airbrake", "endpoint": airbrake.Endpoint, }).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. -func (hook *AirbrakeHook) Levels() []logrus.Level { - return []logrus.Level{ - logrus.Error, - logrus.Fatal, - logrus.Panic, +func (hook *AirbrakeHook) Levels() []log.Level { + return []log.Level{ + log.ErrorLevel, + log.FatalLevel, + log.PanicLevel, } } ``` @@ -148,15 +203,14 @@ Logrus comes with built-in hooks. Add those, or your custom hook, in `init`: ```go import ( - "log/syslog" - "github.com/Sirupsen/logrus" + log "github.com/Sirupsen/logrus" "github.com/Sirupsen/logrus/hooks/airbrake" "github.com/Sirupsen/logrus/hooks/syslog" ) func init() { - log.Hooks.Add(new(logrus_airbrake.AirbrakeHook)) - log.Hooks.Add(logrus_syslog.NewSyslogHook("udp", "localhost:514", syslog.LOG_INFO, "")) + log.AddHook(new(logrus_airbrake.AirbrakeHook)) + log.AddHook(logrus_syslog.NewSyslogHook("udp", "localhost:514", syslog.LOG_INFO, "")) } ``` @@ -188,10 +242,10 @@ that severity or anything above it: ```go // 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. #### Entries @@ -214,16 +268,18 @@ variable `Environment`, which is a string representation of the environment you could do: ```go +import ( + log "github.com/Sirupsen/logrus" +) + init() { // do something here to set environment depending on an environment variable // or command-line flag - log := logrus.New() - if Environment == "production" { - log.Formatter = new(logrus.JSONFormatter) + log.SetFormatter(logrus.JSONFormatter) } else { // 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 { } -log.Formatter = new(MyJSONFormatter) +log.SetFormatter(new(MyJSONFormatter)) 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) if err != nil { 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 diff --git a/entry.go b/entry.go index 08cc15f..44ff056 100644 --- a/entry.go +++ b/entry.go @@ -8,9 +8,24 @@ import ( "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 { Logger *Logger - Data Fields + + // Contains all the fields set by the user. + 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 @@ -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) { serialized, err := entry.Logger.Formatter.Format(entry) return bytes.NewBuffer(serialized), err } +// Returns the string representation from the reader and ultimately the +// formatter. func (entry *Entry) String() (string, error) { reader, err := entry.Reader() if err != nil { @@ -37,10 +55,12 @@ func (entry *Entry) String() (string, error) { return reader.String(), err } +// Add a single field to the Entry. func (entry *Entry) WithField(key string, value interface{}) *Entry { return entry.WithFields(Fields{key: value}) } +// Add a map of fields to the Entry. func (entry *Entry) WithFields(fields Fields) *Entry { data := Fields{} for k, v := range entry.Data { @@ -52,12 +72,12 @@ func (entry *Entry) WithFields(fields Fields) *Entry { return &Entry{Logger: entry.Logger, Data: data} } -func (entry *Entry) log(level string, levelInt Level, msg string) string { - entry.Data["time"] = time.Now().String() - entry.Data["level"] = level - entry.Data["msg"] = msg +func (entry *Entry) log(level Level, msg string) string { + entry.Time = time.Now() + entry.Level = level + 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) } @@ -78,8 +98,8 @@ func (entry *Entry) log(level string, levelInt Level, msg string) string { } func (entry *Entry) Debug(args ...interface{}) { - if entry.Logger.Level >= Debug { - entry.log("debug", Debug, fmt.Sprint(args...)) + if entry.Logger.Level >= DebugLevel { + entry.log(DebugLevel, fmt.Sprint(args...)) } } @@ -88,33 +108,33 @@ func (entry *Entry) Print(args ...interface{}) { } func (entry *Entry) Info(args ...interface{}) { - if entry.Logger.Level >= Info { - entry.log("info", Info, fmt.Sprint(args...)) + if entry.Logger.Level >= InfoLevel { + entry.log(InfoLevel, fmt.Sprint(args...)) } } func (entry *Entry) Warn(args ...interface{}) { - if entry.Logger.Level >= Warn { - entry.log("warning", Warn, fmt.Sprint(args...)) + if entry.Logger.Level >= WarnLevel { + entry.log(WarnLevel, fmt.Sprint(args...)) } } func (entry *Entry) Error(args ...interface{}) { - if entry.Logger.Level >= Error { - entry.log("error", Error, fmt.Sprint(args...)) + if entry.Logger.Level >= ErrorLevel { + entry.log(ErrorLevel, fmt.Sprint(args...)) } } func (entry *Entry) Fatal(args ...interface{}) { - if entry.Logger.Level >= Fatal { - entry.log("fatal", Fatal, fmt.Sprint(args...)) + if entry.Logger.Level >= FatalLevel { + entry.log(FatalLevel, fmt.Sprint(args...)) } os.Exit(1) } func (entry *Entry) Panic(args ...interface{}) { - if entry.Logger.Level >= Panic { - msg := entry.log("panic", Panic, fmt.Sprint(args...)) + if entry.Logger.Level >= PanicLevel { + msg := entry.log(PanicLevel, fmt.Sprint(args...)) panic(msg) } panic(fmt.Sprint(args...)) @@ -123,13 +143,13 @@ func (entry *Entry) Panic(args ...interface{}) { // Entry Printf family functions func (entry *Entry) Debugf(format string, args ...interface{}) { - if entry.Logger.Level >= Debug { + if entry.Logger.Level >= DebugLevel { entry.Debug(fmt.Sprintf(format, args...)) } } func (entry *Entry) Infof(format string, args ...interface{}) { - if entry.Logger.Level >= Info { + if entry.Logger.Level >= InfoLevel { 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{}) { - if entry.Logger.Level >= Warn { + if entry.Logger.Level >= WarnLevel { 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{}) { - if entry.Logger.Level >= Error { + if entry.Logger.Level >= ErrorLevel { entry.Error(fmt.Sprintf(format, args...)) } } func (entry *Entry) Fatalf(format string, args ...interface{}) { - if entry.Logger.Level >= Fatal { + if entry.Logger.Level >= FatalLevel { entry.Fatal(fmt.Sprintf(format, args...)) } } func (entry *Entry) Panicf(format string, args ...interface{}) { - if entry.Logger.Level >= Panic { + if entry.Logger.Level >= PanicLevel { entry.Panic(fmt.Sprintf(format, args...)) } } @@ -169,13 +189,13 @@ func (entry *Entry) Panicf(format string, args ...interface{}) { // Entry Println family functions func (entry *Entry) Debugln(args ...interface{}) { - if entry.Logger.Level >= Debug { + if entry.Logger.Level >= DebugLevel { entry.Debug(entry.sprintlnn(args...)) } } func (entry *Entry) Infoln(args ...interface{}) { - if entry.Logger.Level >= Info { + if entry.Logger.Level >= InfoLevel { entry.Info(entry.sprintlnn(args...)) } } @@ -185,7 +205,7 @@ func (entry *Entry) Println(args ...interface{}) { } func (entry *Entry) Warnln(args ...interface{}) { - if entry.Logger.Level >= Warn { + if entry.Logger.Level >= WarnLevel { entry.Warn(entry.sprintlnn(args...)) } } @@ -195,19 +215,19 @@ func (entry *Entry) Warningln(args ...interface{}) { } func (entry *Entry) Errorln(args ...interface{}) { - if entry.Logger.Level >= Error { + if entry.Logger.Level >= ErrorLevel { entry.Error(entry.sprintlnn(args...)) } } func (entry *Entry) Fatalln(args ...interface{}) { - if entry.Logger.Level >= Fatal { + if entry.Logger.Level >= FatalLevel { entry.Fatal(entry.sprintlnn(args...)) } } func (entry *Entry) Panicln(args ...interface{}) { - if entry.Logger.Level >= Panic { + if entry.Logger.Level >= PanicLevel { entry.Panic(entry.sprintlnn(args...)) } } diff --git a/exported.go b/exported.go new file mode 100644 index 0000000..383ce93 --- /dev/null +++ b/exported.go @@ -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...) +} diff --git a/formatter.go b/formatter.go index 3a2eff5..fc0ebd7 100644 --- a/formatter.go +++ b/formatter.go @@ -1,5 +1,9 @@ package logrus +import ( + "time" +) + // The Formatter interface is used to implement a custom Formatter. It takes an // `Entry`. It exposes all the fields, including the default ones: // @@ -13,3 +17,38 @@ package logrus type Formatter interface { 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() +} diff --git a/hook_test.go b/hook_test.go index 24a02c3..13f34cb 100644 --- a/hook_test.go +++ b/hook_test.go @@ -17,12 +17,12 @@ func (hook *TestHook) Fire(entry *Entry) error { func (hook *TestHook) Levels() []Level { return []Level{ - Debug, - Info, - Warn, - Error, - Fatal, - Panic, + DebugLevel, + InfoLevel, + WarnLevel, + ErrorLevel, + FatalLevel, + PanicLevel, } } @@ -49,12 +49,12 @@ func (hook *ModifyHook) Fire(entry *Entry) error { func (hook *ModifyHook) Levels() []Level { return []Level{ - Debug, - Info, - Warn, - Error, - Fatal, - Panic, + DebugLevel, + InfoLevel, + WarnLevel, + ErrorLevel, + FatalLevel, + PanicLevel, } } @@ -95,7 +95,7 @@ func (hook *ErrorHook) Fire(entry *Entry) error { func (hook *ErrorHook) Levels() []Level { return []Level{ - Error, + ErrorLevel, } } diff --git a/hooks/airbrake/airbrake.go b/hooks/airbrake/airbrake.go index d92dc88..880d21e 100644 --- a/hooks/airbrake/airbrake.go +++ b/hooks/airbrake/airbrake.go @@ -47,8 +47,8 @@ func (hook *AirbrakeHook) Fire(entry *logrus.Entry) error { func (hook *AirbrakeHook) Levels() []logrus.Level { return []logrus.Level{ - logrus.Error, - logrus.Fatal, - logrus.Panic, + logrus.ErrorLevel, + logrus.FatalLevel, + logrus.PanicLevel, } } diff --git a/json_formatter.go b/json_formatter.go index cb3489e..c0e2d18 100644 --- a/json_formatter.go +++ b/json_formatter.go @@ -9,6 +9,8 @@ type JSONFormatter struct { } func (f *JSONFormatter) Format(entry *Entry) ([]byte, error) { + prefixFieldClashes(entry) + serialized, err := json.Marshal(entry.Data) if err != nil { return nil, fmt.Errorf("Failed to marshal fields to JSON, %v", err) diff --git a/logger.go b/logger.go index a545afd..7374fe3 100644 --- a/logger.go +++ b/logger.go @@ -47,7 +47,7 @@ func New() *Logger { Out: os.Stdout, Formatter: new(TextFormatter), Hooks: make(levelHooks), - Level: Info, + Level: InfoLevel, } } diff --git a/logrus.go b/logrus.go index 2376db3..79df39c 100644 --- a/logrus.go +++ b/logrus.go @@ -10,25 +10,45 @@ type Fields map[string]interface{} // Level type 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 // on your instance of logger, obtained with `logrus.New()`. 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, ... - Panic Level = iota - // Fatal level. Logs and then calls `os.Exit(1)`. It will exit even if the + PanicLevel Level = iota + // FatalLevel level. Logs and then calls `os.Exit(1)`. It will exit even if the // logging level is set to Panic. - Fatal - // Error level. Logs. Used for errors that should definitely be noted. + FatalLevel + // ErrorLevel level. Logs. Used for errors that should definitely be noted. // Commonly used for hooks to send errors to an error tracking service. - Error - // Warn level. Non-critical entries that deserve eyes. - Warn - // Info level. General operational entries about what's going on inside the + ErrorLevel + // WarnLevel level. Non-critical entries that deserve eyes. + WarnLevel + // InfoLevel level. General operational entries about what's going on inside the // application. - Info - // Debug level. Usually only enabled when debugging. Very verbose logging. - Debug + InfoLevel + // DebugLevel level. Usually only enabled when debugging. Very verbose logging. + DebugLevel ) // Won't compile if StdLogger can't be realized by a log.Logger diff --git a/logrus_test.go b/logrus_test.go index 82187ae..6202300 100644 --- a/logrus_test.go +++ b/logrus_test.go @@ -128,3 +128,46 @@ func TestWithFieldsShouldAllowAssignments(t *testing.T) { assert.Equal(t, false, ok) 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()) +} diff --git a/text_formatter.go b/text_formatter.go index 06b4970..d71eba1 100644 --- a/text_formatter.go +++ b/text_formatter.go @@ -32,6 +32,8 @@ type TextFormatter struct { func (f *TextFormatter) Format(entry *Entry) ([]byte, error) { b := &bytes.Buffer{} + prefixFieldClashes(entry) + if f.ForceColors || IsTerminal() { levelText := strings.ToUpper(entry.Data["level"].(string))[0:4]