Go語言從入門到精通 -【web項目實戰篇】- Log日誌

本節核心內容

  • 介紹log包的核心數據結構
  • 介紹log包的核心方法
  • 演示了開發中的使用demo

本小節視頻教程和代碼:百度網盤

可先下載視頻和源碼到本地,邊看視頻邊結合源碼理解後續內容,邊學邊練。

Log包簡介

Golang的log包短小精悍,可以非常輕鬆的實現日誌打印轉存功能。並且log支持併發操作(即協程安全-相對於JAVA中的線程安全而言),本小節將對log包的log.go文件以剖析的方式領大家來深入瞭解一下log,最後再給出實際開發中的應用實例,在學習之前,我們先來看一下log.go文件有哪些內容:

image

是不是看起來很多?但是不要怕,其實這裏面有一般的方法和函數是重複的,只是log自帶了一個logger對象,它爲這個對象建立了一系列重複的方法,所以我們只需要學一半內容中的精華就可以了。

Logger結構體

其結構定義如下:

type Logger struct {
    mu     sync.Mutex       // 互斥鎖
    prefix string           // 日誌行前綴
    flag   int              // 日誌打印格式標誌,用於指定每行日誌的打印格式
    out    io.Writer        // 用於指定日誌輸出位置,理論上可以是任何地方,只要實現了io.Writer接口就行
    buf    []byte           // 日誌內容
}

主要方法

對於log.go文件,我們從創建日誌對象到輸出日誌的流程可以對它進行了下分類,可大體分爲下面這些方法

創建logger對象

var std = New(os.Stderr, "", LstdFlags)  // 日誌中只使用的flag爲LstdFlags,即只輸出日期

雖然log包已默認爲我們提供了一個日誌對象,並封裝了包級別的常用函數,並且該對象可以將日誌信息輸出到標準輸出設備中(開箱即用),但我們還是需要使用New()函數來創建日誌對象,這是因爲我們往往需要自己定義日誌對象的數據方式和屬性。

創建對象方法爲:

func New(out io.Writer, prefix string, flag int) *Logger {
    return &Logger{out: out, prefix: prefix, flag: flag}
}

該函數一共有三個參數:

  1. 輸出位置out,是一個io.Writer對象,該對象可以是一個文件也可以是實現了該接口的對象。通常我們可以用這個來指定日誌輸出到哪個文件。
  2. prefix 我們在前面已經看到,就是在日誌內容前面的東西。我們可以將其置爲 "[Info]" 、 "[Warning]"等來幫助區分日誌級別。
  3. flags 是一個選項,顯示日誌開頭的東西,可選的值有:
// 這些標誌定義了日誌記錄器生成的每個日誌條目的前綴文本。
const (
    
    Ldate         = 1 << iota       // 例如 日期:  2009/01/23
    Ltime                           // 例如 時間:  01:23:23
    Lmicroseconds                   // 例如 微秒:  01:23:23.123123.
    Llongfile                       // 全路徑文件名和行號: /a/b/c/d.go:23
    Lshortfile                      // 文件名和行號:   d.go:23
    LUTC                            // 使用標準的UTC時間格式 
    LstdFlags     = Ldate | Ltime   // 默認爲日期和時間
)

設置屬性

log包爲我們提供了可設置日誌記錄器屬性的方法,主要作用是可根據我們事先自定義添加的屬性,在日誌輸出時,按照我們想要的格式進行日誌輸出,源碼爲:

// SetFlags sets the output flags for the logger.
func (l *Logger) SetFlags(flag int) {
    l.mu.Lock()
    defer l.mu.Unlock()
    l.flag = flag
}

示例代碼:

package main

import (
    "log"
)

func main() {
    Ldate()
    Ltime()
    Lmicroseconds()
    Llongfile()
    Lshortfile()
    LUTC()
    Ldefault()
}


func Ldate() {
    log.SetFlags(log.Ldate)
    log.Println("這是輸出日期格式\n")
}

func Ltime() {
    log.SetFlags(log.Ltime)
    log.Println("這是輸出時間格式\n")
}

func Lmicroseconds() {
    log.SetFlags(log.Lmicroseconds)
    log.Println("這是輸出微秒格式\n")
}

func Llongfile() {
    log.SetFlags(log.Llongfile)
    log.Println("這是輸出路徑+文件名+行號格式\n")
}

func Lshortfile() {
    log.SetFlags(log.Lshortfile)
    log.Println("這是輸出文件名+行號格式\n")
}

func LUTC() {
    log.SetFlags(log.Ldate | log.Ltime | log.Lmicroseconds | log.LUTC)
    log.Println("這是輸出 使用標準的UTC時間格式 格式\n")
}

func Ldefault() {
    log.Println("這是默認的格式\n")
}

運行結果:

2019/01/22 這是輸出日期格式

15:29:14 這是輸出時間格式

15:29:14.088798 這是輸出微秒格式

D:/GoPath/src/mylog/main.go:35: 這是輸出路徑+文件名+行號格式

main.go:40: 這是輸出文件名+行號格式

2019/01/22 07:29:14.088798 這是輸出 使用標準的UTC時間格式 格式

2019/01/22 07:29:14.088798 這是默認的格式

日誌輸出

Output方法

func (l *Logger) Output(calldepth int, s string) error ; 

Output是真正負責日誌打印的方法,其它級別的打印方法都將會調用它,該函數主要有兩個參數:

  • s爲要打印的文本,會在Logger記錄器的指定前綴之後打印輸出。如果s的最後一個字符不是換行符,則追加換行符。
  • calldepth用於恢復PC,並提供了通用性,不過目前在所有預定義的路徑上它都是2。

Print接口

