Go语言学习笔记(0)--- 日志

go 刘宇帅 6年前 阅读量: 1269

我们在应用调试或者线上业务中经常会用到日志功能,而 Go 语言内置了 log 模块。

log 模块的使用

先来看下 log 模块的基本使用:

package main

import (
    "log"
)

func main() {
    log.Println("hello log.Println")
    log.Printf("hello %s", "log.Printf")
}

我们运行一下可以看到如下输出:

> $ go run main.go
2018/10/29 10:45:39 hello log.Println
2018/10/29 10:45:39 hello log.Printf

其实我们知道 fmt 包提供的函数有同样的输出函数,但是我们根据上面的输出可以看到 log 包输出的日志包括了日期和时间,这些输出有时候对于我们排查特定问题有一定的帮助作用。
我们可以自己设定输出的前缀,我们可以看下 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
    LstdFlags     = Ldate | Ltime // initial values for the standard logger
)

var std = New(os.Stderr, "", LstdFlags)

我们可以看到 log 包支持的可选输出项包括 Ldate、Ltime、Lmicroseconds、Llongfile、Lshortfile、LUTC,而 log 包默认的输出使用的是 LstdFlags = Ldate|Ltime。log 包提供的 SetFlags 函数可以让我们自定义输出选项。我们这里修改输出选项:

package main

import (
    "log"
)

func main() {
    log.SetFlags(log.Ldate | log.Ltime | log.Lmicroseconds | log.Llongfile)
    log.Println("hello log.Println")
    log.Printf("hello %s", "log.Printf")
}

我们可以看到如下输出:

> $ go run main.go                                                                                                  [10:56:22]
2018/10/29 10:58:04.244899 /Users/liushuai/Documents/goProject/src/github.com/yushuailiu/easyGolang/log/main.go:9: hello log.Println
2018/10/29 10:58:04.245070 /Users/liushuai/Documents/goProject/src/github.com/yushuailiu/easyGolang/log/main.go:10: hello log.Printf

log 包的输出选项有两个选项需要注意,第一个就是 Lshortfile,如果我们设置了该选项那么它会覆盖 Llongfile。第二个是 LUTC,如果我们设置了该选项输出时间将会使用 UTC 时间而不是本地时间。
log 包还提供了一个 SetPrefix 函数用来设置日志的输出前缀,我们可以用日志前缀来区分不同业务或不同类型的日志输出。

package main

import (
    "log"
)

func main() {
    log.SetPrefix("[User] ")
    log.Println("hello log.Println")
    log.Printf("hello %s", "log.Printf")
}

输出

> $ go run main.go
[User] 2018/10/29 11:04:56 hello log.Println
[User] 2018/10/29 11:04:56 hello log.Printf

这样我们就可以很容易拿到用户相关的日志了。
log 模块还提供所有输出函数分为三种:第一种包括 Print Printf Println,这一种只是简单地把信息输出,不会做其他操作。第二种包括 Fatal Fatalf Fatalln,这一种会在把信息输出之后并执行 os.Exit(1) 退出程序执行。最后一种包括Panic Panicf Panicln ,这种会在信息输出之后执行 panic(s)。三种函数可以被用到不同级别的信息输出中。部分函数源码展示:

func Fatalf(format string, v ...interface{}) {
    std.Output(2, fmt.Sprintf(format, v...))
    os.Exit(1)
}

func Panic(v ...interface{}) {
    s := fmt.Sprint(v...)
    std.Output(2, s)
    panic(s)
}

log模块源码分析

log 模块的日志输出最关键的就是 Logger 这个 struct

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
}

各个字段作用

  1. mu 用于保证多 goroutine 情况下的安全
  2. prefix 就是我们上面用到的日志前缀
  3. flag 用来保存上面用到的日志选项
  4. out 日志输出目的地指针
  5. buf 用来保存日志拼接,最早写入 out

我们的所有日志输出都是调用 Logger 的实例输出,而我们上面直接调用 log.Println 则是 log 包提供的默认 Logger。相关代码如下:

var std = New(os.Stderr, "", LstdFlags)

func New(out io.Writer, prefix string, flag int) *Logger {
    return &Logger{out: out, prefix: prefix, flag: flag}
}

func Println(v ...interface{}) {
    std.Output(2, fmt.Sprintln(v...))
}

可以看到 log 包给我们提供的默认 Logger 是 std,std 的默认输出是 os.Stderr,前缀为空,日志选项是 LstdFlags。
而 log 包最重要的函数就是 Output 函数:

func (l *Logger) Output(calldepth int, s string) error {
    // 获得当前时间
    now := time.Now() 
    var file string
    var line int
    l.mu.Lock()
    defer l.mu.Unlock()
    if l.flag&(Lshortfile|Llongfile) != 0 {
        // 去获得调用堆栈的时候先释放锁
        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')
    }
    // 输出日志到 out
    _, err := l.out.Write(l.buf)
    return err
}

自定义日志输出

我们看到 std 的定义,可以想到我们也可以自己定义日志。

package main

import (
    "log"
    "os"
)

func main() {
    errorLogFile ,err:=os.OpenFile("/tmp/error.log",os.O_CREATE|os.O_WRONLY|os.O_APPEND,0666)
    if err != nil {
        panic(err)
    }
    stdoutLog := log.New(os.Stdout, "[User]", log.LstdFlags)
    errLog := log.New(errorLogFile, "Message", log.Ldate|log.Ltime|log.Lshortfile)

    stdoutLog.Println("stdout log test")
    errLog.Println("file log test")
}

查看相关输出

> $ go run main.go
[User]2018/10/29 11:42:17 stdout log test
> $ cat /tmp/error.log
Message2018/10/29 11:42:17 main.go:17: file log test

提示

功能待开通!


暂无评论~