Golang 中 log 日誌包的使用

  1. 前言

作爲後端開發人員,日誌文件記錄了發生在操作系統或其他軟件運行時的事件或狀態。技術人員可以通過日誌記錄進而判斷系統的運行狀態,尋找導致系統出錯、崩潰的成因等。這是我們分析程序問題常用的手段。

2.log 包介紹

在 Golang 中記錄日誌非常方便,Golang 提供了一個簡單的日誌記錄包log,包中定義了一個結構體類型 Logger,是整個包的基礎部分,包中的其他方法都是圍繞這整個結構體創建的。

Logger 結構體

// A Logger represents an active logging object that generates lines of
// output to an io.Writer. Each logging operation makes a single call to
// the Writer's Write method. A Logger can be used simultaneously from
// multiple goroutines; it guarantees to serialize access to the Writer.
type Logger struct {
 mu     sync.Mutex // ensures atomic writes; protects the following fields
 prefix string     // prefix on each line to identify the logger (but see Lmsgprefix)
 flag   int        // properties
 out    io.Writer  // destination for output
 buf    []byte     // for accumulating text to write
}

該結構體表示一個日誌對象,通過io.Writer進行日誌輸出,每次記錄都簡單地調用io.Writerwrite方法。一個Logger可以被多個goroutines同步執行。對各個成員含義解析:

mu :是 sync.Mutex,它是一個同步互斥鎖,用於保證日誌記錄的原子性.
prefix :是輸入的日誌每一行的前綴
flag :是一個標誌,用於設置日誌的打印格式
out :日誌的輸出目標,需要是一個實現了 io.Writer 接口的對象,如:os.Stdout, os.Stderr, os.File 等等
buf :用於緩存數據

flag 可選值

其中 flag 的值在 log 包中定義了一些常量,它的作用主要是用於標識日誌信息附加攜帶的信息:

// These flags define which text to prefix to each log entry generated by the Logger.
// Bits are or'ed together to control what's printed.
// With the exception of the Lmsgprefix flag, there is no
// control over the order they appear (the order listed here)
// or the format they present (as described in the comments).
// The prefix is followed by a colon only when Llongfile or Lshortfile
// is specified.
// For example, flags Ldate | Ltime (or LstdFlags) produce,
// 2009/01/23 01:23:23 message
// while flags Ldate | Ltime | Lmicroseconds | Llongfile produce,
// 2009/01/23 01:23:23.123123 /a/b/c/d.go:23: message
const (
 Ldate         = 1 << iota     // the date in the local time zone: 2009/01/23
 Ltime                         // the time in the local time zone: 01:23:23
 Lmicroseconds                 // microsecond resolution: 01:23:23.123123.  assumes Ltime.
 Llongfile                     // full file name and line number: /a/b/c/d.go:23
 Lshortfile                    // final file name element and line number: d.go:23. overrides Llongfile
 LUTC                          // if Ldate or Ltime is set, use UTC rather than the local time zone
 Lmsgprefix                    // move the "prefix" from the beginning of the line to before the message
 LstdFlags     = Ldate | Ltime // initial values for the standard logger
)

如可以指定日期、時間、毫秒時間、絕對路徑和行號、文件名和行號等,LstdFlags爲默認的 flag,只同時攜帶了日期和時間兩個信息。

3.log 包的使用

3.1 日誌輸出方法

log 包中定義瞭如下的一套日誌信息輸出方法:

func (l *Logger) Print(v ...interface{}) //直接打印輸出
func (l *Logger) Fatal(v ...interface{}) //輸出日誌後立即結束程序
func (l *Logger) Panic(v ...interface{}) //輸出日誌後拋出異常

和這三個方法相似的另外的方法都很好理解,就是換行或者格式化輸出。

3.2 自定義創建日誌對象

log 包定義了一個New方法,並通過默認 flag 初始化了一個全局的私有 logger:

// New creates a new Logger. The out variable sets the
// destination to which log data will be written.
// The prefix appears at the beginning of each generated log line, or
// after the log header if the Lmsgprefix flag is provided.
// The flag argument defines the logging properties.
func New(out io.Writer, prefix string, flag int) *Logger {
 return &Logger{out: out, prefix: prefix, flag: flag}
}

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

如果你剛好只需要日期和時間這兩個額外的信息,就可以直接通過 log 包名調用方法,默認就是使用的這個初始化的 std 結構體的對應方法,如:

