相关文章推荐

# 日志框架logrus

介绍

Logrus 是一个结构化日志处理框架,并且 api 完全兼容 golang 标准库的 logger 日志 api , 意味着你可以直接使用 Logrus 替换 logger logrus 具有以下特性:

  • 完全兼容 golang 标准库日志模块: logrus 拥有七种日志级别: debug info warn error fatal panic和Trace ,这是 golang 标准库日志模块的 API 的超集。如果您的项目使用标准库日志模块,完全可以以最低的代价迁移到 logrus 上。
  • 可扩展的 Hook 机制:允许使用者通过 hook 的方式将日志分发到任意地方,如本地文件系统、标准输出、 logstash elasticsearch 或者 mq 等,或者通过 hook 定义日志内容和格式等。
  • 可选的日志输出格式: logrus 内置了两种日志格式, JSONFormatter TextFormatter ,如果这两个格式不满足需求,可以自己动手实现接口 Formatter ,来定义自己的日志格式。
  • Field 机制: logrus 鼓励通过 Field 机制进行精细化的、结构化的日志记录,而不是通过冗长的消息来记录日志。
  • logrus 是一个可插拔的、结构化的日志框架。

# 1 安装

go get github.com/sirupsen/logrus
1

# 2 第一个示例

logrus与golang 标准库日志模块完全兼容,因此您可以使用 log "github.com/sirupsen/logrus" 替换所有日志导入。

