GoLang logrus 日志组件

Logrus 日志

1
go get github.com/sirupsen/logrus
1
2
3
4
5
6
7
8
9
10
11
import (
log "github.com/sirupsen/logrus"
)

func main(){
log.SetFormatter(&log.JSONFormatter{
TimestampFormat:"2006-01-02 15:04:05",
})
// 日志的输出级别
log.SetLevel(log.InfoLevel)
}

1. 实现日志分割

  1. Linux logrotate

  2. file-rotatelogs

1
go get github.com/lestrrat-go/file-rotatelogs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import (
rotatelogs "github.com/lestrrat-go/file-rotatelogs"
"github.com/sirupsen/logrus"
)

func main() {

content, err := rotatelogs.New(
"/var/log/cli.log."+"-%Y%m%d%H%M",
rotatelogs.WithLinkName("/var/log/cli.log"), // 生成软链,指向最新日志文件
//MaxAge and RotationCount cannot be both set 两者不能同时设置
rotatelogs.WithMaxAge(6*time.Minute), //clear 最小分钟为单位
//rotatelogs.WithRotationCount(5), //number 默认7份 大于7份 或到了清理时间 开始清理
rotatelogs.WithRotationTime(time.Minute), //rotate 最小为1分钟轮询。默认60s 低于1分钟就按1分钟来
rotatelogs.ForceNewFile(),
)
logrus.SetOutput(content)
}

强行生成新的日志文件

1
2
3
4
5
6
7
8
rl := rotatelogs.New(...)

signal.Notify(ch, syscall.SIGHUP)

go func(ch chan os.Signal) {
<-ch
rl.R
}

2. 自定义Logger

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
package main

import (
"github.com/sirupsen/logrus"
"os"
)

// logrus提供了New()函数来创建一个logrus的实例.
// 项目中,可以创建任意数量的logrus实例.
var log = logrus.New()

func main() {
// 为当前logrus实例设置消息的输出,同样地,
// 可以设置logrus实例的输出到任意io.writer
log.Out = os.Stdout

// 为当前logrus实例设置消息输出格式为json格式.
// 同样地,也可以单独为某个logrus实例设置日志级别和hook,这里不详细叙述.
log.Formatter = &logrus.JSONFormatter{}

log.WithFields(logrus.Fields{
"animal": "walrus",
"size": 10,
}).Info("A group of walrus emerges from the ocean")
}

3. hook接口方法

1
2
3
4
5
6
// logrus在记录Levels()返回的日志级别的消息时会触发HOOK,
// 按照Fire方法定义的内容修改logrus.Entry.
type Hook interface {
Levels() []Level
Fire(*Entry) error
}

一个简单自定义hook如下,DefaultFieldHook定义会在所有级别的日志消息中加入默认字段appName=”myAppName”.

1
2
3
4
5
6
7
8
9
10
11
type DefaultFieldHook struct {
}

func (hook *DefaultFieldHook) Fire(entry *log.Entry) error {
entry.Data["appName"] = "MyAppName"
return nil
}

func (hook *DefaultFieldHook) Levels() []log.Level {
return log.AllLevels
}

hook的使用也很简单,在初始化前调用log.AddHook(hook)添加相应的hook即可.

logrus官方仅仅内置了sysloghook.

3.1 Email Hook

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
func Email(){
logger:= logrus.New()
//parameter"APPLICATION_NAME", "HOST", PORT, "FROM", "TO"
//首先开启smtp服务,最后两个参数是smtp的用户名和密码
hook, err := logrus_mail.NewMailAuthHook("testapp", "smtp.163.com",25,"username@163.com","username@163.com","smtp_name","smtp_password")
if err == nil {
logger.Hooks.Add(hook)
}
//生成*Entry
var filename="123.txt"
contextLogger :=logger.WithFields(logrus.Fields{
"file":filename,
"content": "GG",
})
//设置时间戳和message
contextLogger.Time=time.Now()
contextLogger.Message="这是一个hook发来的邮件"
//只能发送Error,Fatal,Panic级别的log
contextLogger.Level=logrus.FatalLevel

//使用Fire发送,包含时间戳,message
hook.Fire(contextLogger)
}

3.2 Logrus-Hook-Slack

1
go get github.com/johntdyer/slackrus
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
package main

import (
logrus "github.com/sirupsen/logrus"
"github.com/johntdyer/slackrus"
"os"
)

func main() {

logrus.SetFormatter(&logrus.JSONFormatter{})

logrus.SetOutput(os.Stderr)

logrus.SetLevel(logrus.DebugLevel)

logrus.AddHook(&slackrus.SlackrusHook{
HookURL: "https://hooks.slack.com/services/abc123/defghijklmnopqrstuvwxyz",
AcceptedLevels: slackrus.LevelThreshold(logrus.DebugLevel),
Channel: "#slack-testing",
IconEmoji: ":ghost:",
Username: "foobot",
})

logrus.Warn("warn")
logrus.Info("info")
logrus.Debug("debug")
}

