在程序中,我们日常需要记录日志,来了解程序的工作状态
为此,标准库提供了log包,进行日志的跟踪记录,传统的CLI程序写到了名为stdout的设备上
作为标准输出
但是我们还需要记录程序的执行细节,为此UNIX还提供了stderr的设备,方便作为日志的记录
这样我们可以分别配置两者的记录,
如果程序没有输出,还可以将一般的日志信息输出到stdout,错误或者警告信息写到stderr
那么在Go中,管理对应输出的,就是log包
我们从log最基础的功能开始,学习如何创建定制日志记录器,记录日志的目的是追踪程序在什么位置做了什么,这就需要通过配置在每个日志项上写的一些嘻嘻
一般的日志记录如下
包含了前缀 时间戳 日志具体由哪个源文件记录的,源文件记录日志所在行,最后是日志消息
这就需要先配置log包,从而完成这种日志输出项
//首先是初始化执行函数,在main函数执行之前
func init() {
//日志设置前缀,前缀建议全大写
log.SetPrefix(“TRACE: “)
//设置日志要显示的信息,有日期,时间,路径和行号,除此外的可选项有
log.SetFlags(log.Ldate | log.Lmicroseconds | log.Llongfile)
}
func main() {
//Println写到标准日志记录
log.Println(“message”)
// Fatalln调用println.然后调用os.exit(1)
log.Fatalln(“fatal message”)
// Panicln 调用println()之后,调用panic()
log.Panicln(“panic message”)
}
上面的代码中,首先我们配置了log的配置
上面的配置中我们使用三个常量
常量的代码如下
const (
Ldate = 1 << iota // the date in the local time zone: 2009/01/23
Ltime // the time in the local time zone: 01:23:23
Lmicroseconds // microsecond resolution: 01:23:23.123123. assumes Ltime.
Llongfile // full file name and line number: /a/b/c/d.go:23
Lshortfile // final file name element and line number: d.go:23. overrides Llongfile
LUTC // if Ldate or Ltime is set, use UTC rather than the local time zone
Lmsgprefix // move the “prefix” from the beginning of the line to before the message
LstdFlags = Ldate | Ltime // initial values for the standard logger
)
上面的常量中,有一个关键字
iota在常量声明区有一个作用,就是iota可以为每个常量复制相同的表达式直到声明区结束
iota同时会设置自己的初始值为0,然后每次处理常量后,自增1,那么下面的关键字就是
上面的常量表达式,就是每次都是右移一位,计算后的结果如上
然后我们实际使用的时候,就是如下
利用最后数来配置对应的日志输出信息
然后是main函数
里面是如何写消息的
里面有三个函数,Println,Fatalln和Panicln来写日志,函数中有可以格式化消息的版本,只需要使用f替换ln就可以了
Fata;系列函数用于写日志消息,然后使用os.Exit(1)终止函数
Panic系列用来写日志消息,然后触发一个panic,除非程序执行recover函数,不然就是进行终止
log还保证了多goroutine安全的,可以同时的调用同一个日志记录器这个函数,不会有彼此的些冲突
那么,接下来的流程,就是不同等级的日志写到不同的目的地
为了定制这样的一个日志记录器,我们需要创建一个Logger类型的值,给每个日志配置一个单独的目的地和独立的前缀和标志,我们来看下面的代码
// 为了创建定制的日志记录器
package main
import (
“io”
“io/ioutil”
“log”
“os”
)
var (
Trace *log.Logger // 所有
Info *log.Logger // 重要信息
Warning *log.Logger // 告警
Error *log.Logger // 错误
)
func init() {
file, err := os.OpenFile(“errors.txt”,
os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666)
if err != nil {
log.Fatalln(“Failed to open error log file:”, err)
}
Trace = log.New(ioutil.Discard,
“TRACE: “,
log.Ldate|log.Ltime|log.Lshortfile)
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(file, os.Stderr),
“ERROR: “,
log.Ldate|log.Ltime|log.Lshortfile)
}
func main() {
Trace.Println(“I have something standard to say”)
Info.Println(“Special Information”)
Warning.Println(“There is something you need to know about”)
Error.Println(“Something has failed”)
}
我们首先创建了4种不同的Logger类型的指针,分别命名为了Trace Info Warning Error
我们看下变量名很简短,,但是含义是有的
init函数代码创建了每个Logger类型的值
利用的是Logger类中端的New函数
func New(out io.Writer, prefix string, flag int) *Logger {
return &Logger{out: out, prefix: prefix, flag: flag}
}
分别传入了日志的目的地地址.要求是实现了io.Wirter的目的地,
然后是prefix的前缀,最后则是日志的标志
我们在创建Trace的时候,使用了
ioutil.Discard
这是个独特的属性,对于Linux,是写到了devNull中,因此对其写入不会有任何的动作,但是会成功返回
然后info和warning级别都使用了stdout作为日志的输出\
当然,除了stdout 还有 stdin stderr这三个标准的输入输出级别
最后是关于Error的级别,利用了一个特殊的函数,io包中的MultiWriter函数
func MultiWriter(writers …Writer) Writer {
allWriters := make([]Writer, 0, len(writers))
for _, w := range writers {
if mw, ok := w.(*multiWriter); ok {
allWriters = append(allWriters, mw.writers…)
} else {
allWriters = append(allWriters, w)
}
}
return &multiWriter{allWriters}
}
传入多个实现了io.Writer接口的值,返回一个io.Wirter,这样在返回的Writer中写入的时候,就会给所有的Writer做一个输出,我们就可以同时写到文件和stderr中了
总结一下
log包的实现,基于对记录日志的需求长时间的实践和记录得出的,标准包的log就给出了记录日志的所有功能,我们也推荐使用这个包