欧美一级特黄大片做受成人-亚洲成人一区二区电影-激情熟女一区二区三区-日韩专区欧美专区国产专区

Go語言之log日志

在我們開發(fā)程序后,如果有一些問題需要對程序進(jìn)行調(diào)試的時候,日志是必不可少的,這是我們分析程序問題常用的手段。

我們提供的服務(wù)有:網(wǎng)站制作、網(wǎng)站建設(shè)、微信公眾號開發(fā)、網(wǎng)站優(yōu)化、網(wǎng)站認(rèn)證、下城ssl等。為1000多家企事業(yè)單位解決了網(wǎng)站和推廣的問題。提供周到的售前咨詢和貼心的售后服務(wù),是有科學(xué)管理、有技術(shù)的下城網(wǎng)站制作公司

 

日志使用

 

日志分析,就是根據(jù)輸出的日志信息,分析挖掘可能的問題,我們使用fmt.Println系列函數(shù)也可以達(dá)到目的,因?yàn)樗鼈円部梢园盐覀冃枰男畔⑤敵龅浇K端或者其他文件中。不過fmt.Println系列函數(shù)輸出的系統(tǒng)比較簡單,比如沒有時間,也沒有源代碼的行數(shù)等,對于我們排查問題,缺少了很多信息。


對此,Go語言為我們提供了標(biāo)準(zhǔn)的log包,來跟蹤日志的記錄。下面我們看看日志包log的使用。

 

func main() {
    log.Println("飛雪無情的博客:","http://www.flysnow.org")
    log.Printf("飛雪無情的微信公眾號:%s\\n","flysnow_org")}


使用非常簡單,函數(shù)名字和用法也和fmt包很相似,但是它的輸出默認(rèn)帶了時間戳。


2017/04/29 13:18:44飛雪無情的博客: http://www.flysnow.org
2017/04/29 13:18:44 飛雪無情的微信公眾號:flysnow_org

 

這樣我們很清晰的就知道了,記錄這些日志的時間,這對我們排查問題,非常有用。


有了時間了,我們還想要更多的信息,必然發(fā)生的源代碼行號等,對此日志包log為我們提供了可定制化的配制,讓我們可以自己定制日志的抬頭信息。

 

func init(){
    log.SetFlags(log.Ldate|log.Lshortfile)}

 

我們使用init函數(shù),這個函數(shù)在main函數(shù)執(zhí)行之前就可以初始化,可以幫我們做一些配置,這里我們自定義日志的抬頭信息為時間+文件名+源代碼所在行號。也就是log.Ldate|log.Lshortfile,中間是一個位運(yùn)算符|,然后通過函數(shù)log.SetFlags進(jìn)行設(shè)置?,F(xiàn)在我們再運(yùn)行下看看輸出的日志。

 

2017/04/29main.go:10:飛雪無情的博客:http://www.flysnow.org
2017/04/29 main.go:11: 飛雪無情的微信公眾號:flysnow_org


比著上一個例子,多了源文件以及行號,但是少了時間,這就是我們自定義出來的結(jié)果。現(xiàn)在我們看看log包為我們提供了那些可以定義的選項(xiàng)常量。

 

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                          //日期時間轉(zhuǎn)為0時區(qū)的
    LstdFlags     = Ldate | Ltime //Go提供的標(biāo)準(zhǔn)抬頭信息)

 

這是log包定義的一些抬頭信息,有日期、時間、毫秒時間、絕對路徑和行號、文件名和行號等,在上面都有注釋說明,這里需要注意的是:如果設(shè)置了Lmicroseconds,那么Ltime就不生效了;設(shè)置了Lshortfile,Llongfile也不會生效,大家自己可以測試一下。


LUTC比較特殊,如果我們配置了時間標(biāo)簽,那么如果設(shè)置了LUTC的話,就會把輸出的日期時間轉(zhuǎn)為0時區(qū)的日期時間顯示。

 

log.SetFlags(log.Ldate|log.Ltime |log.LUTC)

 

那么對我們東八區(qū)的時間來說,就會減去 8個小時,我們看輸出:

 

2017/04/29 05:46:29飛雪無情的博客: http://www.flysnow.org
2017/04/29 05:46:29 飛雪無情的微信公眾號:flysnow_org

 

