一、標准日志庫log
在日常開發中,日志是必不可少的功能。雖然有時可以用fmt庫輸出一些信息,但是靈活性不夠。Go 標准庫提供了一個日志庫log。
1、快速使用
log是 Go 標准庫提供的,不需要另外安裝
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 時間,而非當地時區。
log.SetFlags(log.Lshortfile | log.Ldate | log.Lmicroseconds)
log.SetPrefix("Debug: ")
3、輸出到文件
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的對應方法:
// 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實現多目的地輸出
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、使用方法
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、簡單的示例
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對象來記錄項目所有的日志。如
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來進行精細化的、結構化的信息記錄。
例如下面的記錄日志的方式:
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框架日志中間件使用
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、簡單的日志切割
需要引入外部組件
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的使用方法(性能最高)
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),
)
}
