最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

深入探究Golang中l(wèi)og標(biāo)準(zhǔn)庫的使用

 更新時間:2023年05月07日 10:36:20   作者:江湖十年  
Go?語言標(biāo)準(zhǔn)庫中的?log?包設(shè)計(jì)簡潔明了,易于上手,可以輕松記錄程序運(yùn)行時的信息、調(diào)試錯誤以及跟蹤代碼執(zhí)行過程中的問題等。本文主要來深入探究?log?包的使用和原理,幫助讀者更好地了解和掌握它

Go 語言標(biāo)準(zhǔn)庫中的 log 包設(shè)計(jì)簡潔明了,易于上手,可以輕松記錄程序運(yùn)行時的信息、調(diào)試錯誤以及跟蹤代碼執(zhí)行過程中的問題等。使用 log 包無需繁瑣的配置即可直接使用。本文旨在深入探究 log 包的使用和原理,幫助讀者更好地了解和掌握它。

使用

先來看一個 log 包的使用示例:

package main
import "log"
func main() {
	log.Print("Print")
	log.Printf("Printf: %s", "print")
	log.Println("Println")
	log.Fatal("Fatal")
	log.Fatalf("Fatalf: %s", "fatal")
	log.Fatalln("Fatalln")
	log.Panic("Panic")
	log.Panicf("Panicf: %s", "panic")
	log.Panicln("Panicln")
}

假設(shè)以上代碼存放在 main.go 中,通過 go run main.go 執(zhí)行代碼將會得到如下輸出:

$ go run main.go
2023/03/08 22:33:22 Print
2023/03/08 22:33:22 Printf: print
2023/03/08 22:33:22 Println
2023/03/08 22:33:22 Fatal
exit status 1

以上示例代碼中使用 log 包提供的 9 個函數(shù)分別對日志進(jìn)行輸出,最終得到 4 條打印日志。我們來分析下每個日志函數(shù)的作用,來看看為什么出現(xiàn)這樣的結(jié)果。

log 包提供了 3 類共計(jì) 9 種方法來輸出日志內(nèi)容。

函數(shù)名作用使用示例
Print打印日志log.Print("Print")
Printf打印格式化日志log.Printf("Printf: %s", "print")
Println打印日志并換行log.Println("Println")
Panic打印日志后執(zhí)行 panic(s)(s 為日志內(nèi)容)log.Panic("Panic")
Panicf打印格式化日志后執(zhí)行 panic(s)log.Panicf("Panicf: %s", "panic")
Panicln打印日志并換行后執(zhí)行 panic(s)log.Panicln("Panicln")
Fatal打印日志后執(zhí)行 os.Exit(1)log.Fatal("Fatal")
Fatalf打印格式化日志后執(zhí)行 os.Exit(1)log.Fatalf("Fatalf: %s", "fatal")
Fatalln打印日志并換行后執(zhí)行 os.Exit(1)log.Panicln("Panicln")

根據(jù)以上表格說明,我們可以知道,log 包在執(zhí)行 log.Fatal("Fatal") 時,程序打印完日志就通過 os.Exit(1) 退出了。這也就可以解釋上面的示例程序,為什么打印了 9 次日志,卻只輸出了 4 條日志,并且最后程序退出碼為 1 了。

以上是 log 包最基本的使用方式,如果我們想對日志輸出做一些定制,可以使用 log.New 創(chuàng)建一個自定義 logger

logger := log.New(os.Stdout, "[Debug] - ", log.Lshortfile)

log.New 函數(shù)接收三個參數(shù),分別用來指定:日志輸出位置(一個 io.Writer 對象)、日志前綴(字符串,每次打印日志都會跟隨輸出)、日志屬性(定義好的常量,稍后會詳細(xì)講解)。

使用示例:

package main

import (
	"log"
	"os"
)

func main() {
	logger := log.New(os.Stdout, "[Debug] - ", log.Lshortfile)
	logger.Println("custom logger")
}

示例輸出:

[Debug] - main.go:10: custom logger

