0
点赞
收藏
分享

微信扫一扫

【Golang】关于Go中logrus的用法


一、标准日志库​​log​

在日常开发中,日志是必不可少的功能。虽然有时可以用​​fmt​​​库输出一些信息,但是灵活性不够。Go 标准库提供了一个日志库​​log​​。

1、快速使用

​log​​是 Go 标准库提供的,不需要另外安装

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

​package​​​ ​​main​


​import​​​ ​​(​

​"log"​

​)​


​type​​​ ​​User ​​​​struct​​​ ​​{​

​Name string​

​Age  int​

​}​


​func​​​ ​​main() {​

​u := User{​

​Name: ​​​​"test"​​​​,​

​Age:  18,​

​}​


​log.Printf(​​​​"%s login, age:%d"​​​​, u.Name, u.Age)​

​log.Panicf(​​​​"Oh, system error when %s login"​​​​, u.Name)​

​log.Fatalf(​​​​"Danger! hacker %s login"​​​​, u.Name)​

​}​

​log​​​默认输出到标准错误(​​stderr​​​),每条日志前会自动加上日期和时间。如果日志不是以换行符结尾的,那么​​log​​会自动加上换行符。即每条日志会在新行中输出。

​log​​提供了三组函数:

  • ​Print/Printf/Println​​:正常输出日志;
  • ​Panic/Panicf/Panicln​​​:输出日志后,以拼装好的字符串为参数调用​​panic​​;
  • ​Fatal/Fatalf/Fatalln​​​:输出日志后,调用​​os.Exit(1)​​退出程序。

命名比较容易辨别,带​​f​​​后缀的有格式化功能,带​​ln​​后缀的会在日志后增加一个换行符。

注意,上面的程序中由于调用​​log.Panicf​​​会​​panic​​​,所以​​log.Fatalf​​并不会调用

2、自定义选项

选项

  • ​Ldate​​​:输出当地时区的日期,如​​2020/02/07​​;
  • ​Ltime​​​:输出当地时区的时间,如​​11:45:45​​;
  • ​Lmicroseconds​​​:输出的时间精确到微秒,设置了该选项就不用设置​​Ltime​​​了。如​​11:45:45.123123​​;
  • ​Llongfile​​​:输出长文件名+行号,含包名,如​​github.com/darjun/go-daily-lib/log/flag/main.go:50​​;
  • ​Lshortfile​​​:输出短文件名+行号,不含包名,如​​main.go:50​​;
  • ​LUTC​​​:如果设置了​​Ldate​​​或​​Ltime​​,将输出 UTC 时间,而非当地时区。

1

2

​log.SetFlags(log.Lshortfile | log.Ldate | log.Lmicroseconds)​

​log.SetPrefix(​​​​"Debug: "​​​​)​

3、输出到文件

1

2

3

4

5

6

7

8

​file := ​​​​"./"​​​ ​​+ ​​​​"message"​​​ ​​+ ​​​​".txt"​

​logFile, err := os.OpenFile(file, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0766)​

​if​​​ ​​err != nil {​

​panic(err)​

​}​

​log.SetOutput(logFile) ​​​​// 将文件设置为log输出的文件​

​log.SetPrefix(​​​​"[qcpz]"​​​​)​

​log.SetFlags(log.LstdFlags | log.Lshortfile | log.Ldate | log.Ltime)​

4、自定义输出

实际上,​​log​​​库为我们定义了一个默认的​​Logger​​​,名为​​std​​​,意为标准日志。我们直接调用的​​log​​​库的方法,其内部是调用​​std​​的对应方法:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

​// src/log/log.go​

​var​​​ ​​std = New(os.Stderr, ​​​​""​​​​, LstdFlags)​


​func​​​ ​​Printf(format string, v ...​​​​interface​​​​{}) {​

​std.Output(2, fmt.Sprintf(format, v...))​

​}​


​func​​​ ​​Fatalf(format string, v ...​​​​interface​​​​{}) {​

​std.Output(2, fmt.Sprintf(format, v...))​

​os.Exit(1)​

​}​


​func​​​ ​​Panicf(format string, v ...​​​​interface​​​​{}) {​

​s := fmt.Sprintf(format, v...)​

​std.Output(2, s)​

​panic(s)​

​}​

​log.New​​接受三个参数:

  • ​io.Writer​​​:日志都会写到这个​​Writer​​中;
  • ​prefix​​​:前缀,也可以后面调用​​logger.SetPrefix​​设置;
  • ​flag​​​:选项,也可以后面调用​​logger.SetFlag​​设置。

