Is it possible to wrap logrus.Logger functions without losing the line number prefix?

Viewed 2982

When using the wrapped logrus function/logger, the logger prefixes all log lines with the file name and line number of the logger function call, for example:

INFO[0000]logging.go:39 myfolder/logging.Info()

If I wrap the log function like this, for instance: package logging

import (
    "fmt"
    "github.com/sirupsen/logrus"
    "os"
    "path"
    "runtime"
)

var (
    log *logrus.Logger
)

func init() {

    log = logrus.New()
    log.SetReportCaller(true)
    log.Formatter = &logrus.TextFormatter{
        CallerPrettyfier: func(f *runtime.Frame) (string, string) {
            filename := path.Base(f.File)
            return fmt.Sprintf("%s()", f.Function), fmt.Sprintf("%s:%d", filename, f.Line)
        },
    }
}

func Info(args ...interface{}) {
        log.Info(args...)
}

Every line emitted by this function is going to be prefixed with the line number of the logging function call. That is as expected, but the desired behavior is for each line to be prefixed with the line number of the line where Info is called.

The Desired output should be :

INFO[0000]myfile.go:39 myfolder/myfile.myfunction()

Is there any way around it?

2 Answers

It is not possible to do it in the logrus. I had a similar requirement and ended up doing the following which worked for us.

package mylog

import (
    "fmt"
    "github.com/Sirupsen/logrus"
    "runtime"
    "strings"
)

var logger = logrus.New()

func SetLogFormatter(formatter logrus.Formatter) {
    logger.Formatter = formatter
}

// Info logs a message at level Info on the standard logger.
func Info(args ...interface{}) {
    if logger.Level >= logrus.InfoLevel {
        entry := logger.WithFields(logrus.Fields{})
        entry.Data["file"] = fileInfo(2)
        entry.Info(args...)
    }
}

func fileInfo(skip int) string {
    _, file, line, ok := runtime.Caller(skip)
    if !ok {
        file = "<???>"
        line = 1
    } else {
        slash := strings.LastIndex(file, "/")
        if slash >= 0 {
            file = file[slash+1:]
        }
    }
    return fmt.Sprintf("%s:%d", file, line)
}

See if this or some variation of this works for your use case. I have removed the application-specific code from the code snippet above.

Hoping that I am not misunderstanding so you want the "actual" path and line number where the logger was called. The code (json format as an example) below should give you want you want. If you want to add more info, such as the function name etc., just modify caller() method.

logrus.SetReportCaller(true)
// ...

logrus.SetFormatter(&logrus.JSONFormatter{
    CallerPrettyfier:  caller(),
    FieldMap: logrus.FieldMap{
        logrus.FieldKeyFile: "caller",
    },
})
// caller returns string presentation of log caller which is formatted as
// `/path/to/file.go:line_number`. e.g. `/internal/app/api.go:25`
func caller() func(*runtime.Frame) (function string, file string) {
    return func(f *runtime.Frame) (function string, file string) {
        p, _ := os.Getwd()

        return "", fmt.Sprintf("%s:%d", strings.TrimPrefix(f.File, p), f.Line)
    }
}
{
  "caller": "/internal/controller/create.go:21",
  "level": "info",
  "msg": "i am a dummy log",
  "time": "2020-08-30T19:17:48+01:00"
}
Related