以上示例中,指定日志輸出到 os.Stdout,即標(biāo)準(zhǔn)輸出;日志前綴 [Debug] - 會自動被加入到每行日志的行首;這條日志沒有打印當(dāng)前時間,而是打印了文件名和行號,這是 log.Lshortfile 日志屬性的作用。

日志屬性可選項(xiàng)如下:

屬性說明
Ldate當(dāng)前時區(qū)的日期,格式:2009/01/23
Ltime當(dāng)前時區(qū)的時間,格式:01:23:23
Lmicroseconds當(dāng)前時區(qū)的時間,格式:01:23:23.123123,精確到微妙
Llongfile全文件名和行號,格式:/a/b/c/d.go:23
Lshortfile當(dāng)前文件名和行號,格式:d.go:23,會覆蓋 Llongfile
LUTC使用 UTC 而非本地時區(qū),推薦日志全部使用 UTC 時間
Lmsgprefixprefix 內(nèi)容從行首移動到日志內(nèi)容前面
LstdFlags標(biāo)準(zhǔn) logger 對象的初始值(等于:Ldate|Ltime

這些屬性都是預(yù)定義好的常量,不能修改,可以通過 | 運(yùn)算符組合使用(如:log.Ldate|log.Ltime|log.Lshortfile)。

使用 log.New 函數(shù)創(chuàng)建 logger 對象以后,依然可以通過 logger 對象的方法修改其屬性值:

方法作用
SetOutput設(shè)置日志輸出位置
SetPrefix設(shè)置日志輸出前綴
SetFlags設(shè)置日志屬性

現(xiàn)在我們來看一個更加完整的使用示例:

package main

import (
	"log"
	"os"
)

func main() {
	// 準(zhǔn)備日志文件
	logFile, _ := os.Create("demo.log")
	defer func() { _ = logFile.Close() }()

	// 初始化日志對象
	logger := log.New(logFile, "[Debug] - ", log.Lshortfile|log.Lmsgprefix)
	logger.Print("Print")
	logger.Println("Println")

	// 修改日志配置
	logger.SetOutput(os.Stdout)
	logger.SetPrefix("[Info] - ")
	logger.SetFlags(log.Ldate|log.Ltime|log.LUTC)
	logger.Print("Print")
	logger.Println("Println")
}

執(zhí)行以上代碼,得到 demo.log 日志內(nèi)容如下:

main.go:15: [Debug] - Print
main.go:16: [Debug] - Println

控制臺輸出內(nèi)容如下:

[Info] - 2023/03/11 01:24:56 Print
[Info] - 2023/03/11 01:24:56 Println

可以發(fā)現(xiàn),在 demo.log 日志內(nèi)容中,因?yàn)橹付?log.Lmsgprefix 屬性,所以日志前綴 [Debug] - 被移動到了日志內(nèi)容前面,而非行首。

因?yàn)楹罄m(xù)通過 logger.SetXXXlogger 對象的屬性進(jìn)行了動態(tài)修改,所以最后兩條日志輸出到系統(tǒng)的標(biāo)準(zhǔn)輸出。

以上,基本涵蓋了 log 包的所有常用功能。接下來我們就通過走讀源碼的方式來更深入的了解 log 包了。

源碼

注意:本文以 Go 1.19.4 源碼為例,其他版本可能存在差異。

Go 標(biāo)準(zhǔn)庫的 log 包代碼量非常少,算上注釋也才 400+ 行,非常適合初學(xué)者閱讀學(xué)習(xí)。

在上面介紹的第一個示例中,我們使用 log 包提供的 9 個公開函數(shù)對日志進(jìn)行輸出,并通過表格的形式分別介紹了函數(shù)的作用和使用示例,那么現(xiàn)在我們就來看看這幾個函數(shù)是如何定義的:

