From e5621dca87cc075c12683155e3f226dafd807559 Mon Sep 17 00:00:00 2001 From: Antoine Grondin Date: Tue, 15 Jul 2014 18:30:01 -0400 Subject: [PATCH 01/14] Export package func to a standard logger. --- README.md | 6 +-- entry.go | 48 ++++++++++----------- exported.go | 87 ++++++++++++++++++++++++++++++++++++++ hook_test.go | 26 ++++++------ hooks/airbrake/airbrake.go | 6 +-- logger.go | 2 +- logrus.go | 24 +++++------ 7 files changed, 143 insertions(+), 56 deletions(-) create mode 100644 exported.go diff --git a/README.md b/README.md index 41e23ef..0501a4c 100644 --- a/README.md +++ b/README.md @@ -137,9 +137,9 @@ 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, + logrus.ErrorLevel, + logrus.FatalLevel, + logrus.PanicLevel, } } ``` diff --git a/entry.go b/entry.go index 08cc15f..b706506 100644 --- a/entry.go +++ b/entry.go @@ -78,8 +78,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("debug", DebugLevel, fmt.Sprint(args...)) } } @@ -88,33 +88,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("info", 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("warning", 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("error", 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("fatal", 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("panic", PanicLevel, fmt.Sprint(args...)) panic(msg) } panic(fmt.Sprint(args...)) @@ -123,13 +123,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 +139,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 +149,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 +169,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 +185,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 +195,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..b34cb64 --- /dev/null +++ b/exported.go @@ -0,0 +1,87 @@ +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) +} + +// 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) +} + +// 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) +} 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/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..f41e1a4 100644 --- a/logrus.go +++ b/logrus.go @@ -13,22 +13,22 @@ type Level uint8 // 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 From 9a0c9801d6cdfd9bb91b4865da0e4c238c115a8a Mon Sep 17 00:00:00 2001 From: Antoine Grondin Date: Wed, 23 Jul 2014 16:48:53 -0400 Subject: [PATCH 02/14] Add readme paragraph about package exported funcs. --- README.md | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 0501a4c..0c1d6ac 100644 --- a/README.md +++ b/README.md @@ -78,6 +78,13 @@ func main() { } ``` +#### Package logging + +Alike the stdlib logger, logrus exposes functions that you can use to log +to a default global logger. This is convenient to avoid passing a +`logrus.Logger` thorough your app's packages; you can simply setup `logrus +from your main package and use the package function directly accross your app. + #### Fields Logrus encourages careful, structured logging though logging fields instead of @@ -91,7 +98,7 @@ log = logrus.New() log.WithFields(logrus.Fields{ "event": event, "topic": topic, - "key": key + "key": key, }).Fatal("Failed to send event") ``` From beee753015b78c153ea7ea90587b328a135339a2 Mon Sep 17 00:00:00 2001 From: Antoine Grondin Date: Wed, 23 Jul 2014 17:46:43 -0400 Subject: [PATCH 03/14] Fix missing var args expansion. --- exported.go | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/exported.go b/exported.go index b34cb64..ec4d9a6 100644 --- a/exported.go +++ b/exported.go @@ -58,30 +58,30 @@ func WithFields(fields Fields) *Entry { // Debug logs a message at level Debug on the standard logger. func Debug(args ...interface{}) { - std.Debug(args) + std.Debug(args...) } // Info logs a message at level Info on the standard logger. func Info(args ...interface{}) { - std.Info(args) + std.Info(args...) } // Warn logs a message at level Warn on the standard logger. func Warn(args ...interface{}) { - std.Warn(args) + std.Warn(args...) } // Error logs a message at level Error on the standard logger. func Error(args ...interface{}) { - std.Error(args) + std.Error(args...) } // Panic logs a message at level Panic on the standard logger. func Panic(args ...interface{}) { - std.Panic(args) + std.Panic(args...) } // Fatal logs a message at level Fatal on the standard logger. func Fatal(args ...interface{}) { - std.Fatal(args) + std.Fatal(args...) } From 3fed7c4632d3563356252ad88c8b31b370c49761 Mon Sep 17 00:00:00 2001 From: Simon Eskildsen Date: Sat, 26 Jul 2014 20:47:50 -0400 Subject: [PATCH 04/14] readme: backwards compatibility notice --- README.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 41e23ef..64849d8 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): From cd8fc638cc6b505f733e52aa13792ab11179580f Mon Sep 17 00:00:00 2001 From: Simon Eskildsen Date: Sat, 26 Jul 2014 21:02:08 -0400 Subject: [PATCH 05/14] levels: add string helper --- .gitignore | 1 + logrus.go | 20 ++++++++++++++++++++ logrus_test.go | 9 +++++++++ 3 files changed, 30 insertions(+) create mode 100644 .gitignore diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..66be63a --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +logrus diff --git a/logrus.go b/logrus.go index f41e1a4..79df39c 100644 --- a/logrus.go +++ b/logrus.go @@ -10,6 +10,26 @@ 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 ( diff --git a/logrus_test.go b/logrus_test.go index 82187ae..f14445c 100644 --- a/logrus_test.go +++ b/logrus_test.go @@ -128,3 +128,12 @@ func TestWithFieldsShouldAllowAssignments(t *testing.T) { assert.Equal(t, false, ok) assert.Equal(t, "value1", fields["key1"]) } + +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()) +} From f0cb18fc85a6adc522a38a580c6d438aa29656f3 Mon Sep 17 00:00:00 2001 From: Simon Eskildsen Date: Sat, 26 Jul 2014 21:03:20 -0400 Subject: [PATCH 06/14] entry: use level string helper --- entry.go | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/entry.go b/entry.go index b706506..8292ded 100644 --- a/entry.go +++ b/entry.go @@ -52,12 +52,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 { +func (entry *Entry) log(level Level, msg string) string { entry.Data["time"] = time.Now().String() - entry.Data["level"] = level + entry.Data["level"] = level.String() entry.Data["msg"] = 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) } @@ -79,7 +79,7 @@ func (entry *Entry) log(level string, levelInt Level, msg string) string { func (entry *Entry) Debug(args ...interface{}) { if entry.Logger.Level >= DebugLevel { - entry.log("debug", DebugLevel, fmt.Sprint(args...)) + entry.log(DebugLevel, fmt.Sprint(args...)) } } @@ -89,32 +89,32 @@ func (entry *Entry) Print(args ...interface{}) { func (entry *Entry) Info(args ...interface{}) { if entry.Logger.Level >= InfoLevel { - entry.log("info", InfoLevel, fmt.Sprint(args...)) + entry.log(InfoLevel, fmt.Sprint(args...)) } } func (entry *Entry) Warn(args ...interface{}) { if entry.Logger.Level >= WarnLevel { - entry.log("warning", WarnLevel, fmt.Sprint(args...)) + entry.log(WarnLevel, fmt.Sprint(args...)) } } func (entry *Entry) Error(args ...interface{}) { if entry.Logger.Level >= ErrorLevel { - entry.log("error", ErrorLevel, fmt.Sprint(args...)) + entry.log(ErrorLevel, fmt.Sprint(args...)) } } func (entry *Entry) Fatal(args ...interface{}) { if entry.Logger.Level >= FatalLevel { - entry.log("fatal", FatalLevel, fmt.Sprint(args...)) + entry.log(FatalLevel, fmt.Sprint(args...)) } os.Exit(1) } func (entry *Entry) Panic(args ...interface{}) { if entry.Logger.Level >= PanicLevel { - msg := entry.log("panic", PanicLevel, fmt.Sprint(args...)) + msg := entry.log(PanicLevel, fmt.Sprint(args...)) panic(msg) } panic(fmt.Sprint(args...)) From 40069a98d66cf6190ad97b6990d672cc2324dcac Mon Sep 17 00:00:00 2001 From: Simon Eskildsen Date: Sat, 26 Jul 2014 21:26:04 -0400 Subject: [PATCH 07/14] entry: break out time, level and message from data --- README.md | 3 +++ entry.go | 15 ++++++++++++--- formatter.go | 42 ++++++++++++++++++++++++++++++++++++++++++ json_formatter.go | 3 +++ logrus_test.go | 34 ++++++++++++++++++++++++++++++++++ text_formatter.go | 4 ++++ 6 files changed, 98 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 318c2a9..108fb75 100644 --- a/README.md +++ b/README.md @@ -261,6 +261,9 @@ type MyJSONFormatter struct { log.Formatter = 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) diff --git a/entry.go b/entry.go index 8292ded..1c8e041 100644 --- a/entry.go +++ b/entry.go @@ -11,6 +11,15 @@ import ( type Entry struct { Logger *Logger 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 @@ -53,9 +62,9 @@ func (entry *Entry) WithFields(fields Fields) *Entry { } func (entry *Entry) log(level Level, msg string) string { - entry.Data["time"] = time.Now().String() - entry.Data["level"] = level.String() - entry.Data["msg"] = msg + entry.Time = time.Now() + entry.Level = level + entry.Message = msg if err := entry.Logger.Hooks.Fire(level, entry); err != nil { fmt.Fprintf(os.Stderr, "Failed to fire hook", err) diff --git a/formatter.go b/formatter.go index 3a2eff5..6c9560d 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,41 @@ package logrus type Formatter interface { Format(*Entry) ([]byte, error) } + +type internalFormatter struct { +} + +// 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 (f *internalFormatter) 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/json_formatter.go b/json_formatter.go index cb3489e..c3fb9b8 100644 --- a/json_formatter.go +++ b/json_formatter.go @@ -6,9 +6,12 @@ import ( ) type JSONFormatter struct { + *internalFormatter } func (f *JSONFormatter) Format(entry *Entry) ([]byte, error) { + f.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/logrus_test.go b/logrus_test.go index f14445c..6202300 100644 --- a/logrus_test.go +++ b/logrus_test.go @@ -129,6 +129,40 @@ func TestWithFieldsShouldAllowAssignments(t *testing.T) { 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()) diff --git a/text_formatter.go b/text_formatter.go index 06b4970..09fafde 100644 --- a/text_formatter.go +++ b/text_formatter.go @@ -27,11 +27,15 @@ func miniTS() int { type TextFormatter struct { // Set to true to bypass checking for a TTY before outputting colors. ForceColors bool + + *internalFormatter } func (f *TextFormatter) Format(entry *Entry) ([]byte, error) { b := &bytes.Buffer{} + f.prefixFieldClashes(entry) + if f.ForceColors || IsTerminal() { levelText := strings.ToUpper(entry.Data["level"].(string))[0:4] From cfddc663250ab4a9dd995175f15afded1df99b99 Mon Sep 17 00:00:00 2001 From: Simon Eskildsen Date: Sat, 26 Jul 2014 21:37:06 -0400 Subject: [PATCH 08/14] entry: document entry and methods --- entry.go | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/entry.go b/entry.go index 1c8e041..44ff056 100644 --- a/entry.go +++ b/entry.go @@ -8,9 +8,15 @@ 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 @@ -32,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 { @@ -46,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 { From 3b30f00a1252d7b5f765b38268ccaabc11816c4d Mon Sep 17 00:00:00 2001 From: Simon Eskildsen Date: Sat, 26 Jul 2014 21:46:04 -0400 Subject: [PATCH 09/14] readme: document log rotation --- README.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/README.md b/README.md index 318c2a9..baf1af5 100644 --- a/README.md +++ b/README.md @@ -269,4 +269,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 From 2c0db7c868ca1bb005627e6816fa5f51cb9708c1 Mon Sep 17 00:00:00 2001 From: Simon Eskildsen Date: Sat, 26 Jul 2014 22:22:39 -0400 Subject: [PATCH 10/14] formatter: drop internalFormatter --- formatter.go | 5 +---- json_formatter.go | 3 +-- text_formatter.go | 4 +--- 3 files changed, 3 insertions(+), 9 deletions(-) diff --git a/formatter.go b/formatter.go index 6c9560d..fc0ebd7 100644 --- a/formatter.go +++ b/formatter.go @@ -18,9 +18,6 @@ type Formatter interface { Format(*Entry) ([]byte, error) } -type internalFormatter struct { -} - // This is to not silently overwrite `time`, `msg` and `level` fields when // dumping it. If this code wasn't there doing: // @@ -33,7 +30,7 @@ type internalFormatter struct { // // 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 (f *internalFormatter) prefixFieldClashes(entry *Entry) { +func prefixFieldClashes(entry *Entry) { _, ok := entry.Data["time"] if ok { entry.Data["fields.time"] = entry.Data["time"] diff --git a/json_formatter.go b/json_formatter.go index c3fb9b8..c0e2d18 100644 --- a/json_formatter.go +++ b/json_formatter.go @@ -6,11 +6,10 @@ import ( ) type JSONFormatter struct { - *internalFormatter } func (f *JSONFormatter) Format(entry *Entry) ([]byte, error) { - f.prefixFieldClashes(entry) + prefixFieldClashes(entry) serialized, err := json.Marshal(entry.Data) if err != nil { diff --git a/text_formatter.go b/text_formatter.go index 09fafde..d71eba1 100644 --- a/text_formatter.go +++ b/text_formatter.go @@ -27,14 +27,12 @@ func miniTS() int { type TextFormatter struct { // Set to true to bypass checking for a TTY before outputting colors. ForceColors bool - - *internalFormatter } func (f *TextFormatter) Format(entry *Entry) ([]byte, error) { b := &bytes.Buffer{} - f.prefixFieldClashes(entry) + prefixFieldClashes(entry) if f.ForceColors || IsTerminal() { levelText := strings.ToUpper(entry.Data["level"].(string))[0:4] From 33c9d5aebc1b5419db852072f07e14de7705132e Mon Sep 17 00:00:00 2001 From: Simon Eskildsen Date: Sat, 26 Jul 2014 22:23:41 -0400 Subject: [PATCH 11/14] readme: fix levels --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 108fb75..e1ff807 100644 --- a/README.md +++ b/README.md @@ -191,10 +191,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.Level = logrus.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 From 3f50cc7bf7cf1026a5ff433908bf1930a8da5482 Mon Sep 17 00:00:00 2001 From: Simon Eskildsen Date: Sat, 26 Jul 2014 22:46:08 -0400 Subject: [PATCH 12/14] readme: encourage package level logging Conflicts: README.md --- README.md | 95 +++++++++++++++++++++++++++++++++++++++++-------------- 1 file changed, 71 insertions(+), 24 deletions(-) diff --git a/README.md b/README.md index 6070ab4..7643a04 100644 --- a/README.md +++ b/README.md @@ -45,22 +45,49 @@ 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 ( - "github.com/Sirupsen/logrus" + log "github.com/Sirupsen/logrus" ) -var log = logrus.New() +func main() { + log.WithFields(logrus.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.Formatter = new(logrus.JSONFormatter) - log.Formatter = new(logrus.TextFormatter) // default + // 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() { @@ -81,12 +108,30 @@ func main() { } ``` -#### Package logging +For more advanced usage such as logging to multiple locations from the same +application, you can also create an instance of the `logrus` Logger: -Alike the stdlib logger, logrus exposes functions that you can use to log -to a default global logger. This is convenient to avoid passing a -`logrus.Logger` thorough your app's packages; you can simply setup `logrus -from your main package and use the package function directly accross your app. +```go +package main + +import ( + "github.com/Sirupsen/logrus" +) + +// Create a new instance of the logger. You can have any number of instances. +var log = logrus.New() + +func main() { + // The API for setting attributes is a little different than the package level + // exported logger. See Godoc. + log.Out = os.Sderr + + log.WithFields(logrus.Fields{ + "animal": "walrus", + "size": 10, + }).Info("A group of walrus emerges from the ocean") +} +``` #### Fields @@ -96,8 +141,6 @@ 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{ "event": event, "topic": topic, @@ -122,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{} @@ -158,12 +203,12 @@ Logrus comes with built-in hooks. Add those, or your custom hook, in `init`: ```go import ( - "github.com/Sirupsen/logrus" + log "github.com/Sirupsen/logrus" "github.com/Sirupsen/logrus/hooks/airbrake" ) func init() { - log.Hooks.Add(new(logrus_airbrake.AirbrakeHook)) + log.AddHook(new(logrus_airbrake.AirbrakeHook)) } ``` @@ -191,7 +236,7 @@ that severity or anything above it: ```go // Will log anything that is info or above (warn, error, fatal, panic). Default. -log.Level = logrus.InfoLevel +log.SetLevel(logrus.InfoLevel) ``` It may be useful to set `log.Level = logrus.DebugLevel` in a debug or verbose @@ -217,16 +262,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) } } ``` @@ -258,7 +305,7 @@ 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 From 330e34061ed41f46e4d5e4f729026fd0798836e9 Mon Sep 17 00:00:00 2001 From: Simon Eskildsen Date: Sat, 26 Jul 2014 22:51:23 -0400 Subject: [PATCH 13/14] readme: fix examples --- README.md | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/README.md b/README.md index 7643a04..a171763 100644 --- a/README.md +++ b/README.md @@ -55,8 +55,8 @@ import ( ) func main() { - log.WithFields(logrus.Fields{ - "animal": "walrus" + log.WithFields(log.Fields{ + "animal": "walrus", }).Info("A walrus appears") } ``` @@ -91,17 +91,17 @@ func init() { } func main() { - log.WithFields(logrus.Fields{ + log.WithFields(log.Fields{ "animal": "walrus", "size": 10, }).Info("A group of walrus emerges from the ocean") - log.WithFields(logrus.Fields{ + log.WithFields(log.Fields{ "omg": true, "number": 122, }).Warn("The group's number increased tremendously!") - log.WithFields(logrus.Fields{ + log.WithFields(log.Fields{ "omg": true, "number": 100, }).Fatal("The ice breaks!") @@ -124,9 +124,9 @@ var log = logrus.New() func main() { // The API for setting attributes is a little different than the package level // exported logger. See Godoc. - log.Out = os.Sderr + log.Out = os.Stderr - log.WithFields(logrus.Fields{ + log.WithFields(log.Fields{ "animal": "walrus", "size": 10, }).Info("A group of walrus emerges from the ocean") @@ -141,7 +141,7 @@ to send event %s to topic %s with key %d")`, you should log the much more discoverable: ```go -log.WithFields(logrus.Fields{ +log.WithFields(log.Fields{ "event": event, "topic": topic, "key": key, @@ -180,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") @@ -190,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.ErrorLevel, - logrus.FatalLevel, - logrus.PanicLevel, +func (hook *AirbrakeHook) Levels() []log.Level { + return []log.Level{ + log.ErrorLevel, + log.FatalLevel, + log.PanicLevel, } } ``` @@ -236,7 +236,7 @@ that severity or anything above it: ```go // Will log anything that is info or above (warn, error, fatal, panic). Default. -log.SetLevel(logrus.InfoLevel) +log.SetLevel(log.InfoLevel) ``` It may be useful to set `log.Level = logrus.DebugLevel` in a debug or verbose From e1154b431565f2f11106046e8e86ef35256b4d28 Mon Sep 17 00:00:00 2001 From: Simon Eskildsen Date: Sat, 26 Jul 2014 22:57:45 -0400 Subject: [PATCH 14/14] exported: export to be api compatible --- exported.go | 90 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 90 insertions(+) diff --git a/exported.go b/exported.go index ec4d9a6..383ce93 100644 --- a/exported.go +++ b/exported.go @@ -61,6 +61,11 @@ 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...) @@ -71,6 +76,11 @@ 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...) @@ -85,3 +95,83 @@ func Panic(args ...interface{}) { 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...) +}