【问题】
折腾:
【已解决】go语言中实现log信息同时输出到文件和控制台(命令行)
期间,也已经实现了,把MultiWriter赋值给log,实现了同时输出信息到log文件和console。
但是,当把代码调整后,都统一使用log去输出所有要打印的信息,变成如下代码:
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 | /* * [File] * EmulateLoginBaidu.go * * [Function] * 【记录】用go语言实现模拟登陆百度 * * [Version] * 2013-09-19 * * [Contact] */ 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 //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/"; //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.后来,无意间,看到
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 | //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 //gLogger.Panic("panic 1") //gLogger.Fatal("fatal 1") return } |
中的这行:
1 | defer logFile.Close() |
猜想,不会是由于logFile,在被关闭了后,退出函数initLogger后,后续的输出的内容,就都没有了吧?
然后去试试,把上面这行,放到对应的外部,同时声明一个全局的logFile,等到当前go文件执行完毕后,再close:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 | /* * [File] * EmulateLoginBaidu.go * * [Function] * 【记录】用go语言实现模拟登陆百度 * * [Version] * 2013-09-19 * * [Contact] */ 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 //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/"; //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后,
由于没有去改变之前参考别人的代码,所以接着有这句:
1 | defer gLogFile.Close() |
从而导致:
当前函数initLogger函数执行完毕后,就把gLogFile关闭了。
从而导致,后续的MultiWriter的log,失效了。
从而导致initLogger函数之后的,再去调用log输出,都不显示了。
解决办法是:
把原先放在initLogger函数内的:
1 | defer gLogFile.Close() |
移出去,放到当前go文件的最后,确保go文件内所有用到log的地方,都是log(的MultiWriter)被close之前,所以就可以了。
另外:
由此可见,以后,万一把代码整理成独立的库函数的话,
那么,也要小心这个defer的代码,要放到合适的地方才可以的。
否则,容易出现这样的类似问题:
某个变量, 还正在使用呢,结果就被close,被释放了;
或者是:
某个变量或资源,由于一直没有被释放,理论上也会造成资源占用或资源泄露的。
转载请注明:在路上 » 【已解决】go语言中使用MultiWriter输出到文件和console的信息,但是结果只能输出部分信息