func Print(v ...any) {
	if atomic.LoadInt32(&std.isDiscard) != 0 {
		return
	}
	std.Output(2, fmt.Sprint(v...))
}
func Printf(format string, v ...any) {
	if atomic.LoadInt32(&std.isDiscard) != 0 {
		return
	}
	std.Output(2, fmt.Sprintf(format, v...))
}
func Println(v ...any) {
	if atomic.LoadInt32(&std.isDiscard) != 0 {
		return
	}
	std.Output(2, fmt.Sprintln(v...))
}
func Fatal(v ...any) {
	std.Output(2, fmt.Sprint(v...))
	os.Exit(1)
}
func Fatalf(format string, v ...any) {
	std.Output(2, fmt.Sprintf(format, v...))
	os.Exit(1)
}
func Fatalln(v ...any) {
	std.Output(2, fmt.Sprintln(v...))
	os.Exit(1)
}
func Panic(v ...any) {
	s := fmt.Sprint(v...)
	std.Output(2, s)
	panic(s)
}
func Panicf(format string, v ...any) {
	s := fmt.Sprintf(format, v...)
	std.Output(2, s)
	panic(s)
}
func Panicln(v ...any) {
	s := fmt.Sprintln(v...)
	std.Output(2, s)
	panic(s)
}

可以發(fā)現(xiàn),這些函數(shù)代碼主邏輯基本一致,都是通過 std.Output 輸出日志。不同的是,PrintX 輸出日志后程序就執(zhí)行結(jié)束了;Fatal 輸出日志后會執(zhí)行 os.Exit(1);而 Panic 輸出日志后會執(zhí)行 panic(s)

那么接下來就是要搞清楚這個 std 對象是什么,以及它的 Output 方法是如何定義的。

我們先來看下 std 是什么:

var std = New(os.Stderr, "", LstdFlags)
func New(out io.Writer, prefix string, flag int) *Logger {
	l := &Logger{out: out, prefix: prefix, flag: flag}
	if out == io.Discard {
		l.isDiscard = 1
	}
	return l
}

可以看到,std 其實(shí)就是使用 New 創(chuàng)建的一個 Logger 對象,日志輸出到標(biāo)準(zhǔn)錯誤輸出,日志前綴為空,日志屬性為 LstdFlags。

這跟我們上面講的自定義日志對象 logger := log.New(os.Stdout, "[Debug] - ", log.Lshortfile) 方式如出一轍。也就是說,當(dāng)我們通過 log.Print("Print") 打印日志時,其實(shí)使用的是 log 包內(nèi)部已經(jīng)定義好的 Logger 對象。

Logger 定義如下:

type Logger struct {
	mu        sync.Mutex // 鎖,保證并發(fā)情況下對其屬性操作是原子性的
	prefix    string     // 日志前綴,即 Lmsgprefix 參數(shù)值
	flag      int        // 日志屬性,用來控制日志輸出格式
	out       io.Writer  // 日志輸出位置,實(shí)現(xiàn)了 io.Writer 接口即可,如 文件、os.Stderr
	buf       []byte     // 存儲日志輸出內(nèi)容
	isDiscard int32      // 當(dāng) out = io.Discard 是,此值為 1
}

其中,flagisDiscard 這兩個屬性有必要進(jìn)一步解釋下。

首先是 flag 用來記錄日志屬性,其合法值如下:

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
)

具體含義我就不再一一解釋了,前文的表格已經(jīng)寫的很詳細(xì)了。

值得注意的是,這里在定義常量時,巧妙的使用了左移運(yùn)算符 1 << iota,使得常量的值呈現(xiàn) 1、2、4、8... 這樣的遞增效果。其實(shí)是為了位運(yùn)算方便,通過對屬性進(jìn)行位運(yùn)算,來決定輸出內(nèi)容,其本質(zhì)上跟基于位運(yùn)算的權(quán)限管理是一樣的。所以在使用 log.New 新建 Logger 對象時可以支持 log.Ldate|log.Ltime|log.Lshortfile 這種形式設(shè)置多個屬性。

std 對象的屬性初始值 LstdFlags 也是在這里定義的。

其次還有一個屬性 isDiscard,是用來丟棄日志的。在上面介紹 PrintX 函數(shù)定義時,在輸出日志前有一個 if atomic.LoadInt32(&std.isDiscard) != 0 的判斷,如果結(jié)果為真,則直接 return 不記錄日志。

