【问题】
折腾:
【已解决】go语言中实现log信息同时输出到文件和控制台(命令行)
期间,也已经实现了,把MultiWriter赋值给log,实现了同时输出信息到log文件和console。
但是,当把代码调整后,都统一使用log去输出所有要打印的信息,变成如下代码:
:
/*
* [File]
* EmulateLoginBaidu.go
*
* [Function]
* 【记录】用go语言实现模拟登陆百度
* https://www.crifan.org/emulate_login_baidu_using_go_language/
*
* [Version]
* 2013-09-19
*
* [Contact]
* https://www.crifan.org/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.org/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.org/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.org/emulate_login_baidu_using_go_language/
*
* [Version]
* 2013-09-19
*
* [Contact]
* https://www.crifan.org/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.org/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的信息,但是结果只能输出部分信息