可以使用​​io.MultiWriter​​实现多目的地输出

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

​package​​​ ​​main​


​import​​​ ​​(​

​"bytes"​

​"io"​

​"log"​

​"os"​

​)​


​type​​​ ​​User ​​​​struct​​​ ​​{​

​Name string​

​Age  int​

​}​


​func​​​ ​​main() {​

​u := User{​

​Name: ​​​​"test"​​​​,​

​Age:  18,​

​}​


​writer1 := &bytes.Buffer{}​

​writer2 := os.Stdout​

​writer3, err := os.OpenFile(​​​​"log.txt"​​​​, os.O_WRONLY|os.O_CREATE, 0755)​

​if​​​ ​​err != nil {​

​log.Fatalf(​​​​"create file log.txt failed: %v"​​​​, err)​

​}​


​logger := log.New(io.MultiWriter(writer1, writer2, writer3), ​​​​""​​​​, log.Lshortfile|log.LstdFlags)​

​logger.Printf(​​​​"%s login, age:%d"​​​​, u.Name, u.Age)​

​}​

二、logrus的使用

1、golang日志库

golang标准库的日志框架非常简单,仅仅提供了print,panic和fatal三个函数对于更精细的日志级别、日志文件分割以及日志分发等方面并没有提供支持。所以催生了很多第三方的日志库,但是在golang的世界里,没有一个日志库像slf4j那样在Java中具有绝对统治地位。golang中,流行的日志框架包括logrus、zap、zerolog、seelog等。

logrus是目前Github上star数量最多的日志库。logrus功能强大,性能高效,而且具有高度灵活性,提供了自定义插件的功能。很多开源项目,如docker,prometheus等,都是用了logrus来记录其日志。

zap是Uber推出的一个快速、结构化的分级日志库。具有强大的ad-hoc分析功能,并且具有灵活的仪表盘。

seelog提供了灵活的异步调度、格式化和过滤功能。

2、logrus特性

GitHub访问地址:https://github.com/sirupsen/logrus

logrus具有以下特性:

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

尽管 logrus有诸多优点,但是为了灵活性和可扩展性,官方也削减了很多实用的功能,例如:

  • 没有提供行号和文件名的支持
  • 输出到本地文件系统没有提供日志分割功能
  • 官方没有提供输出到ELK等日志处理中心的功能

但是这些功能都可以通过自定义hook来实现。

3、日志格式

比如,我们约定日志格式为 Text,包含字段如下:

​请求时间​​​、​​日志级别​​​、​​状态码​​​、​​执行时间​​​、​​请求IP​​​、​​请求方式​​​、​​请求路由​​。

4、使用方法

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

​package​​​ ​​main​


​import​​​ ​​(​

​"flag"​

​"fmt"​

​"os"​

​"path"​

​"runtime"​

​"strings"​

​"time"​


​"github.com/Sirupsen/logrus"​

​)​