在 Go 標(biāo)準(zhǔn)庫的 io 包里,有一個 io.Discard 對象,io.Discard 實(shí)現(xiàn)了 io.Writer,它執(zhí)行 Write 操作后不會產(chǎn)生任何實(shí)際的效果,是一個用于丟棄數(shù)據(jù)的對象。比如有時候我們不在意數(shù)據(jù)內(nèi)容,但可能存在數(shù)據(jù)不讀出來就無法關(guān)閉連接的情況,這時候就可以使用 io.Copy(io.Discard, io.Reader) 將數(shù)據(jù)寫入 io.Discard 實(shí)現(xiàn)丟棄數(shù)據(jù)的效果。

使用 New 創(chuàng)建 Logger 對象時,如果 out == io.Discardl.isDiscard 的值會被置為 1,所以使用 PrintX 函數(shù)記錄的日志將會被丟棄,而 isDiscard 屬性之所以是 int32 類型而不是 bool,是因?yàn)榉奖阍硬僮鳌?/p>

現(xiàn)在,我們終于可以來看 std.Output 的實(shí)現(xiàn)了:

func (l *Logger) Output(calldepth int, s string) error {
	now := time.Now() // 獲取當(dāng)前時間
	var file string
	var line int
	// 加鎖,保證并發(fā)安全
	l.mu.Lock()
	defer l.mu.Unlock()
	// 通過位運(yùn)算來判斷是否需要獲取文件名和行號
	if l.flag&(Lshortfile|Llongfile) != 0 {
		// 運(yùn)行 runtime.Caller 獲取文件名和行號比較耗時,所以先釋放鎖
		l.mu.Unlock()
		var ok bool
		_, file, line, ok = runtime.Caller(calldepth)
		if !ok {
			file = "???"
			line = 0
		}
		// 獲取到文件行號后再次加鎖,保證下面代碼并發(fā)安全
		l.mu.Lock()
	}
	// 清空上次緩存的內(nèi)容
	l.buf = l.buf[:0]
	// 格式化日志頭信息(如:日期時間、文件名和行號、前綴)并寫入 buf
	l.formatHeader(&l.buf, now, file, line)
	// 追加日志內(nèi)容到 buf
	l.buf = append(l.buf, s...)
	// 保證輸出日志以 \n 結(jié)尾
	if len(s) == 0 || s[len(s)-1] != '\n' {
		l.buf = append(l.buf, '\n')
	}
	// 調(diào)用 Logger 對象的 out 屬性的 Write 方法輸出日志
	_, err := l.out.Write(l.buf)
	return err
}

