1. 程式人生 > 其它 >【Golang】關於Go中log的用法

【Golang】關於Go中log的用法

一、標準日誌庫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.Panicfpanic,所以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:如果設定了LdateLtime,將輸出 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數量最多的日誌庫,目前(2018.08,下同)star數量為8119,fork數為1031。logrus功能強大,效能高效,而且具有高度靈活性,提供了自定義外掛的功能。很多開源專案,如docker,prometheus等,都是用了logrus來記錄其日誌。

zap是Uber推出的一個快速、結構化的分級日誌庫。具有強大的ad-hoc分析功能,並且具有靈活的儀表盤。zap目前在GitHub上的star數量約為4.3k。

seelog提供了靈活的非同步排程、格式化和過濾功能。

2、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 (
  "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!")
}

5、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")
}

6、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")

7、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) {

    }
}

8、簡單的日誌切割

需要引入外部元件

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)
	}
}

  

  

如果您覺得本文對您的學習有所幫助,可通過支付寶(左) 或者 微信(右) 來打賞博主,增加博主的寫作動力