log.Println("hello,world~")

如果默認提供的日誌配置不能滿足您的需求,我們就可以自己去主動調用這個New方法,配置創建我們自己的 logger。該方法所需的三個參數,描述如下:

out io.Writer:表示輸出位置,可選值如 os.Stdout 爲系統控制檯,os.OpenFile 輸出到單獨的文件
prefix string: 表示統一前綴,會添加到生成的每一條日誌前面,如 debug 的場景我們可以單獨使用一個爲 [DEBUG] 的前綴
flag int:表示額外信息標識,上文介紹過

當然,我們也可以單獨調用如下相關的方法來單獨設置。

func (l *Logger) SetOutput(w io.Writer)
func (l *Logger) SetFlags(flag int)
func (l *Logger) SetPrefix(prefix string)

3.3 封裝自定義日誌包

到這裏,聰明的小夥伴一定有些思路根據自己的應用場景,基於官方的log包,封裝出自己的 log 日誌包了、~ 如下提供一個示例,具體還請自行優化代碼結構:

package logger

import (
 "io"
 "log"
 "os"
)

const (
 flag           = log.Ldate | log.Ltime | log.Lshortfile
 preDebug       = "[DEBUG]"
 preInfo        = "[INFO]"
 preWarning     = "[WARNING]"
 preError       = "[ERROR]"
)

var (
 logFile       io.Writer
 debugLogger   *log.Logger
 infoLogger    *log.Logger
 warningLogger *log.Logger
 errorLogger   *log.Logger
 defaultLogFile = "/var/log/web.log"
)

func init() {
 var err error
 logFile, err = os.OpenFile(defaultLogFile, os.O_CREATE|os.O_APPEND|os.O_RDWR, 0666)
 if err != nil {
  defaultLogFile = "./web.log"
  logFile, err = os.OpenFile(defaultLogFile, os.O_CREATE|os.O_APPEND|os.O_RDWR, 0666)
  if err != nil {
   log.Fatalf("create log file err %+v", err)
  }
 }
 debugLogger = log.New(logFile, preDebug, flag)
 infoLogger = log.New(logFile, preInfo, flag)
 warningLogger = log.New(logFile, preWarning, flag)
 errorLogger = log.New(logFile, preError, flag)
}

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

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

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

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

func SetOutputPath(path string) {
 var err error
 logFile, err = os.OpenFile(path, os.O_CREATE|os.O_APPEND|os.O_RDWR, 0666)
 if err != nil {
  log.Fatalf("create log file err %+v", err)
 }
 debugLogger.SetOutput(logFile)
 infoLogger.SetOutput(logFile)
 warningLogger.SetOutput(logFile)
 errorLogger.SetOutput(logFile)
}
package main

import "yourPath/logger"

func main() {
 author := "korbin"
 logger.Debugf("hello,%s",author)
 logger.Infof("hello,%s",author)
 logger.Warningf("hello,%s",author)
 logger.Errorf("hello,%s",author)
}
[DEBUG]2020/12/01 11:33:07 logger.go:43: hello,korbin
[INFO]2020/12/01 11:33:07 logger.go:47: hello,korbin
[WARNING]2020/12/01 11:33:07 logger.go:51: hello,korbin
[ERROR]2020/12/01 11:33:07 logger.go:55: hello,korbin

如該示例中的輸出文件路徑可以改爲通過從配置文件讀取或者通過命令行參數等,或者輸出方式改爲os.Stderr控制檯等都行,根據自行情況而定。

3.4 log 包進一步解析

另外其實我們也很容易發現,Logger結構體的日誌輸出方法,都是通過調用func (l *Logger) Output(calldepth int, s string) error方法實現的,在 Output 方法中,做了如下這些事情:

  1. 獲取當前事件

  2. 對 Logger 實例進行加鎖操作

  3. 判斷 Logger 的標誌位是否包含 Lshortfile 或 Llongfile, 如果包含進入步驟 4, 如果不包含進入步驟 5

  4. 獲取當前函數調用所在的文件和行號信息

  5. 格式化數據,並將數據寫入到 l.out 中,完成輸出

  6. 解鎖操作

log 包整體結構還是很簡單的,有興趣的小夥伴可以再自己多看一下源碼。

轉自:hezebin

juejin.cn/post/6987204299533058078

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/pgxsC5G3_tqaerakkHK9gg