路漫漫其修远兮
吾将上下而求索

go学习:log记录

在我们开发程序后,如果有一些问题需要对程序进行调试的时候,日志是必不可少的,这是我们分析程序问题常用的手段。

日志使用

日志分析,就是根据输出的日志信息,分析挖掘可能的问题,我们使用fmt.Println系列函数也可以达到目的,因为它们也可以把我们需要的信息输出到终端或者其他文件中。不过fmt.Println系列函数输出的系统比较简单,比如没有时间,也没有源代码的行数等,对于我们排查问题,缺少了很多信息。

对此,Go语言为我们提供了标准的log包,来跟踪日志的记录。下面我们看看日志包log的使用。使用非常简单,函数名字和用法也和fmt包很相似,它的输出默认带了时间戳。这样我们很清晰的就知道了,记录这些日志的时间,这对我们排查问题,非常有用。

func main() {
	log.Println("my blog:","http://andblog.cn")
	log.Printf("my blog:%s\n","http://andblog.cn")
}

[root@master ~]# go run a.go
2018/01/07 17:48:43 my blog: http://andblog.cn
2018/01/07 17:48:43 my blog:http://andblog.cn

有了时间了,我们还想要更多的信息,必然发生的源代码行号等,对此日志包log 为我们提供了可定制化的配制,让我们可以自己定制日志的抬头信息。

func init(){
	log.SetFlags(log.Ldate|log.Lshortfile)
}

我们使用init函数,这个函数在main函数执行之前就可以初始化,可以帮我们做一些配置,这里我们自定义日志的抬头信息为时间+文件名+源代码所在行号。也就是log.Ldate|log.Lshortfile,中间是一个位运算符|,然后通过函数log.SetFlags进行设置。现在我们再运行下看看输出的日志。

[root@master ~]# go run a.go
2018/01/07 a.go:25: my blog: http://andblog.cn
2018/01/07 a.go:26: my blog:http://andblog.cn

我们看看log包为我们提供了那些可以定义的选项常量。

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提供的标准抬头信息
)

这是log包定义的一些抬头信息,有日期、时间、毫秒时间、绝对路径和行号、文件名和行号等,在上面都有注释说明,这里需要注意的是:如果设置了Lmicroseconds,那么Ltime就不生效了;设置了Lshortfile, Llongfile也不会生效。LUTC比较特殊,如果我们配置了时间标签,那么如果设置了LUTC的话,就会把输出的日期时间转为0时区的日期时间显示。

我们大部分情况下,都有很多业务,每个业务都需要记录日志,那么有没有办法,能区分这些业务呢?这样我们在查找日志的时候,就方便多了。对于这种情况,Go语言也帮我们考虑到了,这就是设置日志的前缀,比如一个用户中心系统的日志,我们可以这么设置。

func init(){
	log.SetPrefix("【UserCenter】")
	log.SetFlags(log.LstdFlags | log.Lshortfile |log.LUTC)
}

通过log.SetPrefix可以指定输出日志的前缀,这里我们指定为【UserCenter】,然后就可以看到日志的打印输出已经清晰的标记出我们的这些日志是属于哪些业务的啦。

[root@master ~]# go run a.go
【UserCenter】2018/01/07 a.go:25: my blog: http://andblog.cn
【UserCenter】2018/01/07 a.go:26: my blog:http://andblog.cn

log包除了有Print系列的函数,还有Fatal以及Panic系列的函数,其中Fatal表示程序遇到了致命的错误,需要退出,这时候使用Fatal记录日志后,然后程序退出,也就是说Fatal相当于先调用Print打印日志,然后再调用os.Exit(1)退出程序。

同理Panic系列的函数也一样,表示先使用Print记录日志,然后调用panic()函数抛出一个恐慌,这时候除非使用recover()函数,否则程序就会打印错误堆栈信息,然后程序终止。

这里贴下这几个系列函数的源代码,更好理解。

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)
}

定制自己的日志

通过上面的源码分析,我们知道日志记录的根本就在于一个日志记录器Logger,所以我们定制自己的日志,其实就是创建不同的Logger。

package main

import (
	"os"
	"log"
	"io"
)

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("my blog:","http://andblog.cn")
	Warning.Printf("my blog:%s\n","http://andblog.cn")
	Error.Println("this is a error")
}

执行结果

[root@master ~]# go run a.go
Info   :2018/01/07 17:57:56 a.go:24: my blog: http://andblog.cn
Warning:2018/01/07 17:57:56 a.go:25: my blog:http://andblog.cn
Error  :2018/01/07 17:57:56 a.go:26: this is a error

[root@master ~]#cat errors.log 
Error  :2018/01/07 17:59:52 a.go:26: this is a error

我们根据日志级别定义了三种不同的Logger,分别为Info,Warning,Error,用于不同级别日志的输出。这三种日志记录器都是使用log.New函数进行创建。

这里创建Logger的时候,Info和Warning都比较正常,Error这里采用了多个目的地输出,这里可以同时把错误日志输出到os.Stderr以及我们创建的errors.log文件中。

io.MultiWriter函数可以包装多个io.Writer为一个io.Writer,这样我们就可以达到同时对多个io.Writer输出日志的目的。

io.MultiWriter的实现也很简单,定义一个类型实现io.Writer,然后在实现的Write方法里循环调用要包装的多个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
}

这里我们通过定义了多个Logger来区分不同的日志级别,使用比较麻烦,针对这种情况,可以使用第三方的log框架,也可以自定包装定义,直接通过不同级别的方法来记录不同级别的日志,还可以设置记录日志的级别等。

设置自定义头部

下面是一个示例

package main
import (
    "log"
    "os"
)
func main(){
    // 定义一个文件
    fileName := "ll.log"
    logFile,err  := os.Create(fileName)
    defer logFile.Close()
    if err != nil {
        log.Fatalln("open file error !")
    }
    // 创建一个日志对象
    debugLog := log.New(logFile,"[Debug]",log.LstdFlags)
    debugLog.Println("A debug message here")
    //配置一个日志格式的前缀
    debugLog.SetPrefix("[Info]")
    debugLog.Println("A Info Message here ")
}

执行结果

[root@master ~]#cat ll.log 
[Debug]2018/01/07 16:30:40 A debug message here
[Info]2018/01/07 16:30:40 A Info Message here

下面文档中还有一种更高级的封装,这里不进行演示

参考文档:

http://www.flysnow.org/2017/05/06/go-in-action-go-log.html

http://xiaorui.cc/?p=2972

未经允许不得转载:江哥架构师笔记 » go学习:log记录

分享到:更多 ()

评论 抢沙发

评论前必须登录!