【问题】
折腾:
【已解决】go语言中实现log信息同时输出到文件和控制台(命令行)
期间,也已经实现了,把MultiWriter赋值给log,实现了同时输出信息到log文件和console。
但是,当把代码调整后,都统一使用log去输出所有要打印的信息,变成如下代码:
:
/* * [File] * EmulateLoginBaidu.go * * [Function] * 【记录】用go语言实现模拟登陆百度 * https://www.crifan.com/emulate_login_baidu_using_go_language/ * * [Version] * 2013-09-19 * * [Contact] * https://www.crifan.com/about/me/ */ package main import ( "fmt" "log" "os" "runtime" "path" "strings" "io" "io/ioutil" "net/http" //"net/http/cookiejar" //"sync" //"net/url" ) /*************************************************************************************************** Global Variables ***************************************************************************************************/ var gCurCookies []*http.Cookie; var gLogger *log.Logger; /*************************************************************************************************** Functions ***************************************************************************************************/ //do some init for crifanLib func initCrifanLib(){ gLogger.Println("init for crifanLib"); gCurCookies = nil return } //init for logger func initLogger(){ var filenameOnly string filenameOnly = GetCurFilename() var logFilename string = filenameOnly + ".log"; logFile, err := os.OpenFile(logFilename, os.O_RDWR | os.O_CREATE, 0777) if err != nil { fmt.Printf("open file error=%s\r\n", err.Error()) os.Exit(-1) } defer logFile.Close() writers := []io.Writer{ logFile, os.Stdout, } fileAndStdoutWriter := io.MultiWriter(writers...) //fileAndStdoutWriter.Write([]byte("show in both log file and console via multiwriter")) //gLogger := log.New(logFile,"\r\n", log.Ldate | log.Ltime | log.Lshortfile) //gLogger = log.New(logFile, "\r\n", log.Ldate | log.Ltime | log.Lshortfile) //gLogger = log.New(fileAndStdoutWriter, "\r\n", log.Ldate | log.Ltime | log.Lshortfile) gLogger = log.New(fileAndStdoutWriter, "", log.Ldate | log.Ltime | log.Lshortfile) //gLogger.Println("normal log 1") //gLogger.Println("normal log 2") gLogger.Println("filenameOnly=", filenameOnly) gLogger.Println("logFilename=", logFilename) //【已解决】go代码出错退出:exit status 1 //https://www.crifan.com/go_language_can_printf_info_and_exit_status_1/ //gLogger.Panic("panic 1") //gLogger.Fatal("fatal 1") return } // type Jar struct { // lk sync.Mutex // cookies map[string][]*http.Cookie // } // func NewJar() *Jar { // jar := new(Jar) // jar.cookies = make(map[string][]*http.Cookie) // return jar // } // GetCurFilename // Get current file name, without suffix func GetCurFilename() string { _, fulleFilename, _, _ := runtime.Caller(0) //fmt.Println(fulleFilename) var filenameWithSuffix string filenameWithSuffix = path.Base(fulleFilename) //fmt.Println("filenameWithSuffix=", filenameWithSuffix) var fileSuffix string fileSuffix = path.Ext(filenameWithSuffix) //fmt.Println("fileSuffix=", fileSuffix) var filenameOnly string filenameOnly = strings.TrimSuffix(filenameWithSuffix, fileSuffix) //fmt.Println("filenameOnly=", filenameOnly) return filenameOnly } //get url response html func GetUrlRespHtml(url string) string{ var respHtml string = ""; resp, err := http.Get(url) if err != nil { gLogger.Printf("http get response errror=%s\n", err) } defer resp.Body.Close() body, err := ioutil.ReadAll(resp.Body) //gLogger.Printf("body=%s\n", body) gCurCookies = resp.Cookies() respHtml = string(body) return respHtml } func printCurCookies() { var cookieNum int = len(gCurCookies); gLogger.Printf("cookieNum=%d\r\n", cookieNum) for i := 0; i < cookieNum; i++ { var curCk *http.Cookie = gCurCookies[i]; //gLogger.Printf("curCk.Raw=%s\r\n", curCk.Raw) gLogger.Printf("------ Cookie [%d]------\r\n", i) gLogger.Printf("Name\t=%s\r\n", curCk.Name) gLogger.Printf("Value\t=%s\r\n", curCk.Value) gLogger.Printf("Path\t=%s\r\n", curCk.Path) gLogger.Printf("Domain\t=%s\r\n", curCk.Domain) gLogger.Printf("Expires\t=%s\r\n", curCk.Expires) gLogger.Printf("RawExpires=%s\r\n", curCk.RawExpires) gLogger.Printf("MaxAge\t=%d\r\n", curCk.MaxAge) gLogger.Printf("Secure\t=%t\r\n", curCk.Secure) gLogger.Printf("HttpOnly=%t\r\n", curCk.HttpOnly) gLogger.Printf("Raw\t=%s\r\n", curCk.Raw) gLogger.Printf("Unparsed=%s\r\n", curCk.Unparsed) } } func main() { initLogger() initCrifanLib() gLogger.Println("this is EmulateLoginBaidu.go\n") var baiduMainUrl string baiduMainUrl = "http://www.baidu.com/"; //baiduMainUrl := "http://www.baidu.com/"; //var baiduMainUrl string = "http://www.baidu.com/"; gLogger.Printf("baiduMainUrl=%s\r\n", baiduMainUrl) respHtml := GetUrlRespHtml(baiduMainUrl) gLogger.Printf("respHtml=%s\r\n", respHtml) printCurCookies() }
希望实现,此处要打印的,所有的(在log初始化后的)信息,都可以输出到文件和console。
但是,此处却只能看到两行信息:
其他信息,包括后续的cookie的值,都没显示出来。
【解决过程】
1.开始以为难道是,之前的fmt的Println和Printf的函数,换成log后,log本身没有这些函数,导致无法输出?
但是去确认了下,log是有这些函数的:
http://golang.org/pkg/log/#Println
http://golang.org/pkg/log/#Printf
所以,不是这个问题。
2.后来,无意间,看到
//init for logger func initLogger(){ var filenameOnly string filenameOnly = GetCurFilename() var logFilename string = filenameOnly + ".log"; logFile, err := os.OpenFile(logFilename, os.O_RDWR | os.O_CREATE, 0777) if err != nil { fmt.Printf("open file error=%s\r\n", err.Error()) os.Exit(-1) } defer logFile.Close() writers := []io.Writer{ logFile, os.Stdout, } fileAndStdoutWriter := io.MultiWriter(writers...) //fileAndStdoutWriter.Write([]byte("show in both log file and console via multiwriter")) //gLogger := log.New(logFile,"\r\n", log.Ldate | log.Ltime | log.Lshortfile) //gLogger = log.New(logFile, "\r\n", log.Ldate | log.Ltime | log.Lshortfile) //gLogger = log.New(fileAndStdoutWriter, "\r\n", log.Ldate | log.Ltime | log.Lshortfile) gLogger = log.New(fileAndStdoutWriter, "", log.Ldate | log.Ltime | log.Lshortfile) //gLogger.Println("normal log 1") //gLogger.Println("normal log 2") gLogger.Println("filenameOnly=", filenameOnly) gLogger.Println("logFilename=", logFilename) //【已解决】go代码出错退出:exit status 1 //https://www.crifan.com/go_language_can_printf_info_and_exit_status_1/ //gLogger.Panic("panic 1") //gLogger.Fatal("fatal 1") return }
中的这行:
defer logFile.Close()
猜想,不会是由于logFile,在被关闭了后,退出函数initLogger后,后续的输出的内容,就都没有了吧?
然后去试试,把上面这行,放到对应的外部,同时声明一个全局的logFile,等到当前go文件执行完毕后,再close:
/* * [File] * EmulateLoginBaidu.go * * [Function] * 【记录】用go语言实现模拟登陆百度 * https://www.crifan.com/emulate_login_baidu_using_go_language/ * * [Version] * 2013-09-19 * * [Contact] * https://www.crifan.com/about/me/ */ package main import ( "fmt" //"builtin" "log" "os" "runtime" "path" "strings" "io" "io/ioutil" "net/http" //"net/http/cookiejar" //"sync" //"net/url" ) /*************************************************************************************************** Global Variables ***************************************************************************************************/ var gCurCookies []*http.Cookie; var gLogger *log.Logger; var gLogFile *os.File; /*************************************************************************************************** Functions ***************************************************************************************************/ //do some init for crifanLib func initCrifanLib(){ gLogger.Println("init for crifanLib"); gCurCookies = nil return } //init for logger func initLogger(){ var filenameOnly string filenameOnly = GetCurFilename() var logFilename string = filenameOnly + ".log"; var err error; //gLogFile, err := os.OpenFile(logFilename, os.O_RDWR | os.O_CREATE, 0777) gLogFile, err = os.OpenFile(logFilename, os.O_RDWR | os.O_CREATE, 0777) if err != nil { fmt.Printf("open file error=%s\r\n", err.Error()) os.Exit(-1) } //not close log file here, otherwise later gLogger is not usable //only close log file after whole go file done writers := []io.Writer{ gLogFile, os.Stdout, } fileAndStdoutWriter := io.MultiWriter(writers...) //fileAndStdoutWriter.Write([]byte("show in both log file and console via multiwriter")) //gLogger := log.New(gLogFile,"\r\n", log.Ldate | log.Ltime | log.Lshortfile) //gLogger = log.New(gLogFile, "\r\n", log.Ldate | log.Ltime | log.Lshortfile) //gLogger = log.New(fileAndStdoutWriter, "\r\n", log.Ldate | log.Ltime | log.Lshortfile) gLogger = log.New(fileAndStdoutWriter, "", log.Ldate | log.Ltime | log.Lshortfile) //gLogger.Println("normal log 1") //gLogger.Println("normal log 2") gLogger.Println("filenameOnly=", filenameOnly) gLogger.Println("logFilename=", logFilename) //【已解决】go代码出错退出:exit status 1 //https://www.crifan.com/go_language_can_printf_info_and_exit_status_1/ //gLogger.Panic("panic 1") //gLogger.Fatal("fatal 1") return } // type Jar struct { // lk sync.Mutex // cookies map[string][]*http.Cookie // } // func NewJar() *Jar { // jar := new(Jar) // jar.cookies = make(map[string][]*http.Cookie) // return jar // } // GetCurFilename // Get current file name, without suffix func GetCurFilename() string { _, fulleFilename, _, _ := runtime.Caller(0) //fmt.Println(fulleFilename) var filenameWithSuffix string filenameWithSuffix = path.Base(fulleFilename) //fmt.Println("filenameWithSuffix=", filenameWithSuffix) var fileSuffix string fileSuffix = path.Ext(filenameWithSuffix) //fmt.Println("fileSuffix=", fileSuffix) var filenameOnly string filenameOnly = strings.TrimSuffix(filenameWithSuffix, fileSuffix) //fmt.Println("filenameOnly=", filenameOnly) return filenameOnly } //get url response html func GetUrlRespHtml(url string) string{ var respHtml string = ""; resp, err := http.Get(url) if err != nil { gLogger.Printf("http get response errror=%s\n", err) } defer resp.Body.Close() body, err := ioutil.ReadAll(resp.Body) //gLogger.Printf("body=%s\n", body) gCurCookies = resp.Cookies() respHtml = string(body) return respHtml } func printCurCookies() { var cookieNum int = len(gCurCookies); gLogger.Printf("cookieNum=%d\r\n", cookieNum) for i := 0; i < cookieNum; i++ { var curCk *http.Cookie = gCurCookies[i]; //gLogger.Printf("curCk.Raw=%s\r\n", curCk.Raw) gLogger.Printf("------ Cookie [%d]------\r\n", i) gLogger.Printf("Name\t=%s\r\n", curCk.Name) gLogger.Printf("Value\t=%s\r\n", curCk.Value) gLogger.Printf("Path\t=%s\r\n", curCk.Path) gLogger.Printf("Domain\t=%s\r\n", curCk.Domain) gLogger.Printf("Expires\t=%s\r\n", curCk.Expires) gLogger.Printf("RawExpires=%s\r\n", curCk.RawExpires) gLogger.Printf("MaxAge\t=%d\r\n", curCk.MaxAge) gLogger.Printf("Secure\t=%t\r\n", curCk.Secure) gLogger.Printf("HttpOnly=%t\r\n", curCk.HttpOnly) gLogger.Printf("Raw\t=%s\r\n", curCk.Raw) gLogger.Printf("Unparsed=%s\r\n", curCk.Unparsed) } } func main() { initLogger() initCrifanLib() gLogger.Println("this is EmulateLoginBaidu.go\n") var baiduMainUrl string baiduMainUrl = "http://www.baidu.com/"; //baiduMainUrl := "http://www.baidu.com/"; //var baiduMainUrl string = "http://www.baidu.com/"; gLogger.Printf("baiduMainUrl=%s\r\n", baiduMainUrl) respHtml := GetUrlRespHtml(baiduMainUrl) gLogger.Printf("respHtml=%s\r\n", respHtml) printCurCookies() //de-init something defer gLogFile.Close() }
看看结果,最终是实现了所要的效果:
log文件中,和console中,都可以同时,显示log信息了。
【总结】
此处,通过io的MultiWriter去同时输出信息到log文件和console中,
结果却只是显示部分信息:
原因是:
对于MultiWriter的多个Writer,此处是:
一个是logFile
另一个是os.Stdout
然后由于是把log相关初始化代码,放在单独的函数initLogger中的
而在此函数中,在os.OpenFile后,得到了gLogFile后,
由于没有去改变之前参考别人的代码,所以接着有这句:
defer gLogFile.Close()
从而导致:
当前函数initLogger函数执行完毕后,就把gLogFile关闭了。
从而导致,后续的MultiWriter的log,失效了。
从而导致initLogger函数之后的,再去调用log输出,都不显示了。
解决办法是:
把原先放在initLogger函数内的:
defer gLogFile.Close()
移出去,放到当前go文件的最后,确保go文件内所有用到log的地方,都是log(的MultiWriter)被close之前,所以就可以了。
另外:
由此可见,以后,万一把代码整理成独立的库函数的话,
那么,也要小心这个defer的代码,要放到合适的地方才可以的。
否则,容易出现这样的类似问题:
某个变量, 还正在使用呢,结果就被close,被释放了;
或者是:
某个变量或资源,由于一直没有被释放,理论上也会造成资源占用或资源泄露的。
转载请注明:在路上 » 【已解决】go语言中使用MultiWriter输出到文件和console的信息,但是结果只能输出部分信息