​func​​​ ​​logrus_test() {​


​fmt.Printf(​​​​"<<<<<<<<<logrus test>>>>>>>>>>>>>>\n"​​​​)​


​logrus.WithFields(logrus.Fields{​

​"sb"​​​​: ​​​​"sbvalue"​​​​,​

​}).Info(​​​​"A walrus appears"​​​​)​


​log1 := logrus.New()​

​fmt.Printf(​​​​"log1 level: %d\n"​​​​, log1.Level)​

​log1.Debug(​​​​"log1 debug"​​​​)​

​log1.Debugf(​​​​"log1 debug f, %d"​​​​, 10)​

​log1.Info(​​​​"log1 info"​​​​)​

​log1.Warn(​​​​"log1 warn"​​​​)​

​log1.Error(​​​​"log1 error"​​​​)​

​// log1.Panic("log1 panic")​


​log1.SetLevel(logrus.ErrorLevel)​

​fmt.Printf(​​​​"after set log1 level to errorlevel\n"​​​​)​

​log1.Debug(​​​​"log1 debug"​​​​)​


​fmt.Printf(​​​​"-------------test formater-------------\n"​​​​)​

​log1.SetLevel(logrus.DebugLevel)​

​log1.Formatter = &logrus.TextFormatter{​

​DisableColors:  true,​

​FullTimestamp:  true,​

​DisableSorting: true,​

​}​


​log1.Debug(​​​​"log text formatter test"​​​​)​


​fmt.Printf(​​​​"-----------json formatter-------------\n"​​​​)​

​log1.Formatter = &logrus.JSONFormatter{}​

​log1.Debug(​​​​"log json formatter test"​​​​)​


​fmt.Printf(​​​​"-----------log to file test-----------\n"​​​​)​

​log2 := logrus.New()​

​log2.SetLevel(logrus.DebugLevel)​

​log2.Formatter = &logrus.TextFormatter{​

​DisableColors:  true,​

​FullTimestamp:  true,​

​DisableSorting: true,​

​}​


​logger_name := ​​​​"logrus"​

​cur_time := time.Now()​

​log_file_name := fmt.Sprintf(​​​​"%s_%04d-%02d-%02d-%02d-%02d.txt"​​​​,​

​logger_name, cur_time.Year(), cur_time.Month(), cur_time.Day(), cur_time.Hour(), cur_time.Minute())​

​log_file, err := os.OpenFile(log_file_name, os.O_CREATE|os.O_APPEND|os.O_WRONLY, os.ModeExclusive)​

​if​​​ ​​err != nil {​

​fmt.Printf(​​​​"try create logfile[%s] error[%s]\n"​​​​, log_file_name, err.Error())​

​return​

​}​


​defer​​​ ​​log_file.Close()​


​log2.SetOutput(log_file)​


​for​​​ ​​i := 0; i < 10; i++ {​

​log2.Debugf(​​​​"logrus to file test %d"​​​​, i)​

​}​


​}​

5、简单的示例

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

​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.WarnLevel)​

​}​

 

​func​​​ ​​main() {​

​log.WithFields(log.Fields{​

​"animal"​​​​: ​​​​"walrus"​​​​,​

​"size"​​​​:  10,​

​}).Info(​​​​"A group of walrus emerges from the ocean"​​​​)​

 

​log.WithFields(log.Fields{​

​"omg"​​​​:  true,​

​"number"​​​​: 122,​

​}).Warn(​​​​"The group's number increased tremendously!"​​​​)​

 

​log.WithFields(log.Fields{​

​"omg"​​​​:  true,​

​"number"​​​​: 100,​

​}).Fatal(​​​​"The ice breaks!"​​​​)​

​}​

6、Logger

logger是一种相对高级的用法, 对于一个大型项目, 往往需要一个全局的logrus实例,即​​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"​​​​)​

​}​

7、Fields

logrus不推荐使用冗长的消息来记录运行信息,它推荐使用​​Fields​​来进行精细化的、结构化的信息记录。

例如下面的记录日志的方式:

1

2

3

4

5

6

7

8

9

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


​//替代方案​


​log.WithFields(log.Fields{​

​"event"​​​​: event,​

​"topic"​​​​: topic,​

​"key"​​​​: key,​

​}).Fatal(​​​​"Failed to send event"​​​​)​

8、gin框架日志中间件使用

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

109

110

111

112

113

114

115

116

117

118

119

120

121

122

123

124

125

​package​​​ ​​middleware​


​import​​​ ​​(​

​"fmt"​

​"ginDemo/config"​

​"github.com/gin-gonic/gin"​

​rotatelogs ​​​​"github.com/lestrrat-go/file-rotatelogs"​

​"github.com/rifflock/lfshook"​

​"github.com/sirupsen/logrus"​

​"os"​

​"path"​

​"time"​

​)​


​// 日志记录到文件​

