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()
}
1 + 9 =
快来做第一个评论的人吧~