如何把panic信息重定向,syscall.Dup2的使用

如何把panic信息重定向

根據(jù)“墨菲定律”,我們編寫(xiě)的后臺(tái)的服務(wù)都有出現(xiàn)crash的可能,一種情況是Go的后臺(tái)服務(wù)我們經(jīng)常也會(huì)遇到panic的情況。出問(wèn)題不可怕,我們需要分析并解決問(wèn)題,不過(guò)panic處理的信息,默認(rèn)是直接標(biāo)準(zhǔn)輸出的,我們希望能捕獲它指向我們特定的文件以便能做后續(xù)問(wèn)題的跟蹤排查,而不是一次性輸出難以跟蹤。

我們一個(gè)通用的方法是

err := execFunc()
if err != nil {
    outputToFile(err)
}

但有一些第三方庫(kù)會(huì)使用panic/recover機(jī)制作為其內(nèi)部的異??刂品绞?,這樣我們?cè)谕饷媸请y以察覺(jué)的,異常信息可能就直接打到我們的標(biāo)準(zhǔn)輸出那里了,除非你在執(zhí)行程序之前,使用類似linux的 ./test >> panic.log ,否則我們會(huì)很大機(jī)會(huì)與重要的跟蹤信息擦肩而過(guò)。(跨平臺(tái)到windows可能不適用)

所以,如何把panic的信息靈活地“重定向”呢?

實(shí)現(xiàn)思路一般是:

1、既然panic使用的的是標(biāo)準(zhǔn)輸出,我們可以使用自定義的文件file引用取代go里頭的os.Stdout 和 os.Stderr

2、引起panic并測(cè)試重定向的正確性

3、windows里面沒(méi)有stdout和stderr的輸出方式,也沒(méi)辦法像unix那樣使用“>>”進(jìn)行標(biāo)準(zhǔn)輸出的重新向,這個(gè)如何破?

我們先試試一個(gè)簡(jiǎn)單的例子:

package main

import (
    "fmt";
    "os";
)

const panicFile = "/tmp/panic.log"

func InitPanicFile() error {
    log.Println("init panic file in unix mode")
    file, err := os.OpenFile(panicFile, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0666)
    if err != nil {
        return err
    }

    os.Stdout = file
    os.Stderr = file
    return nil
}

func init() {
    err := pc.InitPanicFile()
    if err != nil {
        println(err)
    }
}

func testPanic() {
    panic("test panic")
}

func main() {
    testPanic()
}

這個(gè)例子,我們嘗試使用 os.Stdout = fileos.Stderr = file 來(lái)“強(qiáng)制”轉(zhuǎn)換,但我們運(yùn)行程序后,發(fā)現(xiàn)不起作用, /tmp/panic.log 沒(méi)有任何信息流入,panic信息照樣輸出到標(biāo)準(zhǔn)輸出那里。

關(guān)于原因,Rob是這樣說(shuō)的:

image.png

看來(lái)是把變量直接賦值到底層是不行的,圖上所說(shuō),推薦使用syscall.Dup的方式。我們?cè)俑膶?xiě)下 InitPanicFile() 函數(shù):

func InitPanicFile() error {
    log.Println("init panic file in unix mode")
    file, err := os.OpenFile(panicFile, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0666)
    if err != nil {
        println(err)
        return err
    }
    if err = syscall.Dup2(int(file.Fd()), int(os.Stderr.Fd())); err != nil {
        return err
    }
    return nil
}

我們運(yùn)行程序,發(fā)現(xiàn)panic正常定向到我們的文件里面去了:

$ tail -f /tmp/panic.log

panic: test panic

goroutine 1 [running]:

... ...
... ...

不過(guò)經(jīng)過(guò)實(shí)踐,上面的代碼是有些bug的,原因是我們上面的file是一個(gè)局部變量,放系統(tǒng)發(fā)生gc的時(shí)候,會(huì)觸發(fā)file里面的 runtime.SetFinalizer(f.file, (*file).close), 會(huì)引起句柄會(huì)被回收, 如果我們代碼是長(zhǎng)期運(yùn)行在后臺(tái)的話,建議代碼調(diào)整如下的形式:

var globalFile *os.File

func InitPanicFile() error {
    log.Println("init panic file in unix mode")
    file, err := os.OpenFile(panicFile, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0666)
    globalFile = file
    if err != nil {
        println(err)
        return err
    }
    if err = syscall.Dup2(int(file.Fd()), int(os.Stderr.Fd())); err != nil {
        return err
    }
    return nil
}

接下來(lái),我們要延伸思考下,如果服務(wù)是運(yùn)行在windows上面該如何破?

使用syscall.Dup2的例子windows下會(huì)編譯直接報(bào)錯(cuò):

undefined: syscall.Dup2

... ...

syscall.Dup2 is a linux/OSX only thing. there's no windows equivalent。

記得我前面的文件,介紹過(guò)go調(diào)用DLL的方法 《使用Go結(jié)合windows dll開(kāi)發(fā)程序》 ,其實(shí)我們也可以想到,可以直接使用DLL的調(diào)用達(dá)到功能效果:

代碼如下:

package main

import (
    "log"
    "os"
    "syscall"
)

const (
    kernel32dll = "kernel32.dll"
)

const panicFile = "C:/panic.log"

var globalFile *os.File

func InitPanicFile() error {
    log.Println("init panic file in windows mode")
    file, err := os.OpenFile(panicFile, os.O_CREATE|os.O_APPEND, 0666)
    globalFile = file
    if err != nil {
        return err
    }
    kernel32 := syscall.NewLazyDLL(kernel32dll)
    setStdHandle := kernel32.NewProc("SetStdHandle")
    sh := syscall.STD_ERROR_HANDLE
    v, _, err := setStdHandle.Call(uintptr(sh), uintptr(file.Fd()))
    if v == 0 {
        return err
    }
    return nil
}

func init() {
    err := pc.InitPanicFile()
    if err != nil {
        println(err)
    }
}

func testPanic() {
    panic("test panic")
}

func main() {
    testPanic()
}

然后我們把編譯后的代碼在windows下運(yùn)行,panic信息也能正常重定向到指定文件上了。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

友情鏈接更多精彩內(nèi)容