Repository: sirupsen/logrus
Pull request #1127 (2020-04-10 10:48:10)

Add loggers that take functions as arguments
**This is connected to #1105** 

Having functions as log parameters can greatly improve performance in some cases.

E.g.: a big output is logged on trace level, when the current log-level is warn. Prior to the change, the log message would've always been generated. Now, we can pass a function and the log message is only generated when the corresponding log level is actually active. 

### Example

```go
log.TraceFn(func()[]interface{} {
	return []interface{}{strings.ReplaceAll(fmt.Sprintf("\n%s", hex.Dump(buf)), "\n", "\n\t")}
})
```


Patch:
diff --git a/.gitignore b/.gitignore
index 6b7d7d1..1fb13ab 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,2 +1,4 @@
 logrus
 vendor
+
+.idea/
diff --git a/exported.go b/exported.go
index 42b04f6..017c30c 100644
--- a/exported.go
+++ b/exported.go
@@ -134,6 +134,51 @@ func Fatal(args ...interface{}) {
 	std.Fatal(args...)
 }
 
+// TraceFn logs a message from a func at level Trace on the standard logger.
+func TraceFn(fn LogFunction) {
+	std.TraceFn(fn)
+}
+
+// DebugFn logs a message from a func at level Debug on the standard logger.
+func DebugFn(fn LogFunction) {
+	std.DebugFn(fn)
+}
+
+// PrintFn logs a message from a func at level Info on the standard logger.
+func PrintFn(fn LogFunction) {
+	std.PrintFn(fn)
+}
+
+// InfoFn logs a message from a func at level Info on the standard logger.
+func InfoFn(fn LogFunction) {
+	std.InfoFn(fn)
+}
+
+// WarnFn logs a message from a func at level Warn on the standard logger.
+func WarnFn(fn LogFunction) {
+	std.WarnFn(fn)
+}
+
+// WarningFn logs a message from a func at level Warn on the standard logger.
+func WarningFn(fn LogFunction) {
+	std.WarningFn(fn)
+}
+
+// ErrorFn logs a message from a func at level Error on the standard logger.
+func ErrorFn(fn LogFunction) {
+	std.ErrorFn(fn)
+}
+
+// PanicFn logs a message from a func at level Panic on the standard logger.
+func PanicFn(fn LogFunction) {
+	std.PanicFn(fn)
+}
+
+// FatalFn logs a message from a func at level Fatal on the standard logger then the process will exit with status set to 1.
+func FatalFn(fn LogFunction) {
+	std.FatalFn(fn)
+}
+
 // Tracef logs a message at level Trace on the standard logger.
 func Tracef(format string, args ...interface{}) {
 	std.Tracef(format, args...)
diff --git a/logger.go b/logger.go
index 6fdda74..dae5e57 100644
--- a/logger.go
+++ b/logger.go
@@ -9,6 +9,11 @@ import (
 	"time"
 )
 
+// LogFunction For big messages, it can be more efficient to pass a function
+// and only call it if the log level is actually enables rather than
+// generating the log message and then checking if the level is enabled
+type LogFunction func()[]interface{}
+
 type Logger struct {
 	// The logs are `io.Copy`'d to this in a mutex. It's common to set this to a
 	// file, or leave it default which is `os.Stderr`. You can also set this to
@@ -195,6 +200,14 @@ func (logger *Logger) Log(level Level, args ...interface{}) {
 	}
 }
 
+func (logger *Logger) LogFn(level Level, fn LogFunction) {
+	if logger.IsLevelEnabled(level) {
+		entry := logger.newEntry()
+		entry.Log(level, fn()...)
+		logger.releaseEntry(entry)
+	}
+}
+
 func (logger *Logger) Trace(args ...interface{}) {
 	logger.Log(TraceLevel, args...)
 }
@@ -234,6 +247,45 @@ func (logger *Logger) Panic(args ...interface{}) {
 	logger.Log(PanicLevel, args...)
 }
 
+func (logger *Logger) TraceFn(fn LogFunction) {
+	logger.LogFn(TraceLevel, fn)
+}
+
+func (logger *Logger) DebugFn(fn LogFunction) {
+	logger.LogFn(DebugLevel, fn)
+}
+
+func (logger *Logger) InfoFn(fn LogFunction) {
+	logger.LogFn(InfoLevel, fn)
+}
+
+func (logger *Logger) PrintFn(fn LogFunction) {
+	entry := logger.newEntry()
+	entry.Print(fn()...)
+	logger.releaseEntry(entry)
+}
+
+func (logger *Logger) WarnFn(fn LogFunction) {
+	logger.LogFn(WarnLevel, fn)
+}
+
+func (logger *Logger) WarningFn(fn LogFunction) {
+	logger.WarnFn(fn)
+}
+
+func (logger *Logger) ErrorFn(fn LogFunction) {
+	logger.LogFn(ErrorLevel, fn)
+}
+
+func (logger *Logger) FatalFn(fn LogFunction) {
+	logger.LogFn(FatalLevel, fn)
+	logger.Exit(1)
+}
+
+func (logger *Logger) PanicFn(fn LogFunction) {
+	logger.LogFn(PanicLevel, fn)
+}
+
 func (logger *Logger) Logln(level Level, args ...interface{}) {
 	if logger.IsLevelEnabled(level) {
 		entry := logger.newEntry()


Test patch:
diff --git a/example_function_test.go b/example_function_test.go
new file mode 100644
index 0000000..f61460e
--- /dev/null
+++ b/example_function_test.go
@@ -0,0 +1,26 @@
+package logrus_test
+
+import (
+	"fmt"
+	log "github.com/sirupsen/logrus"
+	"testing"
+)
+
+func TestLogger_LogFn(t *testing.T) {
+	log.SetFormatter(&log.JSONFormatter{})
+	log.SetLevel(log.WarnLevel)
+
+	log.InfoFn(func() []interface{} {
+		fmt.Println("This is never run")
+		return []interface{} {
+			"Hello",
+		}
+	})
+
+	log.ErrorFn(func() []interface{} {
+		fmt.Println("This runs")
+		return []interface{} {
+			"Oopsi",
+		}
+	})
+}