最后一個LstdFlags表示標(biāo)準(zhǔn)的日志抬頭信息,也就是默認(rèn)的,包含日期和具體時間。


我們大部分情況下,都有很多業(yè)務(wù),每個業(yè)務(wù)都需要記錄日志,那么有沒有辦法,能區(qū)分這些業(yè)務(wù)呢?這樣我們在查找日志的時候,就方便多了。


對于這種情況,Go語言也幫我們考慮到了,這就是設(shè)置日志的前綴,比如一個用戶中心系統(tǒng)的日志,我們可以這么設(shè)置。

 

func init(){

    log.SetPrefix("【UserCenter】")

    log.SetFlags(log.LstdFlags |log.Lshortfile |log.LUTC)
}

 

通過log.SetPrefix可以指定輸出日志的前綴,這里我們指定為【UserCenter】,然后就可以看到日志的打印輸出已經(jīng)清晰的標(biāo)記出我們的這些日志是屬于哪些業(yè)務(wù)的啦。

 

【UserCenter】2017/04/29 05:53:26 main.go:11: 飛雪無情的博客:http://www.flysnow.org
【UserCenter】2017/04/29 05:53:26main.go:12: 飛雪無情的微信公眾號:flysnow_org

 

log包除了有Print系列的函數(shù),還有Fatal以及Panic系列的函數(shù),其中Fatal表示程序遇到了致命的錯誤,需要退出,這時候使用Fatal記錄日志后,然后程序退出,也就是說Fatal相當(dāng)于先調(diào)用Print打印日志,然后再調(diào)用os.Exit(1)退出程序。


同理Panic系列的函數(shù)也一樣,表示先使用Print記錄日志,然后調(diào)用panic()函數(shù)拋出一個恐慌,這時候除非使用recover()函數(shù),否則程序就會打印錯誤堆棧信息,然后程序終止。


這里貼下這幾個系列函數(shù)的源代碼,更好理解。

 

func Println(v...interface{}) {

    std.Output(2, fmt.Sprintln(v...))
}
func Fatalln(v ...interface{}) {

    std.Output(2, fmt.Sprintln(v...))

    os.Exit(1)
}
func Panicln(v ...interface{}) {

    s := fmt.Sprintln(v...)

    std.Output(2, s)   panic(s)
}

 

 

實(shí)現(xiàn)原理


通過上面的源代碼,我們發(fā)現(xiàn),日志包log的這些函數(shù)都是類似的,關(guān)鍵的輸出日志就在于std.Output方法。

 

func New(out io.Writer, prefixstring, flag int) *Logger {

    return &Logger{out: out,prefix: prefix, flag: flag}
}
var std = New(os.Stderr, "", LstdFlags)

 

從以上源代碼可以看出,變量std其實(shí)是一個*Logger,通過log.New函數(shù)創(chuàng)建,默認(rèn)輸出到os.Stderr設(shè)備,前綴為空,日志抬頭信息為標(biāo)準(zhǔn)抬頭LstdFlags。


os.Stderr對應(yīng)的是UNIX里的標(biāo)準(zhǔn)錯誤警告信息的輸出設(shè)備,同時被作為默認(rèn)的日志輸出目的地。初次之外,還有標(biāo)準(zhǔn)輸出設(shè)備os.Stdout以及標(biāo)準(zhǔn)輸入設(shè)備os.Stdin。


var (
    Stdin  = NewFile(uintptr(syscall.Stdin), "/dev/stdin")
    Stdout = NewFile(uintptr(syscall.Stdout), "/dev/stdout")
    Stderr = NewFile(uintptr(syscall.Stderr), "/dev/stderr"))


以上就是定義的UNIX的標(biāo)準(zhǔn)的三種設(shè)備,分別用于輸入、輸出和警告錯誤信息。理解了os.Stderr,現(xiàn)在我們看下Logger這個結(jié)構(gòu)體,日志的信息和操作,都是通過這個Logger操作的。


type Logger struct {

    mu     sync.Mutex //ensures atomic writes; protects the following fields

    prefix string     //prefix to write at beginning of each line

    flag   int       // properties

    out    io.Writer // destination for output

    buf    []byte    // for accumulating text to write
}


