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

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

GO crifan 3471浏览 0评论

【问题】

折腾:

【已解决】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/";
    //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.后来,无意间,看到

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/";
    //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后,

由于没有去改变之前参考别人的代码,所以接着有这句:

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

发表我的评论
取消评论

表情

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

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