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
}
各个字段作用
- mu 用于保证多 goroutine 情况下的安全
- prefix 就是我们上面用到的日志前缀
- flag 用来保存上面用到的日志选项
- out 日志输出目的地指针
- 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