Output 方法代碼并不多,基本邏輯也比較清晰,首先根據(jù)日志屬性來決定是否需要獲取文件名和行號,因?yàn)檎{(diào)用 runtime.Caller 是一個耗時操作,開銷比較大,為了增加并發(fā)性,暫時將鎖釋放,獲取到文件名和行號后再重新加鎖。

接下來就是準(zhǔn)備日志輸出內(nèi)容了,首先清空 buf 中保留的上次日志信息,然后通過 formatHeader 方法格式化日志頭信息,接著把日志內(nèi)容也追加到 buf 中,在這之后有一個保證輸出日志以 \n 結(jié)尾的邏輯,來保證輸出的日志都是單獨(dú)一行的。不知道你有沒有注意到,在前文的 log 包使用示例中,我們使用 PrintPrintln 兩個方法時,最終日志輸出效果并無差別,使用 Print 打印日志也會換行,其實(shí)就是這里的邏輯決定的。

最后,通過調(diào)用 l.out.Write 方法,將 buf 內(nèi)容輸出。

我們來看下用來格式化日志頭信息的 formatHeader 方法是如何定義:

func (l *Logger) formatHeader(buf *[]byte, t time.Time, file string, line int) {
	// 如果沒有設(shè)置 Lmsgprefix 屬性,將日志前綴內(nèi)容設(shè)置到行首
	if l.flag&Lmsgprefix == 0 {
		*buf = append(*buf, l.prefix...)
	}
	// 判斷是否設(shè)置了日期時間相關(guān)的屬性
	if l.flag&(Ldate|Ltime|Lmicroseconds) != 0 {
		// 是否設(shè)置 UTC 時間
		if l.flag&LUTC != 0 {
			t = t.UTC()
		}
		// 是否設(shè)置日期
		if l.flag&Ldate != 0 {
			year, month, day := t.Date()
			itoa(buf, year, 4)
			*buf = append(*buf, '/')
			itoa(buf, int(month), 2)
			*buf = append(*buf, '/')
			itoa(buf, day, 2)
			*buf = append(*buf, ' ')
		}
		// 是否設(shè)置時間
		if l.flag&(Ltime|Lmicroseconds) != 0 {
			hour, min, sec := t.Clock()
			itoa(buf, hour, 2)
			*buf = append(*buf, ':')
			itoa(buf, min, 2)
			*buf = append(*buf, ':')
			itoa(buf, sec, 2)
			if l.flag&Lmicroseconds != 0 {
				*buf = append(*buf, '.')
				itoa(buf, t.Nanosecond()/1e3, 6)
			}
			*buf = append(*buf, ' ')
		}
	}
	// 是否設(shè)置文件名和行號
	if l.flag&(Lshortfile|Llongfile) != 0 {
		if l.flag&Lshortfile != 0 {
			short := file
			for i := len(file) - 1; i > 0; i-- {
				if file[i] == '/' {
					short = file[i+1:]
					break
				}
			}
			file = short
		}
		*buf = append(*buf, file...)
		*buf = append(*buf, ':')
		itoa(buf, line, -1)
		*buf = append(*buf, ": "...)
	}
	// 如果設(shè)置了 Lmsgprefix 屬性,將日志前綴內(nèi)容放到日志頭信息最后,也就是緊挨著日志內(nèi)容前面
	if l.flag&Lmsgprefix != 0 {
		*buf = append(*buf, l.prefix...)
	}
}

formatHeader 方法是 log 包里面代碼量最多的一個方法,主要通過按位與(&)來計(jì)算是否設(shè)置了某個日志屬性,然后根據(jù)日志屬性來格式化頭信息。

其中多次調(diào)用 itoa 函數(shù),itoa 顧名思義,將 int 轉(zhuǎn)換成 ASCII 碼,itoa 定義如下:

func itoa(buf *[]byte, i int, wid int) {
	// Assemble decimal in reverse order.
	var b [20]byte
	bp := len(b) - 1
	for i >= 10 || wid > 1 {
		wid--
		q := i / 10
		b[bp] = byte('0' + i - q*10)
		bp--
		i = q
	}
	// i < 10
	b[bp] = byte('0' + i)
	*buf = append(*buf, b[bp:]...)
}

這個函數(shù)短小精悍,它接收三個參數(shù),buf 用來保存轉(zhuǎn)換后的內(nèi)容,i 就是帶轉(zhuǎn)換的值,比如 year、month 等,wid 表示轉(zhuǎn)換后 ASCII 碼字符串寬度,如果傳進(jìn)來的 i 寬度不夠,則前面補(bǔ)零。比如傳入 itoa(&b, 12, 3),最終輸出字符串為 012。

至此,我們已經(jīng)理清了 log.Print("Print") 是如何打印一條日志的,其函數(shù)調(diào)用流程如下:

上面我們講解了使用 log 包中默認(rèn)的 std 這個 Logger 對象打印日志的調(diào)用流程。當(dāng)我們使用自定義的 Logger 對象(logger := log.New(os.Stdout, "[Debug] - ", log.Lshortfile))來打印日志時,調(diào)用的 loggger.Print 是一個方法,而不是 log.Print 這個包級別的函數(shù),所以其實(shí) Logger 結(jié)構(gòu)體也實(shí)現(xiàn)了 9 種輸出日志方法:

func (l *Logger) Print(v ...any) {
	if atomic.LoadInt32(&l.isDiscard) != 0 {
		return
	}
	l.Output(2, fmt.Sprint(v...))
}

func (l *Logger) Printf(format string, v ...any) {
	if atomic.LoadInt32(&l.isDiscard) != 0 {
		return
	}
	l.Output(2, fmt.Sprintf(format, v...))
}

func (l *Logger) Println(v ...any) {
	if atomic.LoadInt32(&l.isDiscard) != 0 {
		return
	}
	l.Output(2, fmt.Sprintln(v...))
}

func (l *Logger) Fatal(v ...any) {
	l.Output(2, fmt.Sprint(v...))
	os.Exit(1)
}

func (l *Logger) Fatalf(format string, v ...any) {
	l.Output(2, fmt.Sprintf(format, v...))
	os.Exit(1)
}

func (l *Logger) Fatalln(v ...any) {
	l.Output(2, fmt.Sprintln(v...))
	os.Exit(1)
}

func (l *Logger) Panic(v ...any) {
	s := fmt.Sprint(v...)
	l.Output(2, s)
	panic(s)
}

func (l *Logger) Panicf(format string, v ...any) {
	s := fmt.Sprintf(format, v...)
	l.Output(2, s)
	panic(s)
}

func (l *Logger) Panicln(v ...any) {
	s := fmt.Sprintln(v...)
	l.Output(2, s)
	panic(s)
}

這 9 個方法跟 log 包級別的函數(shù)一一對應(yīng),用于自定義 Logger 對象。

有一個值得注意的點(diǎn),在這些方法內(nèi)部,調(diào)用 l.Output(2, s) 時,第一個參數(shù) calldepth 傳遞的是 2,這跟 runtime.Caller(calldepth) 函數(shù)內(nèi)部實(shí)現(xiàn)有關(guān),runtime.Caller 函數(shù)簽名如下:

func Caller(skip int) (pc uintptr, file string, line int, ok bool)

runtime.Caller 返回當(dāng)前 Goroutine 的棧上的函數(shù)調(diào)用信息(程序計(jì)數(shù)器、文件信息、行號),其參數(shù) skip 表示當(dāng)前向上層的棧幀數(shù),0 代表當(dāng)前函數(shù),也就是調(diào)用 runtime.Caller 的函數(shù),1 代表上一層調(diào)用者,以此類推。

因?yàn)楹瘮?shù)調(diào)用鏈為 main.go -> log.Print -> std.Output -> runtime.Caller,所以 skip 值即為 2:

  • 0: 表示 std.Output 中調(diào)用 runtime.Caller 所在的源碼文件和行號。
  • 1: 表示 log.Print 中調(diào)用 std.Output 所在的源碼文件和行號。
  • 2: 表示 main.go 中調(diào)用 log.Print 所在的源碼文件和行號。

