前言:
这次聊下最近使用logrus的心得,没有高深的源码,只是一些相对高级的配置吧。golang默认的log模块略显简陋,连基本的info, warn, error 打印方法都没有,不是太适用。 在使用logrus模块之前,我先前都在使用自己封装的log模块,虽然比不上logrus那么多功能,够用是没问题的。
话说,golang社区里排名最高的日志模块就那么几个,logrus,log4j … 单看github的活跃度logrus显得更显一筹了。国内外比较出名的golang开源项目,大多都是自己实现的日志磨矿。
该文章后续会有更新, 原文地址 http://xiaorui.cc/?p=4963
logrus日志切分功能 ?
对的,logrus默认是没有像logrotate那样的日志切割功能,我曾经在issue里问过sirupsen, 为毛没有日志基本的logrotate ? 他的回复说,不需要切分,首先该功能不是核心功能,最重要的是 作者建议大家把日志输出到其他的日志server里,比如 filebeat, logstash, syslog, rsyslog, Fluentd 等等。 logrus官方支持的日志服务列表有很多,只要我们能想到的基本都有。
那么,如果我现在logrus实现日志切分,该如何操作?两种方法.
第一种,借助于 linux logrotate命令 。
/devops/app/go/src/task_dispatcher/logs/*.log { daily sharedscripts dateext nocompress size 1G missingok notifempty copytruncate rotate 3
第二种方法, 使用logrus的hook来加载 github.com/lestrrat/go-file-rotatelogs 模块. 每次当我们写入日志的时候,logrus都会调用 go-file-rotatelogs 来判断日志是否要进行切分… go-file-rotatelogs 可以实现linux logratate的大多数功能。
package context import ( "github.com/lestrrat/go-file-rotatelogs" "github.com/rifflock/lfshook" log "github.com/sirupsen/logrus" "github.com/pkg/errors" "path" "time" "os" "fmt" "bufio" ) func InitLogger() { baseLogPath := path.Join(GlobalConfig.LogConf.Logdir, GlobalConfig.LogConf.Filename) writer, err := rotatelogs.New( baseLogPath+".%Y%m%d%H%M", rotatelogs.WithLinkName(baseLogPath), // 生成软链,指向最新日志文件 rotatelogs.WithMaxAge(7*24*time.Hour), // 文件最大保存时间 rotatelogs.WithRotationTime(24*time.Hour), // 日志切割时间间隔 ) if err != nil { log.Errorf("config local file system logger error. %v", errors.WithStack(err)) } //log.SetFormatter(&log.TextFormatter{}) switch level := GlobalConfig.LogConf.LogLevel; level { /* 如果日志级别不是debug就不要打印日志到控制台了 */ case "debug": log.SetLevel(log.DebugLevel) log.SetOutput(os.Stderr) case "info": setNull() log.SetLevel(log.InfoLevel) case "warn": setNull() log.SetLevel(log.WarnLevel) case "error": setNull() log.SetLevel(log.ErrorLevel) default: setNull() log.SetLevel(log.InfoLevel) } lfHook := lfshook.NewHook(lfshook.WriterMap{ log.DebugLevel: writer, // 为不同级别设置不同的输出目的 log.InfoLevel: writer, log.WarnLevel: writer, log.ErrorLevel: writer, log.FatalLevel: writer, log.PanicLevel: writer, }) log.AddHook(lfHook) } func setNull() { src, err := os.OpenFile(os.DevNull, os.O_APPEND|os.O_WRONLY, os.ModeAppend) if err!= nil{ fmt.Println("err", err) } writer := bufio.NewWriter(src) log.SetOutput(writer) }
既然作者这么推荐大家使用logrus hook的方式来扩展日志功能,那么怎么自己写一个hook模块? 其实很简单,只需要实现这几个方法就可以了,主要看 Fire 函数… 当我们调用logrus.addHook把自定义的hook加载后,每次我们写日志的时候,都会调用Fire方法。
logrus hook 是一个值得深入学习的设计,你可以轻易适用hook来实现多文件写入。 比如,error级别的日志独立输出到error.log文件里,其他都放在一起。
package syslog import ( "fmt" "log/syslog" "os" "github.com/sirupsen/logrus" ) type SyslogHook struct { Writer *syslog.Writer SyslogNetwork string SyslogRaddr string } func NewSyslogHook(network, raddr string, priority syslog.Priority, tag string) (*SyslogHook, error) { w, err := syslog.Dial(network, raddr, priority, tag) return &SyslogHook{w, network, raddr}, err } func (hook *SyslogHook) Fire(entry *logrus.Entry) error { line, err := entry.String() if err != nil { fmt.Fprintf(os.Stderr, "Unable to read entry, %v", err) return err } switch entry.Level { case logrus.PanicLevel: return hook.Writer.Crit(line) case logrus.FatalLevel: return hook.Writer.Crit(line) case logrus.ErrorLevel: return hook.Writer.Err(line) case logrus.WarnLevel: return hook.Writer.Warning(line) case logrus.InfoLevel: return hook.Writer.Info(line) case logrus.DebugLevel: return hook.Writer.Debug(line) default: return nil } } func (hook *SyslogHook) Levels() []logrus.Level { return logrus.AllLevels }
总结,个人对logrus的功能不是太满意,压根用不到的功能贼多,真正会用到的功能不够简单干练。 针对logrus日志切割的问题,我给作者提过两次了,作者忽忽悠悠把issue给关了。
END.