在日常开发中,日志是必不可少的功能。虽然有时可以用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) } |
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.WithFields(logrus.Fields{ "sb": "sbvalue", }).Info("A walrus appears") log1 := logrus.New() fmt.Printf("log1 level: %dn", 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 errorleveln") 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"
// 下面配置日志每隔 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("<<<<<<<< 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), ) } |
【Golang】关于Go中logrus的用法 - 踏雪无痕SS - 博客园一、标准日志库log 在日常开发中,日志是必不可少的功能。虽然有时可以用fmt库输出一些信息,但是灵活性不够。Go 标准库提供了一个日志库log。 1、快速使用 log是 Go 标准库提供的,不需要另https://www.cnblogs.com/chenpingzhao/p/15389882.html



