Go语言从入门到精通 -【web项目实战篇】- Log日志

本节核心内容

  • 介绍log包的核心数据结构
  • 介绍log包的核心方法
  • 演示了开发中的使用demo

本小节视频教程和代码:百度网盘

可先下载视频和源码到本地,边看视频边结合源码理解后续内容,边学边练。

Log包简介

Golang的log包短小精悍,可以非常轻松的实现日志打印转存功能。并且log支持并发操作(即协程安全-相对于JAVA中的线程安全而言),本小节将对log包的log.go文件以剖析的方式领大家来深入了解一下log,最后再给出实际开发中的应用实例,在学习之前,我们先来看一下log.go文件有哪些内容:

image

是不是看起来很多?但是不要怕,其实这里面有一般的方法和函数是重复的,只是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}
}

该函数一共有三个参数:

  1. 输出位置out,是一个io.Writer对象,该对象可以是一个文件也可以是实现了该接口的对象。通常我们可以用这个来指定日志输出到哪个文件。
  2. prefix 我们在前面已经看到,就是在日志内容前面的东西。我们可以将其置为 "[Info]" 、 "[Warning]"等来帮助区分日志级别。
  3. 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类接口。分别是 PrintPanicFatal ,对每一类接口其提供了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对象来进行日志输出。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章