·        字段mu是一個互斥鎖,主要是是保證這個日志記錄器Logger在多goroutine下也是安全的。

 

·        字段prefix是每一行日志的前綴。

 

·        字段flag是日志抬頭信息。

 

·        字段out是日志輸出的目的地,默認(rèn)情況下是os.Stderr。

 

·        字段buf是一次日志輸出文本緩沖,最終會被寫到out里。


了解了結(jié)構(gòu)體Logger的字段,現(xiàn)在就可以看下它最重要的方法Output了,這個方法會輸出格式化好的日志信息。


func (l *Logger) Output(calldepth int, s string) error {
    now := time.Now() // get this early.
    var file string
    var line int
    //加鎖,保證多goroutine下的安全
    l.mu.Lock()
    defer l.mu.Unlock()    
    //如果配置了獲取文件和行號的話
    if l.flag&(Lshortfile|Llongfile) != 0 {        
        //因?yàn)閞untime.Caller代價比較大,先不加鎖
        l.mu.Unlock()        
        var ok bool
        _, file, line, ok = runtime.Caller(calldepth)        
        if !ok {
            file = "???"
            line = 0
        }        
        //獲取到行號等信息后,再加鎖,保證安全
        l.mu.Lock()
    }    
    //把我們的日志信息和設(shè)置的日志抬頭進(jìn)行拼接
    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] != '\\n' {
        l.buf = append(l.buf, '\\n')
    }    
    //輸出拼接好的緩沖buf里的日志信息到目的地
    _, err := l.out.Write(l.buf)    
    return err
}

 

整個代碼比較簡潔,為了多goroutine安全互斥鎖也用上了,但是在獲取調(diào)用堆棧信息的時候,又要先解鎖,因?yàn)檫@個過程比較重。獲取到文件、行號等信息后,繼續(xù)加互斥鎖保證安全。


后面的就比較簡單了,formatHeader方法主要是格式化日志抬頭信息,然后存儲在buf這個緩沖中,最后再把我們自己的日志信息拼接到緩沖buf的后面,然后為一次log日志輸出追加一個換行符,這樣每次日志輸出都是一行一行的。


有了最終的日志信息buf,然后把它寫到輸出的目的地out里就可以了,這是一個實(shí)現(xiàn)了io.Writer接口的類型,只要實(shí)現(xiàn)了這個接口,都可以當(dāng)作輸出目的地。


func (l *Logger) SetOutput(wio.Writer) {

    l.mu.Lock()    

    defer l.mu.Unlock()

    l.out = w
}


log包的SetOutput函數(shù),可以設(shè)置輸出目的地。這里稍微簡單介紹下runtime.Caller,它可以獲取運(yùn)行時方法的調(diào)用信息。


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

 

參數(shù)skip表示跳過棧幀數(shù),0 表示不跳過,也就是runtime.Caller的調(diào)用者。1 的話就是再向上一層,表示調(diào)用者的調(diào)用者。


log日志包里使用的是 2 ,也就是表示我們在源代碼中調(diào)用log.Print、log.Fatal和log.Panic這些函數(shù)的調(diào)用者。


以main函數(shù)調(diào)用log.Println為例,是main->log.Println->*Logger.Output->runtime.Caller這么一個方法調(diào)用棧,所以這時候,skip的值分別代表:

 

·        0 表示*Logger.Output中調(diào)用runtime.Caller的源代碼文件和行號。

 

·        1 表示log.Println中調(diào)用*Logger.Output的源代碼文件和行號。

 

·        2 表示main中調(diào)用log.Println的源代碼文件和行號。

 

所以這也是log包里的這個skip的值為什么一直是 2 的原因。

 

定制自己的日志


通過上面的源碼分析,我們知道日志記錄的根本就在于一個日志記錄器Logger,所以我們定制自己的日志,其實(shí)就是創(chuàng)建不同的Logger。

