用户登录
用户注册

分享至

logrus日志自定义格式操作

  • 作者: 欠我一场雪
  • 来源: 51数据库
  • 2021-09-21

由于最近开始做一些go写的外围程序,因此开始关注go的日志,毕竟自带的logger模块功能较少。简单看了一些资料以后最开始使用seelog,性能感觉也不错,可以通过配置文件做很多额外处理。

但是由于协程的使用,需要日志标明协程号来方便日志查询请求应答。在一番尝试以后仍然没有解决,只能看看有没有其他日志库备选,因此选择了logrus(github上同类星星最多)

其实一开始看介绍时就看见过logrus这个库,但是之所以没有一开始考虑它, 是因为许多介绍都说它无法显示文件名和行号。不过时代是发展的,现在的logrus版本已经支持该设置。

logrus的基本使用这里就不再多说了,可以移步

1)设置行号

log.setreportcaller(true)

log.withfields(log.fields{"animal": "walrus"}).info("a walrus appears")

time="2020-03-29t15:58:09+08:00" level=info msg="a walrus appears" func=main.main file="f:/workspace/go/src/test/main.go:12" animal=walrus

设置行号通过setrportcaller实现,但是发现一个问题默认的设置file是全路径的,这样实际在日志中输出的话,日志会有很多无用数据,针对于我自己的需求仅为需要提示go文件名以及行号和goid即可。

2)自定义格式

logrus提供了setformatter可以设置格式,logrus自带的格式为(jsonformatter,textformatter)

func (s *logformatter) format(entry *log.entry) ([]byte, error) {
 timestamp := //日期格式实现
 entry.caller //调用者信息
 entry.data  //withfiled方式传入的数据
 msg := 自己想要的格式输出内容
 return []byte(msg), nil
}
 
func main() {
  log.setformatter(new(logformatter))  //注册自定义格式
}

自定义格式可选取的素材是log.entry中提供的,具体了解需要自行查看源码,这里仅简单介绍。其中caller是调用信息,有文件名,函数名,行数等信息,但是需要注意的是如果没有设置setrportcaller为true,这里无法获取改信息。

entry.data信息为withfields传入的map,如果需要一定的范式输出的话,可以好好利用。此外获取当前协程号也是在该函数中实现的,具体可以看文末的范例。定义完成后需要注册自定义格式。

3)日志写入文件

logrus默认是标准输出, setoutput方法可以更换输出io,设置文件句柄就是写入文件了。网上能找到很多通过改写io.write或者hook的方式拆分文件,推送文件等等其他各种需要,总之非常强大,不过我因为没有试过,所以就不在本文中描述了。但是统一项目的日志都是在指定目录按日期目录存放的,因此需要实现io.write,针对日期来划分文件。

package gotools
 
import (
 "bytes"
 "errors"
 "fmt"
 "os"
 "path/filepath"
 "runtime"
 "strconv"
 "strings"
 "time"
 
 log "github.com/sirupsen/logrus"
)
 
//日志自定义格式
type logformatter struct{}
 
//格式详情
func (s *logformatter) format(entry *log.entry) ([]byte, error) {
 timestamp := time.now().local().format("0102-150405.000")
 var file string
 var len int
 if entry.caller != nil {
 file = filepath.base(entry.caller.file)
 len = entry.caller.line
 }
 //fmt.println(entry.data)
 msg := fmt.sprintf("%s [%s:%d][goid:%d][%s] %s\n", timestamp, file, len, getgid(), strings.toupper(entry.level.string()), entry.message)
 return []byte(msg), nil
}
 
func getgid() uint64 {
 b := make([]byte, 64)
 b = b[:runtime.stack(b, false)]
 b = bytes.trimprefix(b, []byte("goroutine "))
 b = b[:bytes.indexbyte(b, ' ')]
 n, _ := strconv.parseuint(string(b), 10, 64)
 return n
}
 
type logfilewriter struct {
 file   *os.file
 logpath string
 filedate string //判断日期切换目录
 appname string
 encoding string
}
 
func (p *logfilewriter) write(data []byte) (n int, err error) {
 if p == nil {
 return 0, errors.new("logfilewriter is nil")
 }
 if p.file == nil {
 return 0, errors.new("file not opened")
 }
 
 //判断是否需要切换日期
 filedate := time.now().format("20060102")
 if p.filedate != filedate {
 p.file.close()
 err = os.mkdirall(fmt.sprintf("%s/%s", p.logpath, filedate), os.modeperm)
 if err != nil {
  return 0, err
 }
 filename := fmt.sprintf("%s/%s/%s-%s.log", p.logpath, filedate, p.appname, filedate)
 
 p.file, err = os.openfile(filename, os.o_wronly|os.o_append|os.o_create|os.o_sync, 0600)
 if err != nil {
  return 0, err
 }
 
 }
 if p.encoding != "" {
 datatoencode := convertstringtobyte(string(data), p.encoding)
 n, e := p.file.write(datatoencode)
 return n, e
 }
 
 n, e := p.file.write(data)
 return n, e
 
}
 
//初始化日志
func initlog(logpath string, appname string, encoding string) {
 filedate := time.now().format("20060102")
 //创建目录
 err := os.mkdirall(fmt.sprintf("%s/%s", logpath, filedate), os.modeperm)
 if err != nil {
 log.error(err)
 return
 }
 
 filename := fmt.sprintf("%s/%s/%s-%s.log", logpath, filedate, appname, filedate)
 file, err := os.openfile(filename, os.o_wronly|os.o_append|os.o_create|os.o_sync, 0600)
 if err != nil {
 log.error(err)
 return
 }
 
 filewriter := logfilewriter{file, logpath, filedate, appname, encoding}
 log.setoutput(&filewriter)
 
 log.setreportcaller(true)
 log.setformatter(new(logformatter)) 
}
package main
 
import (
 log "github.com/sirupsen/logrus"
 "sand.com/gotools"
)
 
func main() {
 
 gotools.initlog("./log/", "test", "gb18030")
 log.withfields(log.fields{"animal": "walrus"}).info("a walrus appears")
 log.info("测试中文")
}

因为其他系统的日志都是gbk编码的,因此在io.write时做了编码转换,如果没有这样需要的话最好不要做这种转换影响性能

0329-163148.199 [main.go:11][goid:1][info] a walrus appears

0329-163148.199 [main.go:12][goid:1][info] 测试中文

补充知识: go使用logrus同时输出屏幕和文件日志

我就废话不多说了,大家还是直接看代码吧~

func initlog() {
  //设置输出样式,自带的只有两种样式logrus.jsonformatter{}和logrus.textformatter{}
  log.setformatter(&log.textformatter{})
  log.setoutput(os.stdout)
  //设置output,默认为stderr,可以为任何io.writer,比如文件*os.file
  file, err := os.openfile("checkemstools.log", os.o_create|os.o_wronly|os.o_append, 0666)
  writers := []io.writer{
    file,
    os.stdout}
  //同时写文件和屏幕
  fileandstdoutwriter := io.multiwriter(writers...)
  if err == nil {
   log.setoutput(fileandstdoutwriter)
  } else {
   log.info("failed to log to file.")
  }
  //设置最低loglevel
  log.setlevel(log.infolevel)
}

以上这篇logrus日志自定义格式操作就是小编分享给大家的全部内容了,希望能给大家一个参考,也希望大家多多支持。

软件
前端设计
程序设计
Java相关