After we develop the program, if there are some problems need to debug the program, the log is necessary, this is our analysis program problems commonly used means.
Log usage
Log analysis, is based on the output of the log information, analysis of the potential problems of mining, we use fmt.Println
series functions can also achieve the purpose, because they can also be the information we need to output to the terminal or other files. But fmt.Println
the series function output of the system is relatively simple, such as no time, there is no source code lines, etc., for us to troubleshoot problems, missing a lot of information.
In this respect, the go language provides us with a standard log
package to track log records. Let's look at log
the use of log packages.
func main() { log.Println("飞雪无情的博客:","http://www.flysnow.org") log.Printf("飞雪无情的公众号:%s\n","flysnow_org")}
It is very simple to use, and the function name and usage is fmt
similar to the package, but its output is time stamped by default.
2017/04/29 13:18:44 飞雪无情的博客: http://www.flysnow.org2017/04/29 13:18:44 飞雪无情的公众号:flysnow_org
So we know very clearly, the time to log these logs, which is very useful for us to troubleshoot problems.
With time, we also want more information, the inevitable occurrence of source code line number, etc., for this log package log
provides us with a customizable configuration, so that we can customize the log header information.
func init(){ log.SetFlags(log.Ldate|log.Lshortfile)}
We use the function init
, this function main
can be initialized before the function executes, can help us to do some configuration, here we custom log header information is time + file name + source code line number. log.Ldate|log.Lshortfile
that is, the middle is a bitwise operator |
, which is then set by the function log.SetFlags
. Now let's run a look at the output log.
2017/04/29 main.go:10: 飞雪无情的博客: http://www.flysnow.org2017/04/29 main.go:11: 飞雪无情的公众号:flysnow_org
More than the previous example, the source file and the line number, but the time is missing, this is the result of our custom. Now let's look at the log
packages that provide us with the option constants that can be defined.
const ( Ldate = 1 << iota //日期示例: 2009/01/23 Ltime //时间示例: 01:23:23 Lmicroseconds //毫秒示例: 01:23:23.123123. Llongfile //绝对路径和行号: /a/b/c/d.go:23 Lshortfile //文件和行号: d.go:23. LUTC //日期时间转为0时区的 LstdFlags = Ldate | Ltime //Go提供的标准抬头信息)
This is a log package to define some of the header information, there are date, time, millisecond time, absolute path and line number, file name and line number, and so on, there are comments on the above, it should be noted that: if set Lmicroseconds
, then Ltime
it does not take effect Lshortfile
, set, and Llongfile
will not take effect, We can test it for ourselves.
LUTC
Very special, if we configure the time tag, then if set LUTC
, it will be the output date time to 0 time zone of the date and time display.
log.SetFlags(log.Ldate|log.Ltime |log.LUTC)
So for the time of our East eight, we subtract 8 hours, we look at the output:
2017/04/29 05:46:29 飞雪无情的博客: http://www.flysnow.org2017/04/29 05:46:29 飞雪无情的公众号:flysnow_org
The last one that LstdFlags
represents the standard log header is the default, including the date and time.
Most of us have a lot of business, every business need to log, then there is no way to differentiate these businesses? This makes it much easier for us to find the logs.
In this case, the Go language also helps us to consider, this is to set the log prefix, such as a User Center system log, we can set this.
func init(){ log.SetPrefix("【UserCenter】") log.SetFlags(log.LstdFlags | log.Lshortfile |log.LUTC)}
By log.SetPrefix
specifying the prefix of the output log, which we specify as 【UserCenter】
, we can then see that the printout of the log clearly marks the business of our logs.
【UserCenter】2017/04/29 05:53:26 main.go:11: 飞雪无情的博客: http://www.flysnow.org【UserCenter】2017/04/29 05:53:26 main.go:12: 飞雪无情的公众号:flysnow_org
log
Package In addition to a Print
series of functions, Fatal
as well as a Panic
series of functions, which indicates that the Fatal
program encountered a fatal error, need to exit, Fatal
when the log is used, then the program exits, that is, the Fatal
equivalent of calling Print
the print log first, The exit program is then called os.Exit(1)
.
Panic
the same is true of the same series of functions, which means that the Print
log is used first, and then the calling panic()
function throws a panic, and unless recover()
the function is used, the program prints the error stack information and the program terminates.
The source code of these series of functions is put down here for better understanding.
func Println(v ...interface{}) { std.Output(2, fmt.Sprintln(v...))}func Fatalln(v ...interface{}) { std.Output(2, fmt.Sprintln(v...)) os.Exit(1)}func Panicln(v ...interface{}) { s := fmt.Sprintln(v...) std.Output(2, s) panic(s)}
Implementation principle
Through the source code above, we found that the log packet log
of these functions are similar, the key output log is the std.Output
method.
func New(out io.Writer, prefix string, flag int) *Logger { return &Logger{out: out, prefix: prefix, flag: flag}}var std = New(os.Stderr, "", LstdFlags)
From the above source code can be seen, the variable std
is actually a *Logger
, through the log.New
function creation, the default output to the os.Stderr
device, the prefix is empty, the log header information is the standard header LstdFlags
.
os.Stderr
The output device corresponding to the standard error warning information in UNIX is also used as the default log output destination. For the first time, there are standard output devices os.Stdout
as well as standard input devices os.Stdin
.
var ( Stdin = NewFile(uintptr(syscall.Stdin), "/dev/stdin") Stdout = NewFile(uintptr(syscall.Stdout), "/dev/stdout") Stderr = NewFile(uintptr(syscall.Stderr), "/dev/stderr"))
The above is the standard three devices defined for UNIX, respectively, for input, output, and warning error messages. Understand os.Stderr
, now we look at Logger
this structure, the information and operation of the log, all through this Logger
operation.
type Logger struct { mu sync.Mutex // ensures atomic writes; protects the following fields prefix string // prefix to write at beginning of each line flag int // properties out io.Writer // destination for output buf []byte // for accumulating text to write}
- The field
mu
is a mutex, mainly to ensure that this logger is Logger
also safe under multiple goroutine.
- Field
prefix
is the prefix for each row of logs
- Field
flag
is log header information
- The field
out
is the destination of the log output, which is by default os.Stderr
.
- The field
buf
is a log output text buffer, which will eventually be written out
in.
Understanding the structure Logger
of the field, you can now look at its most important method Output
, this method will output the formatted good log information.
func (l *Logger) Output(calldepth int, s string) error { now := time.Now() // get this early. var file string var line int //加锁,保证多goroutine下的安全 l.mu.Lock() defer l.mu.Unlock() //如果配置了获取文件和行号的话 if l.flag&(Lshortfile|Llongfile) != 0 { //因为runtime.Caller代价比较大,先不加锁 l.mu.Unlock() var ok bool _, file, line, ok = runtime.Caller(calldepth) if !ok { file = "???" line = 0 } //获取到行号等信息后,再加锁,保证安全 l.mu.Lock() } //把我们的日志信息和设置的日志抬头进行拼接 l.buf = l.buf[:0] l.formatHeader(&l.buf, now, file, line) l.buf = append(l.buf, s...) if len(s) == 0 || s[len(s)-1] != '\n' { l.buf = append(l.buf, '\n') } //输出拼接好的缓冲buf里的日志信息到目的地 _, err := l.out.Write(l.buf) return err}
The entire code is relatively concise, in order to multi-goroutine security mutex is also used, but in the acquisition of the call stack information, the first time to unlock, because this process is heavier. After obtaining information such as file, line number and so on, continue to add the mutex to ensure security.
The following is relatively simple, the formatHeader
Main method is to format the log header information, and then stored in buf
the buffer, and finally put our own log information to the buf
back of the buffer, and then for the log output to append a newline character, so that each log output is a row of one line.
With the final log information buf
, and then write it to the output of the destination out
, which is a type of implementation of the io.Writer
interface, as long as the implementation of this interface, can be used as the output destination.
func (l *Logger) SetOutput(w io.Writer) { l.mu.Lock() defer l.mu.Unlock() l.out = w}
log
The function of the package SetOutput
, you can set the output destination. This is a little bit easier to describe runtime.Caller
, and it can get the invocation information of the runtime method.
func Caller(skip int) (pc uintptr, file string, line int, ok bool)
skip
the parameter represents the number of skipped stack frames, which means that 0
the caller is not skipped runtime.Caller
. 1
is to go up one level, indicating the caller's caller.
The log log is used in the package 2
, which means that we call in the source code log.Print
, and the log.Fatal
log.Panic
callers of these functions.
In the main
case of a function call log.Println
, main->log.Println->*Logger.Output->runtime.Caller
This is a method call stack, so at this point, the value of skip represents:
0
Represents *Logger.Output
runtime.Caller
The source code file and line number that is called in
1
Represents log.Println
*Logger.Output
The source code file and line number that is called in
2
Represents main
log.Println
The source code file and line number that is called in
So that's log
why the value of this bag skip
is always 2
the reason.
Customize your own logs
Through the above source analysis, we know that the root of the log is a logger Logger
, so we customize their own log, in fact, is to create a different Logger
.
var ( Info *log.Logger Warning *log.Logger Error * log.Logger)func init(){ errFile,err:=os.OpenFile("errors.log",os.O_CREATE|os.O_WRONLY|os.O_APPEND,0666) if err!=nil{ log.Fatalln("打开日志文件失败:",err) } Info = log.New(os.Stdout,"Info:",log.Ldate | log.Ltime | log.Lshortfile) Warning = log.New(os.Stdout,"Warning:",log.Ldate | log.Ltime | log.Lshortfile) Error = log.New(io.MultiWriter(os.Stderr,errFile),"Error:",log.Ldate | log.Ltime | log.Lshortfile)}func main() { Info.Println("飞雪无情的博客:","http://www.flysnow.org") Warning.Printf("飞雪无情的公众号:%s\n","flysnow_org") Error.Println("欢迎关注留言")}
We defined three different logger based on the log level, respectively, for Info
the Warning
Error
output of different levels of logging. These three types of loggers are log.New
created using functions.
When the logger is created here, Info
and Warning
both are normal, Error
there are multiple destination outputs, where you can simultaneously output the error log to the os.Stderr
file we created errors.log
.
io.MultiWriter
The function can wrap multiple io.Writer
for one io.Writer
, so that we can achieve the same io.Writer
purpose for multiple output logs simultaneously.
io.MultiWriter
Implementation is also simple, define a type implementation io.Writer
, and then loop through the implemented method Write
to invoke the multiple interfaces to be wrapped Writer
Write
.
func (t *multiWriter) Write(p []byte) (n int, err error) { for _, w := range t.writers { n, err = w.Write(p) if err != nil { return } if n != len(p) { err = ErrShortWrite return } } return len(p), nil}
Here we define a number of logger to distinguish between different log levels, the use of more trouble, in this case, you can use the third-party log framework, you can also customize the wrapper definition, directly through different levels of methods to record different levels of the log, you can set the level of logging and so on.
Each Golang package requires only one of the best article series
Http://www.nextblockchain.top/books/golangpackage/summary