var (
    Info *log.Logger
    Warning *log.Logger
    Error * log.Logger)func init(){
    errFile,err:=os.OpenFile("errors.log",os.O_CREATE|os.O_WRONLY|os.O_APPEND,0666)
    if err!=nil{
        log.Fatalln("打開日志文件失敗:",err)
    }

    Info = log.New(os.Stdout,"Info:",log.Ldate | log.Ltime | log.Lshortfile)
    Warning = log.New(os.Stdout,"Warning:",log.Ldate | log.Ltime | log.Lshortfile)
    Error = log.New(io.MultiWriter(os.Stderr,errFile),"Error:",log.Ldate | log.Ltime | log.Lshortfile)}func main() {
    Info.Println("飛雪無情的博客:","http://www.flysnow.org")
    Warning.Printf("飛雪無情的微信公眾號:%s\\n","flysnow_org")
    Error.Println("歡迎關(guān)注留言")
}

 

我們根據(jù)日志級別定義了三種不同的Logger,分別為Info,Warning,Error,用于不同級別日志的輸出。這三種日志記錄器都是使用log.New函數(shù)進(jìn)行創(chuàng)建。


這里創(chuàng)建Logger的時候,Info和Warning都比較正常,Error這里采用了多個目的地輸出,這里可以同時把錯誤日志輸出到os.Stderr以及我們創(chuàng)建的errors.log文件中。


io.MultiWriter函數(shù)可以包裝多個io.Writer為一個io.Writer,這樣我們就可以達(dá)到同時對多個io.Writer輸出日志的目的。


io.MultiWriter的實(shí)現(xiàn)也很簡單,定義一個類型實(shí)現(xiàn)io.Writer,然后在實(shí)現(xiàn)的Write方法里循環(huán)調(diào)用要包裝的多個Writer接口的Write方法即可。

func (t *multiWriter) Write(p []byte) (n int, err error) {
    for _, w := range t.writers {
        n, err = w.Write(p)
        if err != nil {
                    return
        } 
        if n != len(p) {
            err = ErrShortWrite            
            return
        }
    }    
    return len(p), nil
}


這里我們通過定義了多個Logger來區(qū)分不同的日志級別,使用比較麻煩,針對這種情況,可以使用第三方的log框架,也可以自定包裝定義,直接通過不同級別的方法來記錄不同級別的日志,還可以設(shè)置記錄日志的級別等。

 

網(wǎng)頁題目:Go語言之log日志
當(dāng)前鏈接:http://aaarwkj.com/article28/pdiojp.html

成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供服務(wù)器托管、全網(wǎng)營銷推廣、網(wǎng)站制作、網(wǎng)站建設(shè)、品牌網(wǎng)站設(shè)計(jì)、營銷型網(wǎng)站建設(shè)

廣告

聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請盡快告知,我們將會在第一時間刪除。文章觀點(diǎn)不代表本網(wǎng)站立場,如需處理請聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時需注明來源: 創(chuàng)新互聯(lián)

外貿(mào)網(wǎng)站建設(shè)
日韩无砖区2021不卡| 精品少妇人妻一区二区三区| 最近免费欧美一级黄片| av免费观看男人的天堂| 免费国产成人高清在线视频| 亚洲国产中文日韩欧美在线| 九九九热在线观看视频| 精品国产女同一区二区| 久久国产精品欧美熟妇| 99精品热视频在线观看| av毛片高清在线观看| 女同久久精品国产精品天堂99| 91精品蜜臀国产综合久久久久久| 日韩欧美一区亚洲一区| 国产真人作爱视频免费| 激情亚洲不卡一区二区| 亚洲av天堂天天天堂色 | 依依成人影院在线观看av| 日韩一区欧美中文字幕| 青春草草视频在线观看| 亚洲天堂av现在观看| 日韩一区二区三区无吗电影 | 国产无套内射三级视频| 中文字幕一区二区三区不卡日日| 日韩精品一区二区三区四区蜜桃| 欧美日韩国产亚洲免费| 一区二区在线日韩视频| 九九视频免费观看5| 91久久精品凹凸一区二区| 激情久久五月激情婷婷| 国产一区二区黄色网页| 国产亚洲中文字幕91| 日本精品a秘在线观看| 欧美视频在线免费观看黄片| 亚洲av优选在线观看精品| 免费观看在线黄色大片| 国产三级在线dvd观看| 欧美日韩精品免费在线观看| 亚洲精品国产亚洲精品| 伊人丁香六月日日操操| 四虎永久精品国产毛片|