成人国产在线小视频_日韩寡妇人妻调教在线播放_色成人www永久在线观看_2018国产精品久久_亚洲欧美高清在线30p_亚洲少妇综合一区_黄色在线播放国产_亚洲另类技巧小说校园_国产主播xx日韩_a级毛片在线免费

資訊專欄INFORMATION COLUMN

Golang 源碼剖析:log 標(biāo)準(zhǔn)庫

stackvoid / 2263人閱讀

摘要:源碼剖析標(biāo)準(zhǔn)庫原文地址源碼剖析標(biāo)準(zhǔn)庫日志輸出構(gòu)成日期空格時分秒空格內(nèi)容源碼剖析互斥鎖,用于確保原子的寫入每行需寫入的日志前綴內(nèi)容設(shè)置日志輔助信息時間文件名行號的寫入。

Golang 源碼剖析:log 標(biāo)準(zhǔn)庫

原文地址:Golang 源碼剖析:log 標(biāo)準(zhǔn)庫

日志 輸出
2018/09/28 20:03:08 EDDYCJY Blog...
構(gòu)成

[日期]<空格>[時分秒]<空格>[內(nèi)容]

源碼剖析 Logger
type Logger struct {
    mu     sync.Mutex 
    prefix string
    flag   int
    out    io.Writer
    buf    []byte
}

(1) mu:互斥鎖,用于確保原子的寫入
(2) prefix:每行需寫入的日志前綴內(nèi)容
(3) flag:設(shè)置日志輔助信息(時間、文件名、行號)的寫入??蛇x如下標(biāo)識位:

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:當(dāng)?shù)貢r區(qū)的格式化日期:2009/01/23

Ltime:當(dāng)?shù)貢r區(qū)的格式化時間:01:23:23

Lmicroseconds:在 Ltime 的基礎(chǔ)上,增加微秒的時間數(shù)值顯示

Llongfile:完整的文件名和行號:/a/b/c/d.go:23

Lshortfile:當(dāng)前文件名和行號:d.go:23,會覆蓋 Llongfile 標(biāo)識

LUTC:如果設(shè)置 Ldate 或 Ltime,且設(shè)置 LUTC,則優(yōu)先使用 UTC 時區(qū)而不是本地時區(qū)

LstdFlags:Logger 的默認(rèn)初始值(Ldate 和 Ltime)

(4) out:io.Writer
(5) buf:用于存儲將要寫入的日志內(nèi)容

New
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,接受三個初始參數(shù),可以定制化而在 log 包內(nèi)默認(rèn)會初始一個 std,它指向標(biāo)準(zhǔn)輸入流。而默認(rèn)的標(biāo)準(zhǔn)輸出、標(biāo)準(zhǔn)錯誤就是顯示器(輸出到屏幕上),標(biāo)準(zhǔn)輸入就是鍵盤。輔助的時間信息默認(rèn)為 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:標(biāo)準(zhǔn)輸入

Stdout:標(biāo)準(zhǔn)輸出

Stderr:標(biāo)準(zhǔn)錯誤

Getter

Flags

Prefix

Setter

SetFlags

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 函數(shù) 換行、可變參數(shù)都是通過 fmt 標(biāo)準(zhǔn)庫的方法去實現(xiàn)的

FatalPanic 是通過 os.Exit(1)、panic(s) 集成實現(xiàn)的。而具體的組裝邏輯是通過 Output 方法實現(xiàn)的

Logger.Output
func (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 方法,簡單來講就是將寫入的日志事件信息組裝并輸出,它會根據(jù) flag 標(biāo)識位的不同來使用 runtime.Caller 去獲取當(dāng)前 goroutine 所執(zhí)行的函數(shù)文件、行號等調(diào)用信息(log 標(biāo)準(zhǔn)庫中默認(rèn)深度為 2)。另外如果結(jié)尾不是換行符 ,將自動補(bǔ)全一個換行

Logger.formatHeader
func (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, ": "...)
    }
}

該方法主要是用于格式化日志頭(前綴),根據(jù)入?yún)⒉煌臉?biāo)識位,添加分隔符和對應(yīng)的值到日志信息中。執(zhí)行流程如下:

(1)如果不是空值,則將 prefix 寫入 buf

(2)如果設(shè)置 Ldate、LtimeLmicroseconds,則對應(yīng)將日期和時間寫入 buf

(3)如果設(shè)置 Lshortfile、Llongfile,則對應(yīng)將文件和行號信息寫入 buf

Logger.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ù)轉(zhuǎn)換為定長的十進(jìn)制 ASCII,同時給出負(fù)數(shù)寬度避免左側(cè)補(bǔ) 0。另外會以相反的順序組合十進(jìn)制

如何定制化 Logger

