Go语言从入门到精通 -【web项目实战篇】- Log日志
本节核心内容
- 介绍log包的核心数据结构
- 介绍log包的核心方法
- 演示了开发中的使用demo
本小节视频教程和代码:百度网盘
可先下载视频和源码到本地,边看视频边结合源码理解后续内容,边学边练。
Log包简介
Golang的log包短小精悍,可以非常轻松的实现日志打印转存功能。并且log支持并发操作(即协程安全-相对于JAVA中的线程安全而言),本小节将对log包的log.go文件以剖析的方式领大家来深入了解一下log,最后再给出实际开发中的应用实例,在学习之前,我们先来看一下log.go文件有哪些内容:
是不是看起来很多?但是不要怕,其实这里面有一般的方法和函数是重复的,只是log自带了一个logger对象,它为这个对象建立了一系列重复的方法,所以我们只需要学一半内容中的精华就可以了。
Logger结构体
其结构定义如下:
type Logger struct {
mu sync.Mutex // 互斥锁
prefix string // 日志行前缀
flag int // 日志打印格式标志,用于指定每行日志的打印格式
out io.Writer // 用于指定日志输出位置,理论上可以是任何地方,只要实现了io.Writer接口就行
buf []byte // 日志内容
}
主要方法
对于log.go文件,我们从创建日志对象到输出日志的流程可以对它进行了下分类,可大体分为下面这些方法
创建logger对象
var std = New(os.Stderr, "", LstdFlags) // 日志中只使用的flag为LstdFlags,即只输出日期
虽然log包已默认为我们提供了一个日志对象,并封装了包级别的常用函数,并且该对象可以将日志信息输出到标准输出设备中(开箱即用),但我们还是需要使用New()
函数来创建日志对象,这是因为我们往往需要自己定义日志对象的数据方式和属性。
创建对象方法为:
func New(out io.Writer, prefix string, flag int) *Logger {
return &Logger{out: out, prefix: prefix, flag: flag}
}
该函数一共有三个参数:
- 输出位置out,是一个io.Writer对象,该对象可以是一个文件也可以是实现了该接口的对象。通常我们可以用这个来指定日志输出到哪个文件。
- prefix 我们在前面已经看到,就是在日志内容前面的东西。我们可以将其置为 “[Info]” 、 “[Warning]”等来帮助区分日志级别。
- flags 是一个选项,显示日志开头的东西,可选的值有:
// 这些标志定义了日志记录器生成的每个日志条目的前缀文本。
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 // 使用标准的UTC时间格式
LstdFlags = Ldate | Ltime // 默认为日期和时间
)
设置属性
log包为我们提供了可设置日志记录器属性的方法,主要作用是可根据我们事先自定义添加的属性,在日志输出时,按照我们想要的格式进行日志输出,源码为:
// SetFlags sets the output flags for the logger.
func (l *Logger) SetFlags(flag int) {
l.mu.Lock()
defer l.mu.Unlock()
l.flag = flag
}
示例代码:
package main
import (
"log"
)
func main() {
Ldate()
Ltime()
Lmicroseconds()
Llongfile()
Lshortfile()
LUTC()
Ldefault()
}
func Ldate() {
log.SetFlags(log.Ldate)
log.Println("这是输出日期格式\n")
}
func Ltime() {
log.SetFlags(log.Ltime)
log.Println("这是输出时间格式\n")
}
func Lmicroseconds() {
log.SetFlags(log.Lmicroseconds)
log.Println("这是输出微秒格式\n")
}
func Llongfile() {
log.SetFlags(log.Llongfile)
log.Println("这是输出路径+文件名+行号格式\n")
}
func Lshortfile() {
log.SetFlags(log.Lshortfile)
log.Println("这是输出文件名+行号格式\n")
}
func LUTC() {
log.SetFlags(log.Ldate | log.Ltime | log.Lmicroseconds | log.LUTC)
log.Println("这是输出 使用标准的UTC时间格式 格式\n")
}
func Ldefault() {
log.Println("这是默认的格式\n")
}
运行结果:
2019/01/22 这是输出日期格式
15:29:14 这是输出时间格式
15:29:14.088798 这是输出微秒格式
D:/GoPath/src/mylog/main.go:35: 这是输出路径+文件名+行号格式
main.go:40: 这是输出文件名+行号格式
2019/01/22 07:29:14.088798 这是输出 使用标准的UTC时间格式 格式
2019/01/22 07:29:14.088798 这是默认的格式
日志输出
Output方法
func (l *Logger) Output(calldepth int, s string) error ;
Output是真正负责日志打印的方法,其它级别的打印方法都将会调用它,该函数主要有两个参数:
-
s
为要打印的文本,会在Logger记录器的指定前缀之后打印输出。如果s的最后一个字符不是换行符,则追加换行符。 -
calldepth
用于恢复PC,并提供了通用性,不过目前在所有预定义的路径上它都是2。
Print接口
log模块主要提供了3类接口。分别是 Print
、Panic
、Fatal
,对每一类接口其提供了3种调用方式,分别是 “Xxxx 、 Xxxxln 、Xxxxf”,基本和fmt中的相关函数类似,Print
的源码如下:
func (l *Logger) Println(v ...interface{}) {
l.Output(2, fmt.Sprintln(v...))
}
使用场景:一般信息打印方法,相当于JAVA中log的info级别
下面是一个Print的示例:
package main
import (
"log"
)
func main(){
arr := []int {2,3}
log.Print("Print array ",arr,"\n")
log.Println("Println array",arr)
log.Printf("Printf array with item [%d,%d]\n",arr[0],arr[1])
}
会得到如下结果:
2016/12/15 19:46:19 Print array [2 3]
2016/12/15 19:46:19 Println array [2 3]
2016/12/15 19:46:19 Printf array with item [2,3]
Panic接口
使用场景:业务异常时使用的方法,该方法会抛出异常,调用方可以用recover捕获,相当于JAVA的ERROR级别(JAVA不会自动抛异常)
func (l *Logger) Panicln(v ...interface{}) {
s := fmt.Sprintln(v...)
l.Output(2, s)
panic(s) //通过panic抛出异常,只有上层业务没捕获异常时,程序才会异常中止并退出,
}
对于log.Panic接口,该函数把日志内容刷到标准错误后调用 panic 函数,下面是一个Panic的示例:
package main
import (
"fmt"
"log"
)
func test_deferpanic(){
defer func() {
fmt.Println("--first--")
if err := recover(); err != nil {
fmt.Println(err)
}
}()
log.Panicln("test for defer Panic")
defer func() {
fmt.Println("--second--")
}()
}
func main() {
test_deferpanic()
}
会得到如下结果:
2019/01/19 16:28:35 test for defer Panic
--first--
test for defer Panic
可以看到首先输出了“test for defer Panic”,然后第一个defer函数被调用了并输出了“–first–”,但是第二个defer 函数并没有输出,可见在Panic之后声明的defer是不会执行的。
Fatal接口
// 调用该方法会中止应用程序并直接退出
func Fatalln(v ...interface{}) {
std.Output(2, fmt.Sprintln(v...))
os.Exit(1)
}
对于 log.Fatal 接口,会先将日志内容打印到标准输出,接着调用系统的 os.exit(1) 接口,退出程序并返回状态 1 。但是有一点需要注意,由于是直接调用系统接口退出,defer函数不会被调用,下面是一个Fatal的示例:
package main
import (
"fmt"
"log"
)
func test_deferfatal(){
defer func() {
fmt.Println("--first--")
}()
log.Fatalln("test for defer Fatal")
}
func main() {
test_deferfatal()
}
会得到如下结果:
2019/01/19 16:23:55 test for defer Fatal
可以看到并没有调用defer 函数。
log实战
下面将给出一段可用于实际开发中的示例代码,将日志输出到日志文件中
package main
import (
"fmt"
"log"
"os"
"time"
)
func logInfo() *log.Logger {
//创建io对象,日志的格式为当前时间.log;2006-01-02 15:04:05据说是golang的诞生时间,固定写法
file := "./" + time.Now().Format("2006-01-02") + ".log"
logFile, err := os.OpenFile(file, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0766)
if nil != err {
panic(err)
}
//创建一个Logger:参数1:日志写入目的地 ; 参数2:每条日志的前缀 ;参数3:日志属性
return log.New(logFile, "自定义的前缀",log.Lshortfile)
}
var mylooger *log.Logger
func main() {
//调用日志初始化方法
mylooger = logInfo()
//Prefix返回前缀,Flags返回Logger的输出选项属性值
fmt.Printf("创建时前缀为:%s\n创建时输出项属性值为:%d\n",mylooger.Prefix(),mylooger.Flags())
//SetFlags 重新设置输出选项
mylooger.SetFlags(log.Ldate | log.Ltime | log.Lshortfile)
//重新设置输出前缀
mylooger.SetPrefix("test_")
//获取修改后的前缀和输出项属性值
fmt.Printf("修改后前缀为:%s\n修改后输出项属性值为:%d\n",mylooger.Prefix(),mylooger.Flags())
//输出一条日志
mylooger.Output(2, "使用Output进行日志输出")
//格式化输出日志
mylooger.Printf("我是%v方法在%d行内容为:%s","Printf",40,"其实我底层是以fmt.Printf的方式处理的,相当于Java里的Info级别")
//开启这个注释,下面代码就不会继续走,并且程序停止
//mylooger.Fatal("我是Fatal方法,我会停止程序,但不会抛出异常")
//调用业务层代码
serviceCode()
mylooger.Printf("业务代码里的Panicln不会影响到我,因为他已经被处理干掉了,程序目前正常")
}
func serviceCode() {
defer func() {
if r := recover(); r != nil {
//用以捕捉Panicln抛出的异常
fmt.Printf("使用recover()捕获到的错误:%s\n", r)
}
}()
// 模拟错误业务逻辑,使用抛出异常和捕捉的方式记录日志
if 1 == 1 {
mylooger.Panicln("我是Panicln方法,我会抛异常信息的,相当于Error级别")
}
}
小结
本小节主要介绍了GoLang自带的log包,通过学习log的包log.go文件的结构体,主要函数来了解它的实现原理以及如何创建一个logger对象来进行日志输出。