I'm using logrus OS which works as expected, now we have a requirement to add to the logger output the file and the function which from where you put the logger call,
we need it to be something like
File log-ut-usage
func main(){
logs := lts.InitLogger("test","1","debug")
logs.Debugf("test 123")
....
}
This is the required output
{"file":"log-ut-usage/main.go:21","function":"main","level":"warn","test 123":"ddd","timestamp":"2019-10-02T09:21:39.309559Z"}
currently we got the file and function of the
file logger.go
func InitLog(label string) LoggerI {
loggerImpl = &logrus.Logger{
Out: os.Stdout,
Level: level,
ReportCaller: true,
Formatter: &logrus.JSONFormatter{
TimestampFormat: timestampFormat,
CallerPrettyfier: func(f *runtime.Frame) (string, string) {
s := strings.Split(f.Function, ".")
funcname := s[len(s)-1]
_, filename := path.Split(f.File)
return funcname, filename
},
},
}
This is the (unwanted) output
{"file":"logger.go","func":"InitLog","level":"debug","msg":"test 123","time":"2019-10-02 12:21:39"}
I dont want to get the file logger.go where we coded the json formater, I want to get the file that with the usage of the logger .
You can wrap your logger with file, function and line information and then use that.
Here's an example (live):
package main
import (
"os"
"runtime"
"strconv"
"strings"
log "github.com/sirupsen/logrus"
)
func init() {
log.SetFormatter(&log.JSONFormatter{})
log.SetOutput(os.Stdout)
}
func logger() *log.Entry {
pc, file, line, ok := runtime.Caller(1)
if !ok {
panic("Could not get context info for logger!")
}
filename := file[strings.LastIndex(file, "/")+1:] + ":" + strconv.Itoa(line)
funcname := runtime.FuncForPC(pc).Name()
fn := funcname[strings.LastIndex(funcname, ".")+1:]
return log.WithField("file", filename).WithField("function", fn)
}
func test() {
logger().Info("Testing...")
}
func main() {
logger().Info("Testing...")
test()
}
Output:
{"file":"prog.go:34","function":"main","level":"info","msg":"Testing...","time":"2009-11-10T23:00:00Z"}
{"file":"prog.go:30","function":"test","level":"info","msg":"Testing...","time":"2009-11-10T23:00:00Z"}
Have you tried to use debug.Stack() to fetch the file name which calls InitLog?
https://play.golang.org/p/g6yLGsiuEEn
goroutine 1 [running]:
runtime/debug.Stack(0x15d6b0, 0x3, 0x68360, 0x1580)
/usr/local/go/src/runtime/debug/stack.go:24 +0xc0
main.fun2()
/tmp/sandbox834348417/prog.go:20 +0x20
main.fun1(...)
/tmp/sandbox834348417/prog.go:15
main.main()
/tmp/sandbox834348417/prog.go:10 +0x20
Hope it works.
Related
How to get function name printed in logs from Uber Zap logging ?
This is the PR request with which they seemed to have added the functionality to output function names in log.
I am using golang version 1.15 and go.uber.org/zap v1.16.0
This is my code:
package main
import (
"go.uber.org/zap"
)
var logger *zap.Logger
func main() {
logger := NewLogger()
logger.Info("Test msg Main")
TestFunc(logger)
}
func TestFunc(logger *zap.Logger) {
logger.Info("Test msg TestFunc")
}
func NewLogger() *zap.Logger {
config := zap.NewDevelopmentConfig()
opts := []zap.Option{
zap.AddCallerSkip(1), // traverse call depth for more useful log lines
zap.AddCaller(),
}
logger, _ = config.Build(opts...)
return logger
}
This is the output I get with/without the addition of AddCaller() option
2021-03-01T15:00:02.927-0800 INFO runtime/proc.go:204 Test msg Main
2021-03-01T15:00:02.927-0800 INFO cmd/main.go:12 Test msg TestFunc
I am expecting something like
2021-03-01T15:00:02.927-0800 INFO runtime/proc.go:204 main Test msg Main
2021-03-01T15:00:02.927-0800 INFO cmd/main.go:12 TestFunc Test msg TestFunc
By default, the provided encoder presets (NewDevelopmentEncoderConfig used by NewDevelopmentConfig and NewProductionEncoderConfig used by NewProductionConfig) do not enable function name logging.
To enable function name, you need to enable caller (true by default) and set a non-empty value for config.EncoderConfig.FunctionKey.
Source: EncoderConfig
type EncoderConfig struct {
// Set the keys used for each log entry. If any key is empty, that portion
// of the entry is omitted.
...
CallerKey string `json:"callerKey" yaml:"callerKey"`
FunctionKey string `json:"functionKey" yaml:"functionKey"` // this needs to be set
StacktraceKey string `json:"stacktraceKey" yaml:"stacktraceKey"`
...
}
Example Console Logger:
func main() {
config := zap.NewDevelopmentConfig()
// if you're using console encoding, the FunctionKey value can be any
// non-empty string because console encoding does not print the key.
config.EncoderConfig.FunctionKey = "F"
logger, _ := config.Build()
logger.Info("Test Logging")
// Output: 2021-03-03T11:41:47.728+0800 INFO example/main.go:11 main.main Test Logging
}
Example JSON Logger:
func main() {
config := zap.NewProductionConfig()
// the FunctionKey value matters because it will become the JSON field
config.EncoderConfig.FunctionKey = "func"
logger, _ := config.Build()
log(logger)
// Output: {"level":"info","ts":1614743088.538128,"caller":"example/main.go:15","func":"main.log","msg":"Test Logging"}
}
func log(logger *zap.Logger) {
logger.Info("Test Logging")
}
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?
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"
}
I am using github.com/sirupsen/logrus for logging in my golang scripts, however I want to get the filename and the line number which is logging the message. I am able to get that using the below code:
package main
import (
"fmt"
"os"
"runtime"
"strings"
"github.com/sirupsen/logrus"
)
func GetLogger() (*logrus.Logger, *os.File) {
log := logrus.New()
log.SetReportCaller(true)
file, err := os.OpenFile("info.log", os.O_CREATE|os.O_APPEND, 0644)
if err != nil {
log.Fatal(err)
}
log.Out = file
log.Formatter = &logrus.TextFormatter{
CallerPrettyfier: func(f *runtime.Frame) (string, string) {
repopath := fmt.Sprintf("%s/src/github.com/bob", os.Getenv("GOPATH"))
filename := strings.Replace(f.File, repopath, "", -1)
return fmt.Sprintf("%s()", f.Function), fmt.Sprintf("%s:%d", filename, f.Line)
},
}
return log, file
}
However this gives log in the below format:
time="2020-04-02T11:43:19+05:30" level=info msg=Hello func="main.main()" file="D:/.../main.go:13"
But I want the log in format as below:
Apr 02 00:00:00 INFO main.go:20 : Hello this is a log line
How can a custom formatter be written to get this?
This option is included in the library itself since the end of 2018.
Just set 'SetReportCaller' to true.
Here's an example:
package main
import (
log "github.com/sirupsen/logrus"
)
func main() {
// Add this line for logging filename and line number!
log.SetReportCaller(true)
log.Println("hello world")
}
The output:
INFO[0000]/home/trex/go/src/awesomeProject/main.go:11 main.main() hello world
FYI
log.SetReportCaller(true)
log.SetFormatter(&log.JSONFormatter{
CallerPrettyfier: func(frame *runtime.Frame) (function string, file string) {
fileName := path.Base(frame.File) + ":" + strconv.Itoa(frame.Line)
//return frame.Function, fileName
return "", fileName
},
})
output:
{"file":"inspParse.go:290","level":"info","msg":"(3, 24), ","time":"2021-08-30T16:41:38+08:00"}
you can take advantage of codes below
package main
import (
"bytes"
"fmt"
"github.com/sirupsen/logrus"
"io"
"os"
"strings"
)
type MyFormatter struct {}
var levelList = [] string{
"PANIC",
"FATAL",
"ERROR",
"WARN",
"INFO",
"DEBUG",
"TRACE",
}
func (mf *MyFormatter) Format(entry *logrus.Entry) ([]byte, error){
var b *bytes.Buffer
if entry.Buffer != nil {
b = entry.Buffer
} else {
b = &bytes.Buffer{}
}
level := levelList[int(entry.Level)]
strList := strings.Split(entry.Caller.File, "/")
fileName := strList[len(strList)-1]
b.WriteString(fmt.Sprintf("%s - %s - [line:%d] - %s - %s\n",
entry.Time.Format("2006-01-02 15:04:05,678"), fileName,
entry.Caller.Line, level, entry.Message))
return b.Bytes(), nil
}
func MakeLogger(filename string, display bool) *logrus.Logger {
f, err := os.OpenFile(filename, os.O_CREATE|os.O_RDWR|os.O_APPEND, 0644)
if err != nil {
panic(err.Error())
}
logger := logrus.New()
if display {
logger.SetOutput(io.MultiWriter(os.Stdout, f))
} else {
logger.SetOutput(io.MultiWriter(f))
}
logger.SetReportCaller(true)
logger.SetFormatter(&MyFormatter{})
return logger
}
func main() {
logger := MakeLogger("/tmp/test.log", true)
logger.Info("hello world!")
}
Result:
/tmp/test.log
2021-11-24 00:49:10,678 - main.go - [line:58] - INFO - hello world!
The package you're using github.com/sirupsen/logrus produces structured log output: that is key/value pairs. It looks like you want just a plain text logger.
The standard logger import "log", produces output quite like what you want: log.New(out, "INFO", .Ldate|log.Ltime|log.Lshortfile). (See https://play.golang.org/p/LKitIwjPuVH on the playground)
Here's example output:
INFO 2009/11/10 23:00:00 prog.go:10: hello
In go1.14, the extra flag log.Lmsgprefix moves the INFO to before the message, if that's preferable (and you can wait).
If the standard library logger doesn't do what you want (and you're not prepared to live with it), why not just copy it and edit it, essentially making your own log package? It's around 400 lines of straightforward code, and by the time you remove the parts you don't want, it'll be a lot less.
The source is here: https://golang.org/src/log/log.go
Showing file, function name, and line number with logrus:
func Error(err error, msg ...interface{}) {
if pc, file, line, ok := runtime.Caller(1); ok {
file = file[strings.LastIndex(file, "/")+1:]
funcName := runtime.FuncForPC(pc).Name()
logrus.WithFields(
logrus.Fields{
"err": err,
"src": fmt.Sprintf("%s:%s:%d", file, funcName, line),
}).Error(msg...)
}
}
Call this in every log event.
Is it possible in GO to find structs or functions by criteria such as name, tag or interface? i.e something along the lines of command line tasks/verbs? i.e:
func cmd1() {
...
}
func cmd2() {
...
}
...
func cmdN() {
}
func main() {
// Inspect os.Args and call cmd{X}() based on args.
...
}
I don't mind what the exact mechanism is and if the final targets are functions or structs - the goal is to get something working by convention without any boilerplate code.
You could use reflection
package main
import (
"flag"
"fmt"
"reflect"
)
var cmd command
type command struct{}
func (c command) execute(name string) {
v := reflect.ValueOf(c)
cmd := v.MethodByName(name)
if !cmd.IsValid() {
fmt.Println(name + " not a command")
return
}
cmd.Call([]reflect.Value{})
}
func (c command) Cmd1() {
fmt.Println("command 1")
}
func (c command) Cmd2() {
fmt.Println("command 2")
}
func (c command) Cmd3() {
fmt.Println("command 3")
}
func main() {
flag.Parse()
cmd.execute(flag.Arg(0))
}
or you could use a map.
package main
import (
"flag"
"fmt"
)
func cmd1() {
fmt.Println("command 1")
}
func cmd2() {
fmt.Println("command 2")
}
func cmd3() {
fmt.Println("command 3")
}
var funcs = map[string]func(){
"cmd1": cmd1,
"cmd2": cmd2,
"cmd3": cmd3,
}
func main() {
flag.Parse()
if f, ok := funcs[flag.Arg(0)]; ok {
f()
} else {
fmt.Println(flag.Arg(0) + " command not found")
}
}
I used a similar approach in "How to test a collection of functions by reflection in Go?"
The idea is to list and find all the functions needed, in my case, functions for a certain struct type:
stype := reflect.ValueOf(s)
for _, fname := range funcNames {
sfunc := stype.MethodByName(fname)
// no parameter => empty slice of Value
ret := sfunc.Call([]reflect.Value{})
Given a function, is it possible to get its name? Say:
func foo() {
}
func GetFunctionName(i interface{}) string {
// ...
}
func main() {
// Will print "name: foo"
fmt.Println("name:", GetFunctionName(foo))
}
I was told that runtime.FuncForPC would help, but I failed to understand how to use it.
I found a solution:
package main
import (
"fmt"
"reflect"
"runtime"
)
func foo() {
}
func GetFunctionName(i interface{}) string {
return runtime.FuncForPC(reflect.ValueOf(i).Pointer()).Name()
}
func main() {
// This will print "name: main.foo"
fmt.Println("name:", GetFunctionName(foo))
}
Not exactly what you want, because it logs the filename and the line number, but here is how I do it in my Tideland Common Go Library (http://tideland-cgl.googlecode.com/) using the "runtime" package:
// Debug prints a debug information to the log with file and line.
func Debug(format string, a ...interface{}) {
_, file, line, _ := runtime.Caller(1)
info := fmt.Sprintf(format, a...)
log.Printf("[cgl] debug %s:%d %v", file, line, info)
I found a better solution, in this function down here you just simply pass a function and the output is gonna be simple and straight.
package main
import (
"reflect"
"runtime"
"strings"
)
func GetFunctionName(temp interface{}) string {
strs := strings.Split((runtime.FuncForPC(reflect.ValueOf(temp).Pointer()).Name()), ".")
return strs[len(strs)-1]
}
And this is an example of how you use this:
package main
import "fmt"
func main() {
fmt.Println(GetFunctionName(main))
}
And this is the answer you should expect:
main
By getting the function name of the previous caller:
import (
"os"
"runtime"
)
func currentFunction() string {
counter, _, _, success := runtime.Caller(1)
if !success {
println("functionName: runtime.Caller: failed")
os.Exit(1)
}
return runtime.FuncForPC(counter).Name()
}