Repository: sirupsen/logrus
Pull request #844 (2018-10-18 01:34:10)

Added TRACE level logging.
This is just a rebased update to PR https://github.com/sirupsen/logrus/pull/652

Please see discussion and history there.


Patch:
diff --git a/.gitignore b/.gitignore
index 66be63a..6b7d7d1 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,1 +1,2 @@
 logrus
+vendor
diff --git a/README.md b/README.md
index 072e99b..f088432 100644
--- a/README.md
+++ b/README.md
@@ -246,9 +246,10 @@ A list of currently known of service hook can be found in this wiki [page](https
 
 #### Level logging
 
-Logrus has six logging levels: Debug, Info, Warning, Error, Fatal and Panic.
+Logrus has seven logging levels: Trace, Debug, Info, Warning, Error, Fatal and Panic.
 
 ```go
+log.Trace("Something very low level.")
 log.Debug("Useful debugging information.")
 log.Info("Something noteworthy happened!")
 log.Warn("You should probably take a look at this.")
diff --git a/entry.go b/entry.go
index 225bdb6..a76be76 100644
--- a/entry.go
+++ b/entry.go
@@ -23,9 +23,9 @@ func init() {
 var ErrorKey = "error"
 
 // An entry is the final or intermediate Logrus logging entry. It contains 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.
+// the fields passed with WithField{,s}. It's finally logged when Trace, 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
 
@@ -35,11 +35,11 @@ type Entry struct {
 	// 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 the log entry was logged at: Trace, Debug, Info, Warn, Error, Fatal or Panic
 	// This field will be set on entry firing and the value will be equal to the one in Logger struct field.
 	Level Level
 
-	// Message passed to Debug, Info, Warn, Error, Fatal or Panic
+	// Message passed to Trace, Debug, Info, Warn, Error, Fatal or Panic
 	Message string
 
 	// When formatter is called in entry.log(), a Buffer may be set to entry
@@ -162,6 +162,12 @@ func (entry *Entry) write() {
 	}
 }
 
+func (entry *Entry) Trace(args ...interface{}) {
+	if entry.Logger.IsLevelEnabled(TraceLevel) {
+		entry.log(TraceLevel, fmt.Sprint(args...))
+	}
+}
+
 func (entry *Entry) Debug(args ...interface{}) {
 	if entry.Logger.IsLevelEnabled(DebugLevel) {
 		entry.log(DebugLevel, fmt.Sprint(args...))
@@ -210,6 +216,12 @@ func (entry *Entry) Panic(args ...interface{}) {
 
 // Entry Printf family functions
 
+func (entry *Entry) Tracef(format string, args ...interface{}) {
+	if entry.Logger.IsLevelEnabled(TraceLevel) {
+		entry.Trace(fmt.Sprintf(format, args...))
+	}
+}
+
 func (entry *Entry) Debugf(format string, args ...interface{}) {
 	if entry.Logger.IsLevelEnabled(DebugLevel) {
 		entry.Debug(fmt.Sprintf(format, args...))
@@ -257,6 +269,12 @@ func (entry *Entry) Panicf(format string, args ...interface{}) {
 
 // Entry Println family functions
 
+func (entry *Entry) Traceln(args ...interface{}) {
+	if entry.Logger.IsLevelEnabled(TraceLevel) {
+		entry.Trace(entry.sprintlnn(args...))
+	}
+}
+
 func (entry *Entry) Debugln(args ...interface{}) {
 	if entry.Logger.IsLevelEnabled(DebugLevel) {
 		entry.Debug(entry.sprintlnn(args...))
diff --git a/exported.go b/exported.go
index fb2a7a1..b9f1c9d 100644
--- a/exported.go
+++ b/exported.go
@@ -77,6 +77,11 @@ func WithTime(t time.Time) *Entry {
 	return std.WithTime(t)
 }
 
+// Trace logs a message at level Trace on the standard logger.
+func Trace(args ...interface{}) {
+	std.Trace(args...)
+}
+
 // Debug logs a message at level Debug on the standard logger.
 func Debug(args ...interface{}) {
 	std.Debug(args...)
@@ -117,6 +122,11 @@ func Fatal(args ...interface{}) {
 	std.Fatal(args...)
 }
 
+// Tracef logs a message at level Trace on the standard logger.
+func Tracef(format string, args ...interface{}) {
+	std.Tracef(format, args...)
+}
+
 // Debugf logs a message at level Debug on the standard logger.
 func Debugf(format string, args ...interface{}) {
 	std.Debugf(format, args...)
@@ -157,6 +167,11 @@ func Fatalf(format string, args ...interface{}) {
 	std.Fatalf(format, args...)
 }
 
+// Traceln logs a message at level Trace on the standard logger.
+func Traceln(args ...interface{}) {
+	std.Traceln(args...)
+}
+
 // Debugln logs a message at level Debug on the standard logger.
 func Debugln(args ...interface{}) {
 	std.Debugln(args...)
diff --git a/hooks/syslog/syslog.go b/hooks/syslog/syslog.go
index 329ce0d..02b8df3 100644
--- a/hooks/syslog/syslog.go
+++ b/hooks/syslog/syslog.go
@@ -43,7 +43,7 @@ func (hook *SyslogHook) Fire(entry *logrus.Entry) error {
 		return hook.Writer.Warning(line)
 	case logrus.InfoLevel:
 		return hook.Writer.Info(line)
-	case logrus.DebugLevel:
+	case logrus.DebugLevel, logrus.TraceLevel:
 		return hook.Writer.Debug(line)
 	default:
 		return nil
diff --git a/logger.go b/logger.go
index 1c934ed..52f1761 100644
--- a/logger.go
+++ b/logger.go
@@ -126,6 +126,14 @@ func (logger *Logger) WithTime(t time.Time) *Entry {
 	return entry.WithTime(t)
 }
 
+func (logger *Logger) Tracef(format string, args ...interface{}) {
+	if logger.IsLevelEnabled(TraceLevel) {
+		entry := logger.newEntry()
+		entry.Tracef(format, args...)
+		logger.releaseEntry(entry)
+	}
+}
+
 func (logger *Logger) Debugf(format string, args ...interface{}) {
 	if logger.IsLevelEnabled(DebugLevel) {
 		entry := logger.newEntry()
@@ -189,6 +197,14 @@ func (logger *Logger) Panicf(format string, args ...interface{}) {
 	}
 }
 
+func (logger *Logger) Trace(args ...interface{}) {
+	if logger.IsLevelEnabled(TraceLevel) {
+		entry := logger.newEntry()
+		entry.Trace(args...)
+		logger.releaseEntry(entry)
+	}
+}
+
 func (logger *Logger) Debug(args ...interface{}) {
 	if logger.IsLevelEnabled(DebugLevel) {
 		entry := logger.newEntry()
@@ -252,6 +268,14 @@ func (logger *Logger) Panic(args ...interface{}) {
 	}
 }
 
+func (logger *Logger) Traceln(args ...interface{}) {
+	if logger.IsLevelEnabled(TraceLevel) {
+		entry := logger.newEntry()
+		entry.Traceln(args...)
+		logger.releaseEntry(entry)
+	}
+}
+
 func (logger *Logger) Debugln(args ...interface{}) {
 	if logger.IsLevelEnabled(DebugLevel) {
 		entry := logger.newEntry()
diff --git a/logrus.go b/logrus.go
index 6fff506..03046b6 100644
--- a/logrus.go
+++ b/logrus.go
@@ -15,6 +15,8 @@ type Level uint32
 // Convert the Level to a string. E.g. PanicLevel becomes "panic".
 func (level Level) String() string {
 	switch level {
+	case TraceLevel:
+		return "trace"
 	case DebugLevel:
 		return "debug"
 	case InfoLevel:
@@ -47,6 +49,8 @@ func ParseLevel(lvl string) (Level, error) {
 		return InfoLevel, nil
 	case "debug":
 		return DebugLevel, nil
+	case "trace":
+		return TraceLevel, nil
 	}
 
 	var l Level
@@ -61,6 +65,7 @@ var AllLevels = []Level{
 	WarnLevel,
 	InfoLevel,
 	DebugLevel,
+	TraceLevel,
 }
 
 // These are the different logging levels. You can set the logging level to log
@@ -82,6 +87,8 @@ const (
 	InfoLevel
 	// DebugLevel level. Usually only enabled when debugging. Very verbose logging.
 	DebugLevel
+	// TraceLevel level. Designates finer-grained informational events than the Debug.
+	TraceLevel
 )
 
 // Won't compile if StdLogger can't be realized by a log.Logger
@@ -148,3 +155,12 @@ type FieldLogger interface {
 	// IsFatalEnabled() bool
 	// IsPanicEnabled() bool
 }
+
+// Ext1FieldLogger (the first extension to FieldLogger) is superfluous, it is
+// here for consistancy. Do not use. Use Logger or Entry instead.
+type Ext1FieldLogger interface {
+	FieldLogger
+	Tracef(format string, args ...interface{})
+	Trace(args ...interface{})
+	Traceln(args ...interface{})
+}
diff --git a/text_formatter.go b/text_formatter.go
index d4663b8..74dffcf 100644
--- a/text_formatter.go
+++ b/text_formatter.go
@@ -183,7 +183,7 @@ func (f *TextFormatter) Format(entry *Entry) ([]byte, error) {
 func (f *TextFormatter) printColored(b *bytes.Buffer, entry *Entry, keys []string, timestampFormat string) {
 	var levelColor int
 	switch entry.Level {
-	case DebugLevel:
+	case DebugLevel, TraceLevel:
 		levelColor = gray
 	case WarnLevel:
 		levelColor = yellow
diff --git a/writer.go b/writer.go
index 7bdebed..9e1f751 100644
--- a/writer.go
+++ b/writer.go
@@ -24,6 +24,8 @@ func (entry *Entry) WriterLevel(level Level) *io.PipeWriter {
 	var printFunc func(args ...interface{})
 
 	switch level {
+	case TraceLevel:
+		printFunc = entry.Trace
 	case DebugLevel:
 		printFunc = entry.Debug
 	case InfoLevel:


Test patch:
diff --git a/example_basic_test.go b/example_basic_test.go
index 5f3849b..9ff5655 100644
--- a/example_basic_test.go
+++ b/example_basic_test.go
@@ -12,7 +12,7 @@ func Example_basic() {
 	log.Formatter = new(logrus.TextFormatter)                     //default
 	log.Formatter.(*logrus.TextFormatter).DisableColors = true    // remove colors
 	log.Formatter.(*logrus.TextFormatter).DisableTimestamp = true // remove timestamp from test output
-	log.Level = logrus.DebugLevel
+	log.Level = logrus.TraceLevel
 	log.Out = os.Stdout
 
 	// file, err := os.OpenFile("logrus.log", os.O_CREATE|os.O_WRONLY, 0666)
@@ -37,6 +37,11 @@ func Example_basic() {
 		}
 	}()
 
+	log.WithFields(logrus.Fields{
+		"animal": "walrus",
+		"number": 0,
+	}).Trace("Went to the beach")
+
 	log.WithFields(logrus.Fields{
 		"animal": "walrus",
 		"number": 8,
@@ -62,6 +67,7 @@ func Example_basic() {
 	}).Panic("It's over 9000!")
 
 	// Output:
+	// level=trace msg="Went to the beach" animal=walrus number=0
 	// level=debug msg="Started observing beach" animal=walrus number=8
 	// level=info msg="A group of walrus emerges from the ocean" animal=walrus size=10
 	// level=warning msg="The group's number increased tremendously!" number=122 omg=true
diff --git a/hook_test.go b/hook_test.go
index 80b93b8..7196e99 100644
--- a/hook_test.go
+++ b/hook_test.go
@@ -21,6 +21,7 @@ func (hook *TestHook) Fire(entry *Entry) error {
 
 func (hook *TestHook) Levels() []Level {
 	return []Level{
+		TraceLevel,
 		DebugLevel,
 		InfoLevel,
 		WarnLevel,
@@ -53,6 +54,7 @@ func (hook *ModifyHook) Fire(entry *Entry) error {
 
 func (hook *ModifyHook) Levels() []Level {
 	return []Level{
+		TraceLevel,
 		DebugLevel,
 		InfoLevel,
 		WarnLevel,
diff --git a/logrus_test.go b/logrus_test.go
index 97d15d7..ccdf5cc 100644
--- a/logrus_test.go
+++ b/logrus_test.go
@@ -303,6 +303,7 @@ func TestDoubleLoggingDoesntPrefixPreviousFields(t *testing.T) {
 }
 
 func TestConvertLevelToString(t *testing.T) {
+	assert.Equal(t, "trace", TraceLevel.String())
 	assert.Equal(t, "debug", DebugLevel.String())
 	assert.Equal(t, "info", InfoLevel.String())
 	assert.Equal(t, "warning", WarnLevel.String())
@@ -368,6 +369,14 @@ func TestParseLevel(t *testing.T) {
 	assert.Nil(t, err)
 	assert.Equal(t, DebugLevel, l)
 
+	l, err = ParseLevel("trace")
+	assert.Nil(t, err)
+	assert.Equal(t, TraceLevel, l)
+
+	l, err = ParseLevel("TRACE")
+	assert.Nil(t, err)
+	assert.Equal(t, TraceLevel, l)
+
 	l, err = ParseLevel("invalid")
 	assert.Equal(t, "not a valid logrus Level: \"invalid\"", err.Error())
 }
@@ -444,9 +453,12 @@ func TestReplaceHooks(t *testing.T) {
 }
 
 // Compile test
-func TestLogrusInterface(t *testing.T) {
+func TestLogrusInterfaces(t *testing.T) {
 	var buffer bytes.Buffer
-	fn := func(l FieldLogger) {
+	// This verifies FieldLogger and Ext1FieldLogger work as designed.
+	// Please don't use them. Use Logger and Entry directly.
+	fn := func(xl Ext1FieldLogger) {
+		var l FieldLogger = xl
 		b := l.WithField("key", "value")
 		b.Debug("Test")
 	}
@@ -494,6 +506,7 @@ func TestLogLevelEnabled(t *testing.T) {
 	assert.Equal(t, false, log.IsLevelEnabled(WarnLevel))
 	assert.Equal(t, false, log.IsLevelEnabled(InfoLevel))
 	assert.Equal(t, false, log.IsLevelEnabled(DebugLevel))
+	assert.Equal(t, false, log.IsLevelEnabled(TraceLevel))
 
 	log.SetLevel(FatalLevel)
 	assert.Equal(t, true, log.IsLevelEnabled(PanicLevel))
@@ -502,6 +515,7 @@ func TestLogLevelEnabled(t *testing.T) {
 	assert.Equal(t, false, log.IsLevelEnabled(WarnLevel))
 	assert.Equal(t, false, log.IsLevelEnabled(InfoLevel))
 	assert.Equal(t, false, log.IsLevelEnabled(DebugLevel))
+	assert.Equal(t, false, log.IsLevelEnabled(TraceLevel))
 
 	log.SetLevel(ErrorLevel)
 	assert.Equal(t, true, log.IsLevelEnabled(PanicLevel))
@@ -510,6 +524,7 @@ func TestLogLevelEnabled(t *testing.T) {
 	assert.Equal(t, false, log.IsLevelEnabled(WarnLevel))
 	assert.Equal(t, false, log.IsLevelEnabled(InfoLevel))
 	assert.Equal(t, false, log.IsLevelEnabled(DebugLevel))
+	assert.Equal(t, false, log.IsLevelEnabled(TraceLevel))
 
 	log.SetLevel(WarnLevel)
 	assert.Equal(t, true, log.IsLevelEnabled(PanicLevel))
@@ -518,6 +533,7 @@ func TestLogLevelEnabled(t *testing.T) {
 	assert.Equal(t, true, log.IsLevelEnabled(WarnLevel))
 	assert.Equal(t, false, log.IsLevelEnabled(InfoLevel))
 	assert.Equal(t, false, log.IsLevelEnabled(DebugLevel))
+	assert.Equal(t, false, log.IsLevelEnabled(TraceLevel))
 
 	log.SetLevel(InfoLevel)
 	assert.Equal(t, true, log.IsLevelEnabled(PanicLevel))
@@ -526,6 +542,7 @@ func TestLogLevelEnabled(t *testing.T) {
 	assert.Equal(t, true, log.IsLevelEnabled(WarnLevel))
 	assert.Equal(t, true, log.IsLevelEnabled(InfoLevel))
 	assert.Equal(t, false, log.IsLevelEnabled(DebugLevel))
+	assert.Equal(t, false, log.IsLevelEnabled(TraceLevel))
 
 	log.SetLevel(DebugLevel)
 	assert.Equal(t, true, log.IsLevelEnabled(PanicLevel))
@@ -534,4 +551,14 @@ func TestLogLevelEnabled(t *testing.T) {
 	assert.Equal(t, true, log.IsLevelEnabled(WarnLevel))
 	assert.Equal(t, true, log.IsLevelEnabled(InfoLevel))
 	assert.Equal(t, true, log.IsLevelEnabled(DebugLevel))
+	assert.Equal(t, false, log.IsLevelEnabled(TraceLevel))
+
+	log.SetLevel(TraceLevel)
+	assert.Equal(t, true, log.IsLevelEnabled(PanicLevel))
+	assert.Equal(t, true, log.IsLevelEnabled(FatalLevel))
+	assert.Equal(t, true, log.IsLevelEnabled(ErrorLevel))
+	assert.Equal(t, true, log.IsLevelEnabled(WarnLevel))
+	assert.Equal(t, true, log.IsLevelEnabled(InfoLevel))
+	assert.Equal(t, true, log.IsLevelEnabled(DebugLevel))
+	assert.Equal(t, true, log.IsLevelEnabled(TraceLevel))
 }