這樣當(dāng)代碼出現(xiàn)問題時,就能根據(jù)日志中記錄的函數(shù)調(diào)用棧來找到報(bào)錯的源碼位置了。

另外,前文介紹過三個設(shè)置 Logger 對象屬性的方法,分別是 SetOutputSetPrefix、SetFlags,其實(shí)這三個方法各自還有與之對應(yīng)的獲取相應(yīng)屬性的方法,定義如下:

func (l *Logger) Flags() int {
	l.mu.Lock()
	defer l.mu.Unlock()
	return l.flag
}

func (l *Logger) SetFlags(flag int) {
	l.mu.Lock()
	defer l.mu.Unlock()
	l.flag = flag
}

func (l *Logger) Prefix() string {
	l.mu.Lock()
	defer l.mu.Unlock()
	return l.prefix
}

func (l *Logger) SetPrefix(prefix string) {
	l.mu.Lock()
	defer l.mu.Unlock()
	l.prefix = prefix
}

func (l *Logger) Writer() io.Writer {
	l.mu.Lock()
	defer l.mu.Unlock()
	return l.out
}

func (l *Logger) SetOutput(w io.Writer) {
	l.mu.Lock()
	defer l.mu.Unlock()
	l.out = w
	isDiscard := int32(0)
	if w == io.Discard {
		isDiscard = 1
	}
	atomic.StoreInt32(&l.isDiscard, isDiscard)
}

