摘要:源碼剖析標準庫原文地址源碼剖析標準庫日志輸出構成日期空格時分秒空格內容源碼剖析互斥鎖,用于確保原子的寫入每行需寫入的日志前綴內容設置日志輔助信息時間文件名行號的寫入。
Golang 源碼剖析:log 標準庫
原文地址:Golang 源碼剖析:log 標準庫
日志 輸出2018/09/28 20:03:08 EDDYCJY Blog...構成
[日期]<空格>[時分秒]<空格>[內容]
type Logger struct { mu sync.Mutex prefix string flag int out io.Writer buf []byte }
(1) mu:互斥鎖,用于確保原子的寫入
(2) prefix:每行需寫入的日志前綴內容
(3) flag:設置日志輔助信息(時間、文件名、行號)的寫入。可選如下標識位:
const ( Ldate = 1 << iota // value: 1 Ltime // value: 2 Lmicroseconds // value: 4 Llongfile // value: 8 Lshortfile // value: 16 LUTC // value: 32 LstdFlags = Ldate | Ltime // value: 3 )
Ldate:當地時區的格式化日期:2009/01/23
Ltime:當地時區的格式化時間:01:23:23
Lmicroseconds:在 Ltime 的基礎上,增加微秒的時間數值顯示
Llongfile:完整的文件名和行號:/a/b/c/d.go:23
Lshortfile:當前文件名和行號:d.go:23,會覆蓋 Llongfile 標識
LUTC:如果設置 Ldate 或 Ltime,且設置 LUTC,則優先使用 UTC 時區而不是本地時區
LstdFlags:Logger 的默認初始值(Ldate 和 Ltime)
(4) out:io.Writer
(5) buf:用于存儲將要寫入的日志內容
func New(out io.Writer, prefix string, flag int) *Logger { return &Logger{out: out, prefix: prefix, flag: flag} } var std = New(os.Stderr, "", LstdFlags)
New 方法用于初始化 Logger,接受三個初始參數,可以定制化而在 log 包內默認會初始一個 std,它指向標準輸入流。而默認的標準輸出、標準錯誤就是顯示器(輸出到屏幕上),標準輸入就是鍵盤。輔助的時間信息默認為 Ldate | Ltime,也就是 2009/01/23 01:23:23
// os var ( Stdin = NewFile(uintptr(syscall.Stdin), "/dev/stdin") Stdout = NewFile(uintptr(syscall.Stdout), "/dev/stdout") Stderr = NewFile(uintptr(syscall.Stderr), "/dev/stderr") )
Stdin:標準輸入
Stdout:標準輸出
Stderr:標準錯誤
GetterFlags
Prefix
SetterSetFlags
SetPrefix
SetOutput
Print.., Fatal.., Panic..func Print(v ...interface{}) { std.Output(2, fmt.Sprint(v...)) } func Printf(format string, v ...interface{}) { std.Output(2, fmt.Sprintf(format, v...)) } func Println(v ...interface{}) { std.Output(2, fmt.Sprintln(v...)) } func Fatal(v ...interface{}) { std.Output(2, fmt.Sprint(v...)) os.Exit(1) } func Panic(v ...interface{}) { s := fmt.Sprint(v...) std.Output(2, s) panic(s) } ...
這一部分介紹最常用的日志寫入方法,從源碼可得知 Xrintln、Xrintf 函數 換行、可變參數都是通過 fmt 標準庫的方法去實現的
Fatal 和 Panic 是通過 os.Exit(1)、panic(s) 集成實現的。而具體的組裝邏輯是通過 Output 方法實現的
Logger.Outputfunc (l *Logger) Output(calldepth int, s string) error { now := time.Now() // get this early. var file string var line int l.mu.Lock() defer l.mu.Unlock() if l.flag&(Lshortfile|Llongfile) != 0 { // Release lock while getting caller info - it"s expensive. l.mu.Unlock() var ok bool _, file, line, ok = runtime.Caller(calldepth) if !ok { file = "???" line = 0 } l.mu.Lock() } l.buf = l.buf[:0] l.formatHeader(&l.buf, now, file, line) l.buf = append(l.buf, s...) if len(s) == 0 || s[len(s)-1] != " " { l.buf = append(l.buf, " ") } _, err := l.out.Write(l.buf) return err }
Output 方法,簡單來講就是將寫入的日志事件信息組裝并輸出,它會根據 flag 標識位的不同來使用 runtime.Caller 去獲取當前 goroutine 所執行的函數文件、行號等調用信息(log 標準庫中默認深度為 2)。另外如果結尾不是換行符 ,將自動補全一個換行
Logger.formatHeaderfunc (l *Logger) formatHeader(buf *[]byte, t time.Time, file string, line int) { *buf = append(*buf, l.prefix...) if l.flag&(Ldate|Ltime|Lmicroseconds) != 0 { if l.flag&LUTC != 0 { t = t.UTC() } 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, " ") } 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, " ") } } 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, ": "...) } }
該方法主要是用于格式化日志頭(前綴),根據入參不同的標識位,添加分隔符和對應的值到日志信息中。執行流程如下:
(1)如果不是空值,則將 prefix 寫入 buf
(2)如果設置 Ldate、Ltime、Lmicroseconds,則對應將日期和時間寫入 buf
(3)如果設置 Lshortfile、Llongfile,則對應將文件和行號信息寫入 buf
Logger.itoafunc 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:]...) }
該方法主要用于將整數轉換為定長的十進制 ASCII,同時給出負數寬度避免左側補 0。另外會以相反的順序組合十進制
如何定制化 Logger在標準庫內,可通過其開放的 New 方法來實現各種各樣的自定義 Logger 組件,但是為什么也可以直接 log.Print* 等方法呢?
func New(out io.Writer, prefix string, flag int) *Logger
其實是在標準庫內,如果你剛剛細心的看了前面的小節,不難發現其默認實現了一個 Logger 組件
var std = New(os.Stderr, "", LstdFlags)
這也是一個小小的精妙之處 ??
總結通過查閱 log 標準庫的源碼,可得知最簡單的一個日志包應該如何編寫。另外 log 包是在所有涉及到 Logger 的地方都對 sync.Mutex 進行操作(以此解決原子問題),其余邏輯均為組裝日志信息和轉換數值格式,該包較為經典,可以多讀幾遍
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/29470.html
摘要:源碼剖析標準庫是怎么樣輸出的原文地址源碼剖析標準庫前言標準開場見多了,那內部標準庫又是怎么輸出這段英文的呢今天一起來圍觀下源碼吧 Golang 源碼剖析:fmt 標準庫 --- Print* 是怎么樣輸出的? 原文地址:Golang 源碼剖析:fmt 標準庫 前言 package main import ( fmt ) func main() { fmt.Print...
摘要:目錄結構說明集多編程范式之大成者,使開發者能夠快速的開發測試部署程序,支持全平臺靜態編譯。上目錄位置主要目錄包含如下圖,分別進行說明文件夾存放檢查器的輔助文件。工作區有個子目錄目錄目錄和目錄。目錄用于以代碼包的形式組織并保存源碼文件。 go 目錄結構說明 ??golang集多編程范式之大成者,使開發者能夠快速的開發、測試、部署程序,支持全平臺靜態編譯。go具有優秀的依賴管理,高效的運行...
摘要:首先讀取請求內容,解析請求,接著匹配相應的路由項,隨后調用路由項的回調函數來處理。每一個路由項由請求方法和回調函數組成將監聽地址作為參數,最終執行開始服務于外部請求創建對象首先,實例化對象。我們可以看到一條項由和對應的回調函數組成。 作者:Derek 簡介 Github地址:https://github.com/Bytom/bytom Gitee地址:https://gitee.com...
摘要:大殺器之性能剖析原文地址大殺器之性能剖析前言寫了幾噸代碼,實現了幾百個接口。功能測試也通過了,終于成功的部署上線了結果,性能不佳,什么鬼 Golang 大殺器之性能剖析 PProf 原文地址:Golang 大殺器之性能剖析 PProf 前言 寫了幾噸代碼,實現了幾百個接口。功能測試也通過了,終于成功的部署上線了 結果,性能不佳,什么鬼?
閱讀 844·2021-11-24 10:44
閱讀 2778·2021-11-11 16:54
閱讀 3160·2021-10-08 10:21
閱讀 2067·2021-08-25 09:39
閱讀 2899·2019-08-30 15:56
閱讀 3459·2019-08-30 13:46
閱讀 3493·2019-08-23 18:09
閱讀 2067·2019-08-23 17:05