log模塊主要提供了3類接口。分別是 PrintPanicFatal ,對每一類接口其提供了3種調用方式,分別是 "Xxxx 、 Xxxxln 、Xxxxf",基本和fmt中的相關函數類似,Print的源碼如下:

func (l *Logger) Println(v ...interface{}) {  
        l.Output(2, fmt.Sprintln(v...)) 
}

使用場景:一般信息打印方法,相當於JAVA中log的info級別

下面是一個Print的示例:

package main

import (
    "log"
)

func main(){
    arr := []int {2,3}
    log.Print("Print array ",arr,"\n")
    log.Println("Println array",arr)
    log.Printf("Printf array with item [%d,%d]\n",arr[0],arr[1])
}

會得到如下結果:

2016/12/15 19:46:19 Print array [2 3]
2016/12/15 19:46:19 Println array [2 3]
2016/12/15 19:46:19 Printf array with item [2,3]

Panic接口

使用場景:業務異常時使用的方法,該方法會拋出異常,調用方可以用recover捕獲,相當於JAVA的ERROR級別(JAVA不會自動拋異常)

func (l *Logger) Panicln(v ...interface{}) {                    
    s := fmt.Sprintln(v...)
    l.Output(2, s)
    panic(s)                                                //通過panic拋出異常,只有上層業務沒捕獲異常時,程序纔會異常中止並退出,
}

對於log.Panic接口,該函數把日誌內容刷到標準錯誤後調用 panic 函數,下面是一個Panic的示例:

package main

import (
    "fmt"
    "log"
)

func test_deferpanic(){
    defer func() {
        fmt.Println("--first--")
        if err := recover(); err != nil {
            fmt.Println(err)
        }
    }()
    log.Panicln("test for defer Panic")
    defer func() {
        fmt.Println("--second--")
    }()
}

func main() {
    test_deferpanic()
}

會得到如下結果:

2019/01/19 16:28:35 test for defer Panic
--first--
test for defer Panic

可以看到首先輸出了“test for defer Panic”,然後第一個defer函數被調用了並輸出了“--first--”,但是第二個defer 函數並沒有輸出,可見在Panic之後聲明的defer是不會執行的。

Fatal接口

// 調用該方法會中止應用程序並直接退出
func Fatalln(v ...interface{}) {
    std.Output(2, fmt.Sprintln(v...))
    os.Exit(1)
}

對於 log.Fatal 接口,會先將日誌內容打印到標準輸出,接着調用系統的 os.exit(1) 接口,退出程序並返回狀態 1 。但是有一點需要注意,由於是直接調用系統接口退出,defer函數不會被調用,下面是一個Fatal的示例:

package main

import (
    "fmt"
    "log"
)

func test_deferfatal(){
    defer func() {
        fmt.Println("--first--")
    }()
    log.Fatalln("test for defer Fatal")
}

func main() {
    test_deferfatal()
}

會得到如下結果:

2019/01/19 16:23:55 test for defer Fatal

可以看到並沒有調用defer 函數。

log實戰

下面將給出一段可用於實際開發中的示例代碼,將日誌輸出到日誌文件中

package main
import (
    "fmt"
    "log"
    "os"
    "time"
)


func logInfo() *log.Logger {
    //創建io對象,日誌的格式爲當前時間.log;2006-01-02 15:04:05據說是golang的誕生時間,固定寫法
    file := "./" + time.Now().Format("2006-01-02") + ".log"

    logFile, err := os.OpenFile(file, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0766)
    if nil != err {
        panic(err)
    }

    //創建一個Logger:參數1:日誌寫入目的地 ; 參數2:每條日誌的前綴 ;參數3:日誌屬性
    return  log.New(logFile, "自定義的前綴",log.Lshortfile)
}

var mylooger *log.Logger

func main() {

    //調用日誌初始化方法
    mylooger = logInfo()

    //Prefix返回前綴,Flags返回Logger的輸出選項屬性值
    fmt.Printf("創建時前綴爲:%s\n創建時輸出項屬性值爲:%d\n",mylooger.Prefix(),mylooger.Flags())

    //SetFlags 重新設置輸出選項
    mylooger.SetFlags(log.Ldate | log.Ltime | log.Lshortfile)

    //重新設置輸出前綴
    mylooger.SetPrefix("test_")

    //獲取修改後的前綴和輸出項屬性值
    fmt.Printf("修改後前綴爲:%s\n修改後輸出項屬性值爲:%d\n",mylooger.Prefix(),mylooger.Flags())

    //輸出一條日誌
    mylooger.Output(2, "使用Output進行日誌輸出")

    //格式化輸出日誌
    mylooger.Printf("我是%v方法在%d行內容爲:%s","Printf",40,"其實我底層是以fmt.Printf的方式處理的,相當於Java裏的Info級別")

    //開啓這個註釋,下面代碼就不會繼續走,並且程序停止
    //mylooger.Fatal("我是Fatal方法,我會停止程序,但不會拋出異常")

    //調用業務層代碼
    serviceCode()

    mylooger.Printf("業務代碼裏的Panicln不會影響到我,因爲他已經被處理幹掉了,程序目前正常")
}

func serviceCode()  {
    defer func() {
        if r := recover(); r != nil {
            //用以捕捉Panicln拋出的異常
            fmt.Printf("使用recover()捕獲到的錯誤:%s\n", r)
        }
    }()
    // 模擬錯誤業務邏輯,使用拋出異常和捕捉的方式記錄日誌
    if 1 == 1 {
        mylooger.Panicln("我是Panicln方法,我會拋異常信息的,相當於Error級別")
    }
}

小結

本小節主要介紹了GoLang自帶的log包,通過學習log的包log.go文件的結構體,主要函數來了解它的實現原理以及如何創建一個logger對象來進行日誌輸出。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章