​func​​​ ​​LoggerToFile() gin.HandlerFunc {​


​logFilePath := config.Log_FILE_PATH​

​logFileName := config.LOG_FILE_NAME​


​// 日志文件​

​fileName := path.Join(logFilePath, logFileName)​


​// 写入文件​

​src, err := os.OpenFile(fileName, os.O_APPEND|os.O_WRONLY, os.ModeAppend)​

​if​​​ ​​err != nil {​

​fmt.Println(​​​​"err"​​​​, err)​

​}​


​// 实例化​

​logger := logrus.New()​


​// 设置输出​

​logger.Out = src​


​// 设置日志级别​

​logger.SetLevel(logrus.DebugLevel)​


​// 设置 rotatelogs​

​logWriter, err := rotatelogs.New(​

​// 分割后的文件名称​

​fileName + ​​​​".%Y%m%d.log"​​​​,​


​// 生成软链,指向最新日志文件​

​rotatelogs.WithLinkName(fileName),​


​// 设置最大保存时间(7天)​

​rotatelogs.WithMaxAge(7*24*time.Hour),​


​// 设置日志切割时间间隔(1天)​

​rotatelogs.WithRotationTime(24*time.Hour),​

​)​


​writeMap := lfshook.WriterMap{​

​logrus.InfoLevel:  logWriter,​

​logrus.FatalLevel: logWriter,​

​logrus.DebugLevel: logWriter,​

​logrus.WarnLevel:  logWriter,​

​logrus.ErrorLevel: logWriter,​

​logrus.PanicLevel: logWriter,​

​}​


​lfHook := lfshook.NewHook(writeMap, &logrus.JSONFormatter{​

​TimestampFormat:​​​​"2006-01-02 15:04:05"​​​​,​

​})​


​// 新增 Hook​

​logger.AddHook(lfHook)​


​return​​​ ​​func​​​​(c *gin.Context) {​

​// 开始时间​

​startTime := time.Now()​


​// 处理请求​

​c.Next()​


​// 结束时间​

​endTime := time.Now()​


​// 执行时间​

​latencyTime := endTime.Sub(startTime)​


​// 请求方式​

​reqMethod := c.Request.Method​


​// 请求路由​

​reqUri := c.Request.RequestURI​


​// 状态码​

​statusCode := c.Writer.Status()​


​// 请求IP​

​clientIP := c.ClientIP()​


​// 日志格式​

​logger.WithFields(logrus.Fields{​

​"status_code"​​​  ​​: statusCode,​

​"latency_time"​​​ ​​: latencyTime,​

​"client_ip"​​​    ​​: clientIP,​

​"req_method"​​​   ​​: reqMethod,​

​"req_uri"​​​      ​​: reqUri,​

​}).Info()​

​}​

​}​


​// 日志记录到 MongoDB​

​func​​​ ​​LoggerToMongo() gin.HandlerFunc {​

​return​​​ ​​func​​​​(c *gin.Context) {​


​}​

​}​


​// 日志记录到 ES​

​func​​​ ​​LoggerToES() gin.HandlerFunc {​

​return​​​ ​​func​​​​(c *gin.Context) {​


​}​

​}​


​// 日志记录到 MQ​

​func​​​ ​​LoggerToMQ() gin.HandlerFunc {​

​return​​​ ​​func​​​​(c *gin.Context) {​


​}​

​}​

9、简单的日志切割

需要引入外部组件

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

​package​​​ ​​main​


​import​​​ ​​(​

​"time"​


​rotatelogs ​​​​"github.com/lestrrat-go/file-rotatelogs"​

​log ​​​​"github.com/sirupsen/logrus"​

​)​


​func​​​ ​​init() {​

​path := ​​​​"message.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)​

​}​

​}​

 10、ZAP的使用方法(性能最高)

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

​package​​​ ​​main​


​import​​​ ​​(​

​"flag"​

​"fmt"​

​"os"​

​"path"​

​"runtime"​

​"strings"​

​"time"​


​"github.com/golang/glog"​

​)​


​func​​​ ​​zap_log_test() {​

​fmt.Printf(​​​​"<<<<<<<<<zap log test>>>>>>>>>>>\n"​​​​)​

​logger := zap.NewExample()​

​defer​​​ ​​logger.Sync()​


​const​​​ ​​url = ​​​​"http://example.com"​


​// In most circumstances, use the SugaredLogger. It's 4-10x faster than most​

​// other structured logging packages and has a familiar, loosely-typed API.​

​sugar := logger.Sugar()​

​sugar.Infow(​​​​"Failed to fetch URL."​​​​,​

​// Structured context as loosely typed key-value pairs.​

​"url"​​​​, url,​

​"attempt"​​​​, 3,​

​"backoff"​​​​, time.Second,​

​)​

​sugar.Infof(​​​​"Failed to fetch URL: %s"​​​​, url)​


​// In the unusual situations where every microsecond matters, use the​

​// Logger. It's even faster than the SugaredLogger, but only supports​

​// structured logging.​

​logger.Info(​​​​"Failed to fetch URL."​​​​,​

​// Structured context as strongly typed fields.​

​zap.String(​​​​"url"​​​​, url),​

​zap.Int(​​​​"attempt"​​​​, 3),​

​zap.Duration(​​​​"backoff"​​​​, time.Second),​

​)​

​}​


举报

相关推荐

0 条评论