在標(biāo)準(zhǔn)庫內(nèi),可通過其開放的 New 方法來實現(xiàn)各種各樣的自定義 Logger 組件,但是為什么也可以直接 log.Print* 等方法呢?

func New(out io.Writer, prefix string, flag int) *Logger

其實是在標(biāo)準(zhǔn)庫內(nèi),如果你剛剛細(xì)心的看了前面的小節(jié),不難發(fā)現(xiàn)其默認(rèn)實現(xiàn)了一個 Logger 組件

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

這也是一個小小的精妙之處 ??

總結(jié)

通過查閱 log 標(biāo)準(zhǔn)庫的源碼,可得知最簡單的一個日志包應(yīng)該如何編寫。另外 log 包是在所有涉及到 Logger 的地方都對 sync.Mutex 進(jìn)行操作(以此解決原子問題),其余邏輯均為組裝日志信息和轉(zhuǎn)換數(shù)值格式,該包較為經(jīng)典,可以多讀幾遍

文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請注明本文地址:http://systransis.cn/yun/29470.html

相關(guān)文章

  • Golang 源碼剖析:fmt 標(biāo)準(zhǔn) --- Print* 是怎么樣輸出的?

    摘要:源碼剖析標(biāo)準(zhǔn)庫是怎么樣輸出的原文地址源碼剖析標(biāo)準(zhǔn)庫前言標(biāo)準(zhǔn)開場見多了,那內(nèi)部標(biāo)準(zhǔn)庫又是怎么輸出這段英文的呢今天一起來圍觀下源碼吧 Golang 源碼剖析:fmt 標(biāo)準(zhǔn)庫 --- Print* 是怎么樣輸出的? 原文地址:Golang 源碼剖析:fmt 標(biāo)準(zhǔn)庫 前言 package main import ( fmt ) func main() { fmt.Print...

    charles_paul 評論0 收藏0
  • 重拾golang - go目錄結(jié)構(gòu)說明

    摘要:目錄結(jié)構(gòu)說明集多編程范式之大成者,使開發(fā)者能夠快速的開發(fā)測試部署程序,支持全平臺靜態(tài)編譯。上目錄位置主要目錄包含如下圖,分別進(jìn)行說明文件夾存放檢查器的輔助文件。工作區(qū)有個子目錄目錄目錄和目錄。目錄用于以代碼包的形式組織并保存源碼文件。 go 目錄結(jié)構(gòu)說明 ??golang集多編程范式之大成者,使開發(fā)者能夠快速的開發(fā)、測試、部署程序,支持全平臺靜態(tài)編譯。go具有優(yōu)秀的依賴管理,高效的運行...

    zhisheng 評論0 收藏0
  • Derek解讀Bytom源碼-Api Server接口服務(wù)

    摘要:首先讀取請求內(nèi)容,解析請求,接著匹配相應(yīng)的路由項,隨后調(diào)用路由項的回調(diào)函數(shù)來處理。每一個路由項由請求方法和回調(diào)函數(shù)組成將監(jiān)聽地址作為參數(shù),最終執(zhí)行開始服務(wù)于外部請求創(chuàng)建對象首先,實例化對象。我們可以看到一條項由和對應(yīng)的回調(diào)函數(shù)組成。 作者:Derek 簡介 Github地址:https://github.com/Bytom/bytom Gitee地址:https://gitee.com...

    GitCafe 評論0 收藏0
  • PHPer書單

    摘要:想提升自己,還得多看書多看書多看書下面是我收集到的一些程序員應(yīng)該看得書單及在線教程,自己也沒有全部看完。共勉吧當(dāng)然,如果你有好的書想分享給大家的或者覺得書單不合理,可以去通過進(jìn)行提交。講師溫銘,軟件基金會主席,最佳實踐作者。 想提升自己,還得多看書!多看書!多看書!下面是我收集到的一些PHP程序員應(yīng)該看得書單及在線教程,自己也沒有全部看完。共勉吧!當(dāng)然,如果你有好的書想分享給大家的或者...

    jimhs 評論0 收藏0
  • Golang 大殺器之性能剖析 PProf

    摘要:大殺器之性能剖析原文地址大殺器之性能剖析前言寫了幾噸代碼,實現(xiàn)了幾百個接口。功能測試也通過了,終于成功的部署上線了結(jié)果,性能不佳,什么鬼 Golang 大殺器之性能剖析 PProf 原文地址:Golang 大殺器之性能剖析 PProf 前言 寫了幾噸代碼,實現(xiàn)了幾百個接口。功能測試也通過了,終于成功的部署上線了 結(jié)果,性能不佳,什么鬼?

    leeon 評論0 收藏0

發(fā)表評論

0條評論

閱讀需要支付1元查看
<