golang logrus库
文章最后更新时间为:2021年03月17日 11:04:54
golang 的笔记,内容多来源于互联网,放这里方便查找。
基本用法
package main
import (
"os"
log "github.com/sirupsen/logrus"
)
func init() {
// 设置日志格式为json格式
log.SetFormatter(&log.JSONFormatter{})
// 设置将日志输出到标准输出(默认的输出为stderr,标准错误)
// 日志消息输出可以是任意的io.writer类型
log.SetOutput(os.Stdout)
// 设置日志级别为warn以上
log.SetLevel(log.InfoLevel)
}
func main() {
log.Info("A group of walrus emerges from the ocean")
log.Warn("The group's number increased tremendously!")
}
日志分割切片
package main
import (
"time"
rotatelogs "github.com/lestrrat-go/file-rotatelogs"
log "github.com/sirupsen/logrus"
)
func init() {
path := "./go.log"
/* 日志轮转相关函数
`WithLinkName` 为最新的日志建立软连接
`WithRotationTime` 设置日志分割的时间,隔多久分割一次
WithMaxAge 和 WithRotationCount二者只能设置一个
`WithMaxAge` 设置文件清理前的最长保存时间
`WithRotationCount` 设置文件清理前最多保存的个数
*/
// 下面配置日志每隔 1 分钟轮转一个新文件,保留最近 3 分钟的日志文件,多余的自动清理掉。
writer, _ := rotatelogs.New(
path+".%Y%m%d%H%M",
rotatelogs.WithLinkName(path),
rotatelogs.WithMaxAge(time.Duration(180)*time.Second),
rotatelogs.WithRotationTime(time.Duration(60)*time.Second),
)
log.SetOutput(writer)
//log.SetFormatter(&log.JSONFormatter{})
}
func main() {
for {
log.Info("hello, world!")
time.Sleep(time.Duration(2) * time.Second)
}
}
Hook
hook内部实现
Hook 接口定义如下:
type Hook interface {
// 定义哪些等级的日志触发 hook 机制
Levels() []Level
// hook 触发器的具体执行操作
// 如果 Fire 执行失败,错误日志会重定向到标准错误流
Fire(*Entry) error
}
那logrus的内部是怎么实现触发的呢, logrus中有个内部结构LevelHooks用来存储所有定义的 hook 函数。
// 存储全局 hooks, 以日志等级为键聚合存储
type LevelHooks map[Level][]Hook
// 添加 hooks
func (hooks LevelHooks) Add(hook Hook) {
for _, level := range hook.Levels() {
hooks[level] = append(hooks[level], hook)
}
}
// 根据日志等级触发 hooks
func (hooks LevelHooks) Fire(level Level, entry *Entry) error {
for _, hook := range hooks[level] {
if err := hook.Fire(entry); err != nil {
return err
}
}
return nil
}
在打印日志时, entry会调用 fireHooks()函数,该函数会触发所有对应的日志等级 的 hook 逻辑。
// 触发 hooks
func (entry *Entry) fireHooks() {
entry.Logger.mu.Lock()
defer entry.Logger.mu.Unlock()
err := entry.Logger.Hooks.Fire(entry.Level, entry)
if err != nil {
fmt.Fprintf(os.Stderr, "Failed to fire hook: %v\n", err)
}
}
自带的syslog
package main
import (
"log/syslog"
log "github.com/sirupsen/logrus"
logrus_syslog "github.com/sirupsen/logrus/hooks/syslog"
airbrake "gopkg.in/gemnasium/logrus-airbrake-hook.v2"
)
func init() {
// Use the Airbrake hook to report errors that have Error severity or above to
// an exception tracker. You can create custom hooks, see the Hooks section.
log.AddHook(airbrake.NewHook(123, "xyz", "production"))
hook, err := logrus_syslog.NewSyslogHook("udp", "localhost:514", syslog.LOG_INFO, "")
if err != nil {
log.Error("Unable to connect to local syslog daemon")
} else {
log.AddHook(hook)
}
}
func main() {
log.Info("A group of walrus emerges from the ocean")
log.Warn("The group's number increased tremendously!")
}
自定义hook,输出error/panic到文件中
在这个例子中我们希望当系统发生error或者panic的时候,将错误日志打印到单独的 err.log 文件中便于我们排查错误。
package main
import (
"os"
log "github.com/sirupsen/logrus"
)
// MyHook ...
type MyHook struct {
}
// Levels 只定义 error 和 panic 等级的日志,其他日志等级不会触发 hook
func (h *MyHook) Levels() []log.Level {
return []log.Level{
log.ErrorLevel,
log.PanicLevel,
}
}
// Fire 将异常日志写入到指定日志文件中
func (h *MyHook) Fire(entry *log.Entry) error {
f, err := os.OpenFile("err.log", os.O_RDWR|os.O_CREATE|os.O_APPEND, 0644)
if err != nil {
return err
}
if _, err := f.Write([]byte(entry.Message)); err != nil {
return err
}
return nil
}
func main() {
log.AddHook(&MyHook{})
log.Error("some errors\n")
log.Panic("some panic\n")
log.Print("hello world\n")
}
Hook日志分割
// log_hook.go
package main
import (
"fmt"
"io"
"os"
"path/filepath"
"sync"
"time"
"github.com/pkg/errors"
"github.com/rifflock/lfshook"
"github.com/sirupsen/logrus"
)
//自实现 logrus hook
func getLogger(module string) *logrus.Logger {
//实例化
logger := logrus.New()
//设置输出
logger.Out = os.Stdout
//设置日志级别
logger.SetLevel(logrus.DebugLevel)
//设置日志格式
//自定writer就行, hook 交给 lfshook
logger.AddHook(newLogrusHook("./log", module))
logger.SetFormatter(&logrus.JSONFormatter{
TimestampFormat: "2006-01-02 15:04:05",
})
return logger
}
//确保每次调用使用的文件都是唯一的。
func GetNewFieldLoggerContext(module, appField string) *logrus.Entry {
logger := getLogger(module)
return logger.WithFields(logrus.Fields{
"app": appField,
})
}
type LogWriter struct {
logDir string //日志根目录地址。
module string //模块 名
curFileName string //当前被指定的filename
curBaseFileName string //在使用中的file
turnCateDuration time.Duration
mutex sync.RWMutex
outFh *os.File
}
func (w *LogWriter) Write(p []byte) (n int, err error) {
w.mutex.Lock()
defer w.mutex.Unlock()
if out, err := w.getWriter(); err != nil {
return 0, errors.New("failed to fetch target io.Writer")
} else {
return out.Write(p)
}
}
func (w *LogWriter) getFileName() string {
base := time.Now().Truncate(w.turnCateDuration)
return fmt.Sprintf("%s/%s/%s_%s", w.logDir, base.Format("2006-01-02"), w.module, base.Format("15"))
}
func (w *LogWriter) getWriter() (io.Writer, error) {
fileName := w.curBaseFileName
//判断是否有新的文件名
//会出现新的文件名
baseFileName := w.getFileName()
if baseFileName != fileName {
fileName = baseFileName
}
dirname := filepath.Dir(fileName)
if err := os.MkdirAll(dirname, 0755); err != nil {
return nil, errors.Wrapf(err, "failed to create directory %s", dirname)
}
fileHandler, err := os.OpenFile(fileName, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0644)
if err != nil {
return nil, errors.Errorf("failed to open file %s", err)
}
w.outFh.Close()
w.outFh = fileHandler
w.curBaseFileName = fileName
w.curFileName = fileName
return fileHandler, nil
}
func New(logPath, module string, duration time.Duration) *LogWriter {
return &LogWriter{
logDir: logPath,
module: module,
turnCateDuration: duration,
curFileName: "",
curBaseFileName: "",
}
}
func newLogrusHook(logPath, moduel string) logrus.Hook {
logrus.SetLevel(logrus.WarnLevel)
writer := New(logPath, moduel, time.Hour*2)
lfsHook := lfshook.NewHook(lfshook.WriterMap{
logrus.DebugLevel: writer,
logrus.InfoLevel: writer,
logrus.WarnLevel: writer,
logrus.ErrorLevel: writer,
logrus.FatalLevel: writer,
logrus.PanicLevel: writer,
}, &logrus.TextFormatter{DisableColors: true})
// writer 生成新的log文件类型 writer 在通过new hook函数 消费 fire 函数
// writer 是实现了writer 接口的库,在日志调用write是做预处理
return lfsHook
}
func main() {
lg := GetNewFieldLoggerContext("test", "d")
lg.Logger.Info("????")
}
logger实例持有了 自定义的 io.writer 结构体,在消费Fire函数时,会调用Write方法,此时通过Truncate时间切片函数逻辑判断需要写入的文件。或创建新的文件。
注: 文章提供的代码是按天切分文件夹的,文件夹内模块日志再按2小时切分。可自行替换成按模块切分。
dingding hook
package main
import (
"github.com/leafney/dingtalkrus"
"github.com/sirupsen/logrus"
"os"
)
func main() {
logrus.SetFormatter(&logrus.JSONFormatter{})
logrus.SetOutput(os.Stderr)
logrus.SetLevel(logrus.DebugLevel)
logrus.AddHook(dingtalkrus.NewHook(
"", // dingtalk token
"", // dingtalk secret
dingtalkrus.LevelThreshold(logrus.ErrorLevel)),
)
logrus.Info("This is the info test message.")
logrus.WithFields(dingtalkrus.SendTextMsg("This is the warn test message.",[]string{},false)).Warn()
logrus.WithFields(dingtalkrus.SendMarkdownMsg("杭州天气","#### 杭州天气 \n 9度,西北风1级,空气良89,相对温度73%\n",[]string{},false)).Error()
}