Repository: sirupsen/logrus
Pull request #911 (2019-02-27 12:37:10)

Add a CallerPrettyfier callback to the text formatter


Patch:
diff --git a/text_formatter.go b/text_formatter.go
index 786bedf..b3b1a30 100644
--- a/text_formatter.go
+++ b/text_formatter.go
@@ -72,6 +72,12 @@ type TextFormatter struct {
 	//         FieldKeyMsg:   "@message"}}
 	FieldMap FieldMap
 
+	// CallerPrettyfier can be set by the user to modify the content
+	// of the function and file keys in the json data when ReportCaller is
+	// activated. If any of the returned value is the empty string the
+	// corresponding key will be removed from json fields.
+	CallerPrettyfier func(*runtime.Frame) (function string, file string)
+
 	terminalInitOnce sync.Once
 }
 
@@ -113,6 +119,8 @@ func (f *TextFormatter) Format(entry *Entry) ([]byte, error) {
 		keys = append(keys, k)
 	}
 
+	var funcVal, fileVal string
+
 	fixedKeys := make([]string, 0, 4+len(data))
 	if !f.DisableTimestamp {
 		fixedKeys = append(fixedKeys, f.FieldMap.resolve(FieldKeyTime))
@@ -127,6 +135,12 @@ func (f *TextFormatter) Format(entry *Entry) ([]byte, error) {
 	if entry.HasCaller() {
 		fixedKeys = append(fixedKeys,
 			f.FieldMap.resolve(FieldKeyFunc), f.FieldMap.resolve(FieldKeyFile))
+		if f.CallerPrettyfier != nil {
+			funcVal, fileVal = f.CallerPrettyfier(entry.Caller)
+		} else {
+			funcVal = entry.Caller.Function
+			fileVal = fmt.Sprintf("%s:%d", entry.Caller.File, entry.Caller.Line)
+		}
 	}
 
 	if !f.DisableSorting {
@@ -161,6 +175,7 @@ func (f *TextFormatter) Format(entry *Entry) ([]byte, error) {
 	if f.isColored() {
 		f.printColored(b, entry, keys, data, timestampFormat)
 	} else {
+
 		for _, key := range fixedKeys {
 			var value interface{}
 			switch {
@@ -173,9 +188,9 @@ func (f *TextFormatter) Format(entry *Entry) ([]byte, error) {
 			case key == f.FieldMap.resolve(FieldKeyLogrusError):
 				value = entry.err
 			case key == f.FieldMap.resolve(FieldKeyFunc) && entry.HasCaller():
-				value = entry.Caller.Function
+				value = funcVal
 			case key == f.FieldMap.resolve(FieldKeyFile) && entry.HasCaller():
-				value = fmt.Sprintf("%s:%d", entry.Caller.File, entry.Caller.Line)
+				value = fileVal
 			default:
 				value = data[key]
 			}
@@ -212,8 +227,13 @@ func (f *TextFormatter) printColored(b *bytes.Buffer, entry *Entry, keys []strin
 	caller := ""
 
 	if entry.HasCaller() {
-		caller = fmt.Sprintf("%s:%d %s()",
-			entry.Caller.File, entry.Caller.Line, entry.Caller.Function)
+		funcVal := fmt.Sprintf("%s()", entry.Caller.Function)
+		fileVal := fmt.Sprintf("%s:%d", entry.Caller.File, entry.Caller.Line)
+
+		if f.CallerPrettyfier != nil {
+			funcVal, fileVal = f.CallerPrettyfier(entry.Caller)
+		}
+		caller = fileVal + " " + funcVal
 	}
 
 	if f.DisableTimestamp {


Test patch:
diff --git a/internal/testutils/testutils.go b/internal/testutils/testutils.go
index 20bc3c3..6e3a620 100644
--- a/internal/testutils/testutils.go
+++ b/internal/testutils/testutils.go
@@ -40,7 +40,7 @@ func LogAndAssertText(t *testing.T, log func(*Logger), assertions func(fields ma
 	log(logger)
 
 	fields := make(map[string]string)
-	for _, kv := range strings.Split(buffer.String(), " ") {
+	for _, kv := range strings.Split(strings.TrimRight(buffer.String(), "\n"), " ") {
 		if !strings.Contains(kv, "=") {
 			continue
 		}
diff --git a/logrus_test.go b/logrus_test.go
index 47a386f..dfa053b 100644
--- a/logrus_test.go
+++ b/logrus_test.go
@@ -53,6 +53,17 @@ func TestReportCallerWhenConfigured(t *testing.T) {
 		assert.Equal(t, "somekindoffunc", fields[FieldKeyFunc])
 		assert.Equal(t, "thisisafilename", fields[FieldKeyFile])
 	})
+
+	LogAndAssertText(t, func(log *Logger) {
+		log.ReportCaller = true
+		log.Formatter.(*TextFormatter).CallerPrettyfier = func(f *runtime.Frame) (string, string) {
+			return "somekindoffunc", "thisisafilename"
+		}
+		log.Print("testWithCallerPrettyfier")
+	}, func(fields map[string]string) {
+		assert.Equal(t, "somekindoffunc", fields[FieldKeyFunc])
+		assert.Equal(t, "thisisafilename", fields[FieldKeyFile])
+	})
 }
 
 func logSomething(t *testing.T, message string) Fields {