Merge upstream
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 <img src="http://i.imgur.com/hTeVwmJ.png" width="40" height="40" alt=":walrus:" class="emoji" title=":walrus:"/> [](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 @@
#### 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 @@
"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 @@
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 @@
```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 @@
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 @@
}
// `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 @@
```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 @@
```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 @@
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 @@
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 @@
}
```
+#### 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 @@
"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 @@
}
}
+// 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 @@
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 @@
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) 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) 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 @@
// 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) 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) 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 @@
// 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) 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) 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 @@
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) Levels() []Level {
return []Level{
- Debug,
- Info,
- Warn,
- Error,
- Fatal,
- Panic,
+ DebugLevel,
+ InfoLevel,
+ WarnLevel,
+ ErrorLevel,
+ FatalLevel,
+ PanicLevel,
}
}
@@ -49,12 +49,12 @@
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) 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) 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 @@
}
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 @@
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 @@
// 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 @@
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 @@
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]