3.3 Logrus-Hook 日志分隔

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
import (
"github.com/lestrrat-go/file-rotatelogs"
"github.com/rifflock/lfshook"
log "github.com/sirupsen/logrus"
"time"
)

func newLfsHook(logLevel *string, maxRemainCnt uint) log.Hook {
writer, err := rotatelogs.New(
logName+".%Y%m%d%H",
// WithLinkName为最新的日志建立软连接,以方便随着找到当前日志文件
rotatelogs.WithLinkName(logName),

// WithRotationTime设置日志分割的时间,这里设置为一小时分割一次
rotatelogs.WithRotationTime(time.Hour),

// WithMaxAge和WithRotationCount二者只能设置一个,
// WithMaxAge设置文件清理前的最长保存时间,
// WithRotationCount设置文件清理前最多保存的个数.
//rotatelogs.WithMaxAge(time.Hour*24),
rotatelogs.WithRotationCount(maxRemainCnt),
)

if err != nil {
log.Errorf("config local file system for logger error: %v", err)
}

level, ok := logLevels[*logLevel]

if ok {
log.SetLevel(level)
} else {
log.SetLevel(log.WarnLevel)
}

lfsHook := lfshook.NewHook(lfshook.WriterMap{
log.DebugLevel: writer,
log.InfoLevel: writer,
log.WarnLevel: writer,
log.ErrorLevel: writer,
log.FatalLevel: writer,
log.PanicLevel: writer,
}, &log.TextFormatter{DisableColors: true})

return lfsHook
}

3.4 Logrus-Dingding-Hook 阿里钉钉群机器人

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
package utils

import (
"bytes"
"encoding/json"
"fmt"
"log"
"net/http"
"github.com/sirupsen/logrus"
)

var allLvls = []logrus.Level{
logrus.DebugLevel,
logrus.InfoLevel,
logrus.WarnLevel,
logrus.ErrorLevel,
logrus.FatalLevel,
logrus.PanicLevel,
}

func NewDingHook(url, app string, thresholdLevel logrus.Level) *dingHook {
temp := []logrus.Level{}
for _, v := range allLvls {
if v <= thresholdLevel {
temp = append(temp, v)
}
}
hook := &dingHook{apiUrl: url, levels: temp, appName: app}
hook.jsonBodies = make(chan []byte)
hook.closeChan = make(chan bool)
//开启chan 队列 执行post dingding hook api
go hook.startDingHookQueueJob()
return hook
}

func (dh *dingHook) startDingHookQueueJob() {
for {
select {
case <-dh.closeChan:
return
case bs := <-dh.jsonBodies:
res, err := http.Post(dh.apiUrl, "application/json", bytes.NewBuffer(bs))
if err != nil {
log.Println(err)
}
if res != nil && res.StatusCode != 200 {
log.Println("dingHook go chan http post error", res.StatusCode)
}
}
}

}

type dingHook struct {
apiUrl string
levels []logrus.Level
appName string
jsonBodies chan []byte
closeChan chan bool
}

// Levels sets which levels to sent to slack
func (dh *dingHook) Levels() []logrus.Level {
return dh.levels
}

//Fire2 这个异步有可能导致 最后一条消息丢失,main goroutine 提前结束到导致 子线程http post 没有发送
func (dh *dingHook) Fire2(e *logrus.Entry) error {
msg, err := e.String()
if err != nil {
return err
}
dm := dingMsg{Msgtype: "text"}
dm.Text.Content = fmt.Sprintf("%s \n %s", dh.appName, msg)
bs, err := json.Marshal(dm)
if err != nil {
return err
}
dh.jsonBodies <- bs
return nil
}
func (dh *dingHook) Fire(e *logrus.Entry) error {
msg, err := e.String()
if err != nil {
return err
}
dm := dingMsg{Msgtype: "text"}
dm.Text.Content = fmt.Sprintf("%s \n %s", dh.appName, msg)
bs, err := json.Marshal(dm)
if err != nil {
return err
}
res, err := http.Post(dh.apiUrl, "application/json", bytes.NewBuffer(bs))
if err != nil {
return err
}
if res != nil && res.StatusCode != 200 {
return fmt.Errorf("dingHook go chan http post error %d", res.StatusCode)
}
return nil
}

type dingMsg struct {
Msgtype string `json:"msgtype"`
Text struct {
Content string `json:"content"`
} `json:"text"`
}

使用方法

1
2
3
4
5
6
7
8
9
10
11
func initSlackLogrus() {
lvl := logrus.InfoLevel
//钉钉群机器人API地址
apiUrl := viper.GetString("logrus.dingHookUrl")
dingHook := utils.NewDingHook(apiUrl, "Felix", lvl)

logrus.SetLevel(lvl)
logrus.SetFormatter(&logrus.JSONFormatter{TimestampFormat: "06-01-02T15:04:05"})
logrus.SetReportCaller(true)
logrus.AddHook(dingHook)
}