最新消息:20210816 当前crifan.com域名已被污染,为防止失联,请关注(页面右下角的)公众号

【已解决】go语言中使用MultiWriter输出到文件和console的信息,但是结果只能输出部分信息

GO crifan 3172浏览 0评论

【问题】

折腾:

【已解决】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。

但是,此处却只能看到两行信息:

console only output two line info

log file also only show two line

其他信息,包括后续的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()
}

看看结果,最终是实现了所要的效果:

can normally show info to log file and console

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的信息,但是结果只能输出部分信息

发表我的评论
取消评论

表情

Hi,您需要填写昵称和邮箱!

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址
82 queries in 0.163 seconds, using 22.18MB memory