其實(shí)就是針對每個私有屬性,定義了 getter、setter 方法,并且每個方法內(nèi)部為了保證并發(fā)安全,都進(jìn)行了加鎖操作。

當(dāng)然,log 包級別的函數(shù),也少不了這幾個功能:

func Default() *Logger { return std }

func SetOutput(w io.Writer) {
	std.SetOutput(w)
}

func Flags() int {
	return std.Flags()
}

func SetFlags(flag int) {
	std.SetFlags(flag)
}

func Prefix() string {
	return std.Prefix()
}

func SetPrefix(prefix string) {
	std.SetPrefix(prefix)
}

func Writer() io.Writer {
	return std.Writer()
}

func Output(calldepth int, s string) error {
	return std.Output(calldepth+1, s) // +1 for this frame.
}

至此,log 包的全部代碼我們就一起走讀完成了。

總結(jié)一下:log 包主要設(shè)計(jì)了 Logger 對象和其方法,并且為了開箱即用,在包級別又對外提供了默認(rèn)的 Logger 對象 std 和一些包級別的對外函數(shù)。Logger 對象的方法,和包級別的函數(shù)基本上是一一對應(yīng)的,簽名一樣,這樣就大大降低了使用難度。

使用建議

關(guān)于 log 包的使用,我還有幾條建議分享給你:

log 默認(rèn)不支持 Debug、Info、Warn 等更細(xì)粒度級別的日志輸出方法,不過我們可以通過創(chuàng)建多個 Logger 對象的方式來實(shí)現(xiàn),只需要給每個 Logger 對象指定不同的日志前綴即可:

loggerDebug = log.New(os.Stdout, "[Debug]", log.LstdFlags)
loggerInfo = log.New(os.Stdout, "[Info]", log.LstdFlags)
loggerWarn = log.New(os.Stdout, "[Warn]", log.LstdFlags)
loggerError = log.New(os.Stdout, "[Error]", log.LstdFlags)
  • 僅在 main.go 文件中使用 log.Panic、log.Fatal,下層程序遇到錯誤時先記錄日志,然后將錯誤向上一層層拋出,直到調(diào)用棧頂層才決定要不要使用 log.Paniclog.Fatal。
  • log 包作為 Go 標(biāo)準(zhǔn)庫,僅支持日志的基本功能,不支持記錄結(jié)構(gòu)化日志、日志切割、Hook 等高級功能,所以更適合小型項(xiàng)目使用,比如一個單文件的腳本。對于中大型項(xiàng)目,則推薦使用一些主流的第三方日志庫,如 logrus、zapglog 等。
  • 另外,如果你對 Go 標(biāo)準(zhǔn)日志庫有所期待,Go 官方還打造了另一個日志庫 slog 現(xiàn)已進(jìn)入實(shí)驗(yàn)階段,如果項(xiàng)目發(fā)展順利,將可能成為 log 包的替代品。

