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.
How does one parse multiple yamls in a file similar to how kubectl does it?
example.yaml
---
a: Easy!
b:
c: 0
d: [1, 2]
---
a: Peasy!
b:
c: 1000
d: [3, 4]
There's a difference in behavior between gopkg.in/yaml.v2 and gopkg.in/yaml.v3:
V2: https://play.golang.org/p/XScWhdPHukO
V3: https://play.golang.org/p/OfFY4qH5wW2
Both implementations produce an incorrect result IMHO but V3 is apparently slightly worse.
There's a workaround. If you slightly change code in the accepted answer, it works correctly and in the same fashion with both versions of yaml package: https://play.golang.org/p/r4ogBVcRLCb
Current gopkg.in/yaml.v3 Deocder produces quite correct result, you just need to pay attention on creating new structure for each document and check it was parsed correctly (with nil check), and handle EOF error correctly:
package main
import "fmt"
import "gopkg.in/yaml.v3"
import "os"
import "errors"
import "io"
type Spec struct {
Name string `yaml:"name"`
}
func main() {
f, err := os.Open("spec.yaml")
if err != nil {
panic(err)
}
d := yaml.NewDecoder(f)
for {
// create new spec here
spec := new(Spec)
// pass a reference to spec reference
err := d.Decode(&spec)
// check it was parsed
if spec == nil {
continue
}
// break the loop in case of EOF
if errors.Is(err, io.EOF) {
break
}
if err != nil {
panic(err)
}
fmt.Printf("name is '%s'\n", spec.Name)
}
}
Test file spec.yaml:
---
name: "doc first"
---
name: "second"
---
---
name: "skip 3, now 4"
---
Solution I found using gopkg.in/yaml.v2:
package main
import (
"bytes"
"fmt"
"io/ioutil"
"log"
"path/filepath"
"gopkg.in/yaml.v2"
)
type T struct {
A string
B struct {
RenamedC int `yaml:"c"`
D []int `yaml:",flow"`
}
}
func main() {
filename, _ := filepath.Abs("./example.yaml")
yamlFile, err := ioutil.ReadFile(filename)
if err != nil {
panic(err)
}
r := bytes.NewReader(yamlFile)
dec := yaml.NewDecoder(r)
var t T
for dec.Decode(&t) == nil {
fmt.Printf("a :%v\nb :%v\n", t.A, t.B)
}
}
The plain demo code works as they integrated the Logrus's config and the logic of main, as follows
func main() {
var filename string = "logfile.log"
f, err := os.OpenFile(filename, os.O_WRONLY|os.O_APPEND|os.O_CREATE, 0644)
Formatter := new(log.TextFormatter)
Formatter.TimestampFormat = "02-01-2006 15:04:05"
Formatter.FullTimestamp = true
log.SetFormatter(Formatter)
if err != nil {
fmt.Println(err)
} else {
log.SetOutput(f)
}
log.Info("Some info. Earth is not flat")
log.Warning("This is a warning")
log.Error("Not fatal. An error. Won't stop execution")
}
But in real world, the config of logrus should be seperated into individual file, consider the file structure as :
logrusdemo/
├── main.go
└── mylib
├── aa.go
└── bb.go
And I want to share the same config in these files:
The source code of aa.go:
package mylib
// GetTestA testing
func GetTestA() string {
//log.info("entering aa.go")
return "001"
}
The source code of bb.go:
package mylib
// GetTestB testing
func GetTestB() string {
//log.info("entering bb.go")
return "001"
}
The source code of main.go:
package main
import (
"fmt"
"logrusdemo/mylib"
)
func main() {
//log.info("entering main")
fmt.Printf("%v", mylib.GetTestA())
fmt.Printf("%v", mylib.GetTestB())
}
I was wondering how to make logrus works in this situation?
You can do so by passing your logger to your structs / functions.
package main
import (
"fmt"
"github.com/sirupsen/logrus"
"logrusdemo/mylib"
)
func main() {
log = logrus.New()
var filename string = "logfile.log"
f, err := os.OpenFile(filename, os.O_WRONLY|os.O_APPEND|os.O_CREATE, 0644)
Formatter := new(log.TextFormatter)
Formatter.TimestampFormat = "02-01-2006 15:04:05"
Formatter.FullTimestamp = true
log.SetFormatter(Formatter)
if err != nil {
fmt.Println(err)
} else {
log.SetOutput(f)
}
log.Info("Some info. Earth is not flat")
log.Warning("This is a warning")
log.Error("Not fatal. An error. Won't stop execution")
fmt.Printf("%v", mylib.GetTestA(log))
fmt.Printf("%v", mylib.GetTestB(log))
}
And
package mylib
import (
"github.com/sirupsen/logrus"
)
// GetTestA testing
func GetTestA(log *logrus.Logger) string {
log.Info("entering aa.go")
return "001"
}
If you don't want to pass it as an argument, you can also pass your logger in a context, or use it as a global.
I'm going to parse HCL configuration file using this repository.
package main
import (
"fmt"
hclParser "github.com/hashicorp/hcl/hcl/parser"
)
const (
EXAMPLE_CONFIG_STRING = "log_dir = \"/var/log\""
)
func main() {
// parse HCL configuration
if astFile, err := hclParser.Parse([]byte(EXAMPLE_CONFIG_STRING)); err == nil {
fmt.Println(astFile)
} else {
fmt.Println("Parsing failed.")
}
}
How can I parse log_dir in this case?
github.com/hashicorp/hcl/hcl/parser is a low-level package. Use the high-level API instead:
package main
import (
"fmt"
"github.com/hashicorp/hcl"
)
type T struct {
LogDir string `hcl:"log_dir"`
}
func main() {
var t T
err := hcl.Decode(&t, `log_dir = "/var/log"`)
fmt.Println(t.LogDir, err)
}
There is also DecodeObject available if you really want to deal with the AST yourself.
I need help understanding how to demonize a process in Go.
package main
import (
"fmt"
"os"
)
func start() {
var procAttr os.ProcAttr
procAttr.Files = []*os.File{nil, nil, nil}
_, err := os.StartProcess("/Path/prog", nil, &procAttr)
if err != nil {
fmt.Printf("%v", err)
}
}
func main () {
start()
}
If you start this code on the command line the program returns control, but is still connected with cmd. Closing the cmd closes the program.
How can I decouple it from the cmd? Adding:
procAttr.Sys.HideWindow = true
Results in this error: "panic" to wrong memory pointer
I asked in 'golang-nuts', and found out that Go has a link option:
go tool 8l -o output.exe -Hwindowsgui input.8
Here is a fake daemon in go; it's simple to use: https://github.com/icattlecoder/godaemon
An example:
package main
import (
_ "github.com/icattlecoder/godaemon"
"log"
"net/http"
)
func main() {
mux := http.NewServeMux()
mux.HandleFunc("/index", func(rw http.ResponseWriter, req *http.Request) {
rw.Write([]byte("hello, golang!\n"))
})
log.Fatalln(http.ListenAndServe(":7070", mux))
}