本節核心內容
- 介紹log包的核心數據結構
- 介紹log包的核心方法
- 演示了開發中的使用demo
本小節視頻教程和代碼:百度網盤
可先下載視頻和源碼到本地,邊看視頻邊結合源碼理解後續內容,邊學邊練。
Log包簡介
Golang的log包短小精悍,可以非常輕鬆的實現日誌打印轉存功能。並且log支持併發操作(即協程安全-相對於JAVA中的線程安全而言),本小節將對log包的log.go文件以剖析的方式領大家來深入瞭解一下log,最後再給出實際開發中的應用實例,在學習之前,我們先來看一下log.go文件有哪些內容:
是不是看起來很多?但是不要怕,其實這裏面有一般的方法和函數是重複的,只是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}
}
該函數一共有三個參數:
- 輸出位置out,是一個io.Writer對象,該對象可以是一個文件也可以是實現了該接口的對象。通常我們可以用這個來指定日誌輸出到哪個文件。
- prefix 我們在前面已經看到,就是在日誌內容前面的東西。我們可以將其置爲 "[Info]" 、 "[Warning]"等來幫助區分日誌級別。
- 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類接口。分別是 Print
、Panic
、Fatal
,對每一類接口其提供了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對象來進行日誌輸出。