以上就是深入探究Golang中l(wèi)og標(biāo)準(zhǔn)庫的使用的詳細(xì)內(nèi)容,更多關(guān)于Golang log標(biāo)準(zhǔn)庫的資料請關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • golang實(shí)現(xiàn)簡單工廠、方法工廠、抽象工廠三種設(shè)計(jì)模式

    golang實(shí)現(xiàn)簡單工廠、方法工廠、抽象工廠三種設(shè)計(jì)模式

    這篇文章介紹了golang實(shí)現(xiàn)簡單工廠、方法工廠、抽象工廠三種設(shè)計(jì)模式的方法,文中通過示例代碼介紹的非常詳細(xì)。對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下
    2022-04-04
  • Go語言判斷文件或文件夾是否存在的方法

    Go語言判斷文件或文件夾是否存在的方法

    這篇文章主要介紹了Go語言判斷文件或文件夾是否存在的方法,結(jié)合具體實(shí)例形式對比分析了Go語言針對文件與目錄判斷的操作技巧與相關(guān)注意事項(xiàng),需要的朋友可以參考下
    2017-05-05
  • go1.21中slog日志包用法入門

    go1.21中slog日志包用法入門

    go1.21中,slog這一被Go語言團(tuán)隊(duì)精心設(shè)計(jì)的結(jié)構(gòu)化日志包正式落地,本文將帶領(lǐng)讀者上手slog,體會其與傳統(tǒng)log的差異,感興趣的小伙伴快跟隨小編一起學(xué)習(xí)一下吧
    2023-09-09
  • 深入探討Go語言中的預(yù)防性接口為什么是不必要的

    深入探討Go語言中的預(yù)防性接口為什么是不必要的

    在Go語言中,有一種從其他語言帶來的常見模式:預(yù)防性接口,雖然這種模式在?Java?等語言中很有價值,但在Go中往往會成為反模式,本文我們就來深入探討一下原因
    2025-01-01
  • 一文理解Goland協(xié)程調(diào)度器scheduler的實(shí)現(xiàn)

    一文理解Goland協(xié)程調(diào)度器scheduler的實(shí)現(xiàn)

    本文主要介紹了Goland協(xié)程調(diào)度器scheduler的實(shí)現(xiàn),文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2022-06-06
  • 深入淺出Go:掌握基礎(chǔ)知識的關(guān)鍵要點(diǎn)

    深入淺出Go:掌握基礎(chǔ)知識的關(guān)鍵要點(diǎn)

    Go是一種開源的編程語言,由Google開發(fā),它具有簡潔、高效、并發(fā)性強(qiáng)的特點(diǎn),適用于構(gòu)建可靠的、高性能的軟件系統(tǒng),本文將介紹Go的基礎(chǔ)知識,需要的朋友可以參考下
    2023-10-10
  • Go中的?=?和?:=?區(qū)別小結(jié)

    Go中的?=?和?:=?區(qū)別小結(jié)

    在Go語言編程中,"="用于給已聲明的變量賦值,而":="同時聲明并初始化變量,只能在函數(shù)內(nèi)使用,理解這兩者的不同,有助于編寫更清晰的代碼,下面就來介紹一下
    2024-10-10
  • golang新手們?nèi)菀追傅?個錯誤總結(jié)

    golang新手們?nèi)菀追傅?個錯誤總結(jié)

    這篇文章主要給大家介紹了關(guān)于golang新手們?nèi)菀追傅?個錯誤,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2018-08-08
  • Golang創(chuàng)建構(gòu)造函數(shù)的方法超詳細(xì)講解

    Golang創(chuàng)建構(gòu)造函數(shù)的方法超詳細(xì)講解

    構(gòu)造器一般面向?qū)ο笳Z言的典型特性,用于初始化變量。Go語言沒有任何具體構(gòu)造器,但我們能使用該特性去初始化變量。本文介紹不同類型構(gòu)造器的差異及其應(yīng)用場景
    2023-01-01
  • golang結(jié)構(gòu)化日志slog的用法簡介

    golang結(jié)構(gòu)化日志slog的用法簡介

    日志是任何軟件的重要組成部分,Go?提供了一個內(nèi)置日志包(slog),在本文中,小編將簡單介紹一下slog包的功能以及如何在?Go?應(yīng)用程序中使用它,感興趣的可以了解下
    2023-09-09

最新評論

凭祥市| 自贡市| 新闻| 潍坊市| 仪陇县| 陇西县| 双江| 筠连县| 页游| 秀山| 银川市| 文安县| 前郭尔| 长丰县| 思南县| 长泰县| 齐齐哈尔市| 潍坊市| 扶绥县| 东乡族自治县| 五指山市| 东乌珠穆沁旗| 丰县| 拜城县| 孝义市| 宜昌市| 永仁县| 宁城县| 和龙市| 当雄县| 华阴市| 紫金县| 庆安县| 双峰县| 房山区| 朝阳区| 钦州市| 桐乡市| 正镶白旗| 东海县| 巩义市|