刚开始我学日志就随便看看,一直认为是很简单的东西,没必要特意花时间去学,对日志的理解就是打印错误,方便排查。这么理解也没问题,也就是这么点作用,但是我们应该学会如何去配置以及打印日志可以让自己更直观的理解错误,以及错误的地方,仅仅是个人的习惯,参考为主。
log库
简介
Golang 自带的标准库 log 包提供了基本的日志记录功能,足以满足大多数日常使用需求。它简单易用,是学习和使用日志的一个很好的起点。
log打印日志方法
// Print calls Output to print to the standard logger.
// Arguments are handled in the manner of fmt.Print.
// Print调用Output以打印到标准记录器。
// 以fmt.Print的方式处理参数。
func Print(v ...any) {
if atomic.LoadInt32(&std.isDiscard) != 0 {
return
}
std.Output(2, fmt.Sprint(v...))
}
// Printf calls Output to print to the standard logger.
// Arguments are handled in the manner of fmt.Printf.
// Print调用Output以打印到标准记录器。
// 以fmt.Printf的方式处理参数。
func Printf(format string, v ...any) {
if atomic.LoadInt32(&std.isDiscard) != 0 {
return
}
std.Output(2, fmt.Sprintf(format, v...))
}
// Println calls Output to print to the standard logger.
// Arguments are handled in the manner of fmt.Println.
// Print调用Output以打印到标准记录器。
// 以fmt.Println的方式处理参数。
func Println(v ...any) {
if atomic.LoadInt32(&std.isDiscard) != 0 {
return
}
std.Output(2, fmt.Sprintln(v...))
}
// Fatal is equivalent to Print() followed by a call to os.Exit(1).
// Fatal相当于Print(),然后调用os.Exit(1)。
func Fatal(v ...any) {
std.Output(2, fmt.Sprint(v...))
os.Exit(1)
}
// Fatalf is equivalent to Printf() followed by a call to os.Exit(1).
// Fatal相当于Printf(),然后调用os.Exit(1)。
func Fatalf(format string, v ...any) {
std.Output(2, fmt.Sprintf(format, v...))
os.Exit(1)
}
// Fatalln is equivalent to Println() followed by a call to os.Exit(1).
// Fatal相当于Println(),然后调用os.Exit(1)。
func Fatalln(v ...any) {
std.Output(2, fmt.Sprintln(v...))
os.Exit(1)
}
// Panic is equivalent to Print() followed by a call to panic().
// Panic相当于Print()之后再调用Panic()。
func Panic(v ...any) {
s := fmt.Sprint(v...)
std.Output(2, s)
panic(s)
}
// Panicf is equivalent to Printf() followed by a call to panic().
// Panic相当于Printf()之后再调用Panic()。
func Panicf(format string, v ...any) {
s := fmt.Sprintf(format, v...)
std.Output(2, s)
panic(s)
}
// Panicln is equivalent to Println() followed by a call to panic().
// Panic相当于Println()之后再调用Panic()。
func Panicln(v ...any) {
s := fmt.Sprintln(v...)
std.Output(2, s)
panic(s)
}
log结构体
// A Logger represents an active logging object that generates lines of
// output to an io.Writer. Each logging operation makes a single call to
// the Writer's Write method. A Logger can be used simultaneously from
// multiple goroutines; it guarantees to serialize access to the Writer.
type Logger struct {
mu sync.Mutex // ensures atomic writes; protects the following fields
prefix string // prefix on each line to identify the logger (but see Lmsgprefix)
flag int // properties
out io.Writer // destination for output
buf []byte // for accumulating text to write
isDiscard int32 // atomic boolean: whether out == io.Discard
}
简单说明一下具体用法
func main() {
log.SetPrefix("我这个方法是测试log:")
log.Printf("Starting")
log.Fatal("err")
log.Panic("panic")
}
//打印结果
我这个方法是测试log:2024/06/20 14:54:28 Starting
我这个方法是测试log:2024/06/20 14:54:28 err
---------------------------------------------------------------------
func main() {
log.SetPrefix("我这个方法是测试log:")
log.Printf("Starting")
log.Panic("panic")
log.Fatal("err")
}
//打印结果
我这个方法是测试log:2024/06/20 14:56:12 Starting
我这个方法是测试log:2024/06/20 14:56:12 panic
panic: panic
goroutine 1 [running]:
log.Panic({0xc0000c9f60?, 0x10ae3ae?, 0x0?})
/Users/yyl/Desktop/www/go/env/go1.19/src/log/log.go:388 +0x65
main.main()
/Users/yyl/Desktop/www/go/service/demo/log/main.go:8 +0x78
-
如果调用SetPrefix方法,则这个方法所有调用log的前缀都会有这个信息,我觉得很好用,这样我就知道那个方法出现的日志了。
-
无论Panic或者Fatal哪一个被调用,这个方法都会结束。
-
细心的小伙伴已经发现了,每一条日志都会打印日期和时间,这是因为log的默认配置是打印日期和时间。默认有8个选项
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 Lmsgprefix // move the "prefix" from the beginning of the line to before the message LstdFlags = Ldate | Ltime // initial values for the standard logger )
使用方法
func main() {
log.SetPrefix("我这个方法是测试log:")
log.SetFlags(log.Lshortfile | log.Ldate | log.Ltime)
log.Printf("Starting")
}
//打印结果(输出顺序并不会和我们定义的一样,这是log的工作机制,感兴趣可以深入底层看一下。)
我这个方法是测试log:2024/06/20 15:06:05 main.go:8: Starting
学完基础的log,可以学一下高级的,比如说zap,logours,zerolog等都是对log的封装和扩展。
zap日志
从实现上来说,Zap 是在标准库 log 包的基础上进行了封装和扩展。Zap 内部会创建一个 *log.Logger 对象,并在此基础上实现了自己的日志记录逻辑。这样做可以利用标准库 log 包已有的基础设施,同时又能够提供更丰富的功能。
日志级别
//最低级,记录一条调试日志。
logger.Debug("Hello from Zap logger Debug!")
//默认级别,记录一条普通日志。
logger.Info("Hello from Zap logger Info!")
//记录一条警告日志。
logger.Warn("Hello from Zap logger Warn!")
//记录一条错误日志。
logger.Error("Hello from Zap logger Error!")
// 记录一条 fatal 级别的日志,并退出程序。
logger.Fatal("Hello from Zap logger Fatal!")
//记录一条 panic 级别的日志,并 panic。
logger.DPanic("Hello from Zap logger DPanic!")
//记录一条 panic 级别的日志,并 panic。
logger.Panic("Hello from Zap logger Panic!")
日志记录器
- zap.NewDevelopment():
- 这个函数创建一个适用于开发环境的日志记录器。
- 它会输出详细的日志信息,包括调用堆栈等,方便开发人员调试。
- 它使用的是DPanicLevel 级别的日志,当输出 DPanicLevel 级别的日志时,如果是开发环境会触发 panic。
- zap.NewExample():
- 这个函数创建一个示例用途的日志记录器。
- 它会输出一些示例格式的日志信息,用于演示或测试。
- 它使用的是 InfoLevel级别的日志,不会触发 panic。
- zap.NewProduction():
- 这个函数创建一个适用于生产环境的日志记录器。
- 它会输出简洁的日志信息,适合在实际生产环境中使用。
- 它使用的是 InfoLevel级别的日志,不会触发 panic。
使用方法
func main() {
logger := zap.Must(zap.NewProduction())
defer logger.Sync()
logger.Debug("Hello from Zap logger Debug!")
logger.Info("Hello from Zap logger Info!")
logger.Warn("Hello from Zap logger Warn!")
logger.Error("Hello from Zap logger Error!")
//logger.Fatal("Hello from Zap logger Fatal!")(这里不执行,要不然程序会直接退出,不会打印后面的)
logger.DPanic("Hello from Zap logger DPanic!")
logger.Panic("Hello from Zap logger Panic!")
}
// 打印结果
{"level":"info","ts":1718873152.804436,"caller":"log/zap.go:14","msg":"Hello from Zap logger Info!"}
{"level":"warn","ts":1718873152.80452,"caller":"log/zap.go:15","msg":"Hello from Zap logger Warn!"}
{"level":"error","ts":1718873152.804534,"caller":"log/zap.go:16","msg":"Hello from Zap logger Error!","stacktrace":"main.main\n\t/Users/yyl/Desktop/www/go/service/demo/log/zap.go:16\nruntime.main\n\t/Users/yyl/Desktop/www/go/env/go1.19/src/runtime/proc.go:250"}
{"level":"dpanic","ts":1718873152.804563,"caller":"log/zap.go:18","msg":"Hello from Zap logger DPanic!","stacktrace":"main.main\n\t/Users/yyl/Desktop/www/go/service/demo/log/zap.go:18\nruntime.main\n\t/Users/yyl/Desktop/www/go/env/go1.19/src/runtime/proc.go:250"}
{"level":"panic","ts":1718873152.804578,"caller":"log/zap.go:19","msg":"Hello from Zap logger Panic!","stacktrace":"main.main\n\t/Users/yyl/Desktop/www/go/service/demo/log/zap.go:19\nruntime.main\n\t/Users/yyl/Desktop/www/go/env/go1.19/src/runtime/proc.go:250"}
panic: Hello from Zap logger Panic!
goroutine 1 [running]:
go.uber.org/zap/zapcore.CheckWriteAction.OnWrite(0x0?, 0x10aa6e5?, {0x0?, 0x0?, 0xc0000681a0?})
/Users/yyl/go/pkg/mod/go.uber.org/zap@v1.27.0/zapcore/entry.go:196 +0x65
go.uber.org/zap/zapcore.(*CheckedEntry).Write(0xc00010f040, {0x0, 0x0, 0x0})
/Users/yyl/go/pkg/mod/go.uber.org/zap@v1.27.0/zapcore/entry.go:262 +0x3ec
go.uber.org/zap.(*Logger).Panic(0x0?, {0x11c2eb7?, 0x0?}, {0x0, 0x0, 0x0})
/Users/yyl/go/pkg/mod/go.uber.org/zap@v1.27.0/logger.go:285 +0x59
main.main()
/Users/yyl/Desktop/www/go/service/demo/log/zap.go:19 +0x125
可以看出,当使用zap.NewProduction()环境的时候,Debug不会打印,默认是Info。DPanic虽然打印了,但是没有调Panic,看到这再去理解日志记录器 就应该明白了。
配置sugerLog (也是核心部分)
package main
import (
"fmt"
"go.uber.org/zap"
"go.uber.org/zap/zapcore"
)
type LogConfig struct {
Level string `yaml:"level"`
Encoding string `yaml:"encoding"`
InitialFields map[string]string `yaml:"initialFields"`
}
func main() {
// 从配置文件中读取配置信息
config := &LogConfig{
Level: "debug",
Encoding: "console",
InitialFields: map[string]string{"app": "demo-zap"},
}
// 创建一个标准的 zap.Config
zapConfig := zap.NewProductionConfig()
// 修改一些配置项
zapConfig.Level = zap.NewAtomicLevelAt(parseLevel(config.Level))
zapConfig.Encoding = config.Encoding
// 将 config.InitialFields 转换为 map[string]interface{}
initialFields := make(map[string]interface{}, len(config.InitialFields))
for k, v := range config.InitialFields {
initialFields[k] = v
}
zapConfig.InitialFields = initialFields
// 根据配置创建 Logger 实例
logger, _ := zapConfig.Build()
defer logger.Sync()
// 创建一个 SugaredLogger
sugar := logger.Sugar()
// 打印日志
sugar.Debugf("i am debug, using %s", "Debugf")
sugar.Infof("i am info, using %s", "Infof")
sugar.Warnf("i am warn, using %s", "Warnf")
sugar.Errorf("i am error, using %s", "Errorf")
sugar.Fatalf("i am fatal, using %s", "Fatalf")
fmt.Println("can i be printed?")
}
func parseLevel(level string) zapcore.Level {
switch level {
case "debug":
return zapcore.DebugLevel
case "info":
return zapcore.InfoLevel
case "warn":
return zapcore.WarnLevel
case "error":
return zapcore.ErrorLevel
case "fatal":
return zapcore.FatalLevel
default:
return zapcore.InfoLevel
}
}
//打印结果
1.7188756604731e+09 debug log/zap.go:45 i am debug, using Debugf {"app": "demo-zap"}
1.7188756604731982e+09 info log/zap.go:46 i am info, using Infof {"app": "demo-zap"}
1.718875660473212e+09 warn log/zap.go:47 i am warn, using Warnf {"app": "demo-zap"}
1.7188756604732218e+09 error log/zap.go:48 i am error, using Errorf {"app": "demo-zap"}
main.main
/Users/yyl/Desktop/www/go/service/demo/log/zap.go:48
runtime.main
/Users/yyl/Desktop/www/go/env/go1.19/src/runtime/proc.go:250
1.718875660473251e+09 fatal log/zap.go:49 i am fatal, using Fatalf {"app": "demo-zap"}
main.main
/Users/yyl/Desktop/www/go/service/demo/log/zap.go:49
runtime.main
/Users/yyl/Desktop/www/go/env/go1.19/src/runtime/proc.go:250
进程 已完成,退出代码为 1