package main
import (
	// 导入logrus日志包,别名为log
	log "github.com/sirupsen/logrus"
func main() {
	// 设置日志等级
	log.SetLevel(log.DebugLevel)
	// 设置日志输出到什么地方去
	// 将日志输出到标准输出,就是直接在控制台打印出来。
	log.SetOutput(os.Stdout)
	// 设置为true则显示日志在代码什么位置打印的
	//log.SetReportCaller(true)
	// 设置日志以json格式输出, 如果不设置默认以text格式输出
	log.SetFormatter(&log.JSONFormatter{})
	// 打印日志
	log.Debug("调试信息")
	log.Info("提示信息")
	log.Warn("警告信息")
	log.Error("错误信息")
	//log.Panic("致命错误")
	// 为日志加上字段信息,log.Fields其实就是map[string]interface{}类型的别名
	log.WithFields(log.Fields{
		"user_id":    1001,
		"ip":         "192.168.0。100",
		"request_id": "ec2bf8e55a11474392f8867e92624e04",
	}).Info("用户登陆失败.")
-----------------------------------输出结果如下------------------------------------
{"level":"debug","msg":"调试信息","time":"2020-03-06T23:52:41+08:00"}
{"level":"info","msg":"提示信息","time":"2020-03-06T23:52:41+08:00"}
{"level":"warning","msg":"警告信息","time":"2020-03-06T23:52:41+08:00"}
{"level":"error","msg":"错误信息","time":"2020-03-06T23:52:41+08:00"}
{"ip":"192.168.0。100","level":"info","msg":"用户登陆失败.","request_id":"ec2bf8e55a11474392f8867e92624e04","time":"2020-03-06T23:52:41+08:00","user_id":1001}
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

# 3 Logger

logger 是一种相对高级的用法,对于一个大型项目,往往需要一个全局的 logrus 实例,即 logger 对象来记录项目所有的日志。如

package main
import (
	"github.com/sirupsen/logrus"
// 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.Fields其实就是map[string]interface{}类型的别名
	log.WithFields(logrus.Fields{
		"name":    "tom",
		"address": "chengdu",
	}).Info("Make a little progress every day")
-----------------------------输出结果如下-------------------------------
{"address":"chengdu","level":"info","msg":"Make a little progress every day","name":"tom","time":"2020-03-07T00:01:31+08:00"}
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

# 4 Fields

Logrus 鼓励通过日志字段进行谨慎的结构化日志记录,而不是冗长的、不可解析的错误消息。例如下面的记录日志的方式:

log.Fatalf("Failed to send event %s to topic %s with key %d", event, topic, key)
1

# logrus 中不太提倡, logrus 鼓励使用以下方式替代之:

log.WithFields(log.Fields{
  "event": event,
  "topic": topic,
  "key": key,
}).Fatal("Failed to send event")
1
2
3
4
5

上面的 WithFields API 可以规范使用者按照其提倡的方式记录日志。但是 WithFields 依然是可选的,因为某些场景下,使用者确实只需要记录仪一条简单的消息。

# 5 Hooks

logrus 最令人心动的功能就是其可扩展的 HOOK 机制了,通过在初始化时为 logrus 添加 hook , logrus 可以实现各种扩展功能。

# 5.1 Hook接口定义

logrus hook 接口定义如下,其原理是每此写入日志时拦截,修改 logrus.Entry

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

# 5.2 简单Hook定义示例

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

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
1
2
3
4
5
6
7
8
9
10
11

# 5.3 Hook简单使用

  • hook 的使用也很简单,在初始化前调用 log.AddHook(hook) 添加相应的 hook 即可。
  • logrus 官方仅仅内置了 syslog hook
package main
import (
	log "github.com/sirupsen/logrus"
	"gopkg.in/gemnasium/logrus-airbrake-hook.v2" // the package is named "airbrake"
	logrus_syslog "github.com/sirupsen/logrus/hooks/syslog"
	"log/syslog"
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)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

# 6 将日志保存到文件

我们如何将日志保存到本地文件。前面的例子我们知道,可以通过 SetOutput 函数设置将日志保存到什么地方,下面演示如何将日志保存到文件中。

package main
import (
	"github.com/sirupsen/logrus"
// logrus提供了New()函数来创建一个logrus的实例。
// 项目中,可以创建任意数量的logrus实例。
var log = logrus.New()
func main() {
	// 先打开一个日志文件
	file, err := os.OpenFile("logrus.log", os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666)
	if err == nil {
		// 设置将日志输出到文件
		log.SetOutput(file)
	} else {
		log.Info("打开日志文件失败,默认输出到stderr")
	// 打印日志
	log.Debug("调试信息")
	log.Info("提示信息")
	log.Warn("警告信息")
	log.Error("错误信息")
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

logrus1

# 7 记录函数名

如果你希望将调用的函数名添加为字段,请通过以下方式设置:

log.SetReportCaller(true)
1

这会将调用者添加为 method ,如下所示:

{"name":"Tom","level":"fatal","method":"github.com/sirupsen/arcticcreatures.migrate","msg":"a penguin swims by",
"time":"2020-03-07 23:57:38.562543129 -0400 EDT"}
1
2

# 8 设置日志级别

你可以在 Logger 上设置日志记录级别,然后它只会记录具有该级别或以上级别任何内容的条目 日志级别大小说明:Panic>Fatal>Error>Warn>Info>Debug>Trace,举例如下:

// 会记录info及以上级别 (warn, error, fatal, panic)
log.SetLevel(log.InfoLevel)
1
2

如果你的程序支持 debug 或环境变量模式,设置 log.Level = logrus.DebugLevel 会很有帮助。

# 9 日志本地文件分割

logrus 本身不带日志本地文件分割功能,但是我们可以通过 file-rotatelogs 进行日志本地文件分割.每次当我们写入日志的时候, logrus 都会调用 file-rotatelogs 来判断日志是否要进行切分。关于本地日志文件分割的例子网上很多,这里不再详细介绍,奉上代码

package main
import (
	"github.com/lestrrat-go/file-rotatelogs"
	"github.com/pkg/errors"
	"github.com/rifflock/lfshook"
	log "github.com/sirupsen/logrus"
	"path"
	"time"
//建议使用这一种
func ConfigLocalFilesystemLogger(logPath string, logFileName string, maxAge time.Duration, rotationTime time.Duration) {
	baseLogPath := path.Join(logPath, logFileName)
	writer, err := rotatelogs.New(
		baseLogPath+"-%Y%m%d%H%M.log",
		//rotatelogs.WithLinkName(baseLogPath), // 生成软链,指向最新日志文件
		rotatelogs.WithMaxAge(maxAge),             // 文件最大保存时间
		rotatelogs.WithRotationTime(rotationTime), // 日志切割时间间隔
	if err != nil {
		log.Errorf("config local file system logger error. %+v", errors.WithStack(err))
	lfHook := 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})
	log.SetReportCaller(true) //将函数名和行数放在日志里面
	log.AddHook(lfHook)
//切割日志和清理过期日志
func ConfigLocalFilesystemLogger1(filePath string) {
	writer, err := rotatelogs.New(
		filePath+"-%Y%m%d%H%M.log",
		rotatelogs.WithLinkName(filePath),           // 生成软链,指向最新日志文件
		rotatelogs.WithMaxAge(time.Second*60*3),     // 文件最大保存时间
		rotatelogs.WithRotationTime(time.Second*60), // 日志切割时间间隔
	if err != nil {
		log.Fatal("Init log failed, err:", err)
	log.SetReportCaller(true) //将函数名和行数放在日志里面
	log.SetOutput(writer)
	log.SetLevel(log.InfoLevel)
func main() {
	//ConfigLocalFilesystemLogger1("log")
	ConfigLocalFilesystemLogger("D:/benben", "sentalog", time.Second*60*3, time.Second*60)
	for {
        log.Debug("调试信息")
        log.Info("提示信息")
        log.Warn("警告信息")
        log.Error("错误信息")
		time.Sleep(500 * time.Millisecond)
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

# 10 其他注意事项

# 10.1 Fatal处理

和很多日志框架一样, logrus Fatal 系列函数会执行 os.Exit(1) 。但是 logrus 提供可以注册一个或多个 fatal handler 函数的接口 logrus.RegisterExitHandler(handler func() {} ) ,让 logrus 在执行 os.Exit(1) 之前进行相应的处理。 fatal handler 可以在系统异常时调用一些资源释放 api 等,让应用正确的关闭。

# 10.2 线程安全

默认情况下, logrus的api 都是线程安全的,其内部通过互斥锁来保护并发写。互斥锁工作于调用 hooks 或者写日志的时候,如果不需要锁,可以调用 logger.SetNoLock() 来关闭之。可以关闭 logrus 互斥锁的情形包括:

  • 没有设置 hook ,或者所有的 hook 都是线程安全的实现。
  • 写日志到 logger.Out 已经是线程安全的了,如 logger.Out 已经被锁保护,或者写文件时,文件是以 O_APPEND 方式打开的,并且每次写操作都小于4k。
上次更新: 2020-7-11 0:26:35
 
推荐文章