18.日志
18.1 自带log包
在日常项目,在出现问题之后需要排查,一种比较主要的排查方式是通过日志。所以在代码的关键地方,需要打印相应的日志。在Go语言中log包提供了简单的日志功能,其输出格式如下所示:
| 打印 | 格式化打印 | 换行打印 | 备注 |
|---|---|---|---|
| log.Print() | log.Printf() | log.Println() | 类似于fmt.Print* |
| log.Fatal() | log.Fatalf() | log.Fatalln() | 类似于fmt.Print* + os.Exit(1) |
| log.Panic() | log.Panicf() | log.Panicln() | 类似于fmt.Print*+ panic() |
一般在打印日志时,我们会定义一个Logger进行日志的打印,默认的日志定义如下所示:
type Logger struct {
outMu sync.Mutex
out io.Writer // destination for output
prefix atomic.Pointer[string] // prefix on each line to identify the logger (but see Lmsgprefix)
flag atomic.Int32 // properties
isDiscard atomic.Bool
}
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
)
var std = New(os.Stderr, "", LstdFlags)
// Default returns the standard logger used by the package-level output functions.
func Default() *Logger { return std }
上面都是使用std.Output打印日志。而std本质上是使用标准错误输出、无前缀、LstdFlags标准标记的Logger
18.1.1 使用Std打印日志
示例代码如下所示:
package main
import (
"log"
"os"
)
func main() {
// 使用默认配置打印日志
log.Print("使用log.Print打印日志\n")
log.Printf("使用log.Printf打印日志,%v", "Surpass")
log.Println("使用log.Println打印日志")
// 使用自定义Logger
lInfo := log.New(os.Stdout, "[INFO]", log.LstdFlags|log.Lmsgprefix)
lInfo.Println("使用自定义Logger打印日志,日志级别 info")
lError := log.New(os.Stderr, "[ERROR]", log.LstdFlags|log.Lmsgprefix|log.Lshortfile)
lError.Fatalln("使用自定义Logger打印日志,日志级别 error")
}
代码运行结果如下所示:
2024/12/30 23:48:43 使用log.Print打印日志
2024/12/30 23:48:43 使用log.Printf打印日志,Surpass
2024/12/30 23:48:43 使用log.Println打印日志
2024/12/30 23:48:43 [INFO]使用自定义Logger打印日志,日志级别 info
2024/12/30 23:48:43 main.go:18: [ERROR]使用自定义Logger打印日志,日志级别 error
exit status 1
18.1.2 日志写入文件
示例代码如下所示:
package main
import (
"log"
"os"
"path/filepath"
)
func main() {
curpath, _ := os.Getwd()
flag := os.O_CREATE | os.O_APPEND | os.O_WRONLY
filename := filepath.Join(curpath, "surpass.log")
f, err := os.OpenFile(filename, flag, 0)
if err != nil {
log.Panicf("打开文件%v出错:%v\n", filename, err)
}
defer f.Close()
lInfo := log.New(f, "[INFO]", log.Lshortfile|log.LstdFlags)
lInfo.Println("这是一个写入文件的日志,级别-INFO")
lError := log.New(f, "[WARNING]", log.Lshortfile|log.LstdFlags)
lError.Fatalln("这是一个写入文件的日志,级别-WARNING")
lPanic := log.New(f, "[ERROR]", log.Lshortfile|log.LstdFlags)
lPanic.Panicln("这是一个写入文件的日志,级别-ERROR")
}
代码运行结果如下所示:
[INFO]2024/12/31 00:09:37 main.go:20: 这是一个写入文件的日志,级别-INFO
[WARNING]2024/12/31 00:09:37 main.go:23: 这是一个写入文件的日志,级别-WARNING
18.2 zerolog
Go自带的log库太简单,在实际开发过程中,使用并不方便。因此出现很多第三方比较优秀的开源日志库,如下所示:
- logrus: 有日志级别、Hook机制、日志格式输出
- zap是Uber开源的高性能的日志库
- zerolog更注重开发体验、也是一款高性能的日志库,有日志级别,链式API,JSON格式化的日志记录
Github地址:https://github.com/rs/zerolog
18.2.1 安装
执行以下命令即可
go get -u github.com/rs/zerolog/log
18.2.2 默认Logger
示例代码如下所示:
package main
import (
"github.com/rs/zerolog/log"
)
func main() {
log.Print("Hello,Surpass")
}
代码运行结果如下所示:
{"level":"debug","time":"2024-12-31T23:18:14+08:00","message":"Hello,Surpass"}
默认输出格式为JSON,级别且为Debug模式
18.2.3 消息级别
zerolog对应的日志级别从高到低如下所示:
- panic (zerolog.PanicLevel, 5)
- fatal (zerolog.FatalLevel, 4)
- error (zerolog.ErrorLevel, 3)
- warn (zerolog.WarnLevel, 2)
- info (zerolog.InfoLevel, 1)
- debug (zerolog.DebugLevel, 0)
- trace (zerolog.TraceLevel, -1)
查看源码定义如下所示:
const (
// DebugLevel defines debug log level.
DebugLevel Level = iota
// InfoLevel defines info log level.
InfoLevel
// WarnLevel defines warn log level.
WarnLevel
// ErrorLevel defines error log level.
ErrorLevel
// FatalLevel defines fatal log level.
FatalLevel
// PanicLevel defines panic log level.
PanicLevel
// NoLevel defines an absent log level.
NoLevel
// Disabled disables the logger.
Disabled
// TraceLevel defines trace log level.
TraceLevel Level = -1
// Values less than TraceLevel are handled as numbers.
)
在zerolog中,日志级别可以全局设置,也可以局部设置,即为每一个Logger或消息设置不同的消息级别。其中全局级别的使用形式如下所示:
- 设置级别:zerolog.SetGlobalLevel(zerolog.InfoLevel)
- 获取级别:zerolog.GlobalLevel()
示例代码如下所示:
package main
import (
"fmt"
"github.com/rs/zerolog"
"github.com/rs/zerolog/log"
)
func main() {
// 获取全局的消息级别
fmt.Printf("获取全局消息级别:%+v\n", zerolog.GlobalLevel())
fmt.Printf("默认Logger的消息级别为:%+v\n", log.Logger.GetLevel())
// 创建一个Logger,并获取相应的消息级别
l := log.Level(zerolog.WarnLevel)
fmt.Printf("新建Logger的级别为:%+v", l.GetLevel())
// 使用默认Logger 自定义每一个消息的级别
log.Info().Msg("默认Logger消息级别-INFO")
log.Error().Msg("默认Logger消息级别-ERROR")
log.Warn().Msg("默认Logger消息级别-WARNING")
log.Debug().Msg("默认Logger消息级别-DEBUG")
log.Trace().Msg("默认Logger消息级别-TRACE")
// 使用自定义Logger 自定义每一个消息的级别
l.Debug().Msg("自定义Logger消息级别-DEBUG") //不输出
l.Info().Msg("自定义Logger消息级别-INFO") // 不输出
l.Error().Msg("自定义Logger消息级别-ERROR") //输出
l.Warn().Msg("自定义Logger消息级别-WARNING") //输出
}
代码运行结果如下所示:
获取全局消息级别:trace
默认Logger的消息级别为:trace
新建Logger的级别为:warn
{"level":"info","time":"2024-12-31T23:41:34+08:00","message":"默认Logger消息级别-INFO"}
{"level":"error","time":"2024-12-31T23:41:34+08:00","message":"默认Logger消息级别-ERROR"}
{"level":"warn","time":"2024-12-31T23:41:34+08:00","message":"默认Logger消息级别-WARNING"}
{"level":"debug","time":"2024-12-31T23:41:34+08:00","message":"默认Logger消息级别-DEBUG"}
{"level":"trace","time":"2024-12-31T23:41:34+08:00","message":"默认Logger消息级别-TRACE"}
{"level":"error","time":"2024-12-31T23:41:34+08:00","message":"自定义Logger消息级别-ERROR"}
{"level":"warn","time":"2024-12-31T23:41:34+08:00","message":"自定义Logger消息级别-WARNING"}
从代码运行结果可以总结如下所示:
- 使用默认Logger,可以输出所有日志消息
- 使用自定义Logger,只能输出部分日志消息,是因为在zerolog中存在消息级别和Logger级别两个概念
在上述代码中自定义的Logger中,其Logger级别被设置为WarnLevel,其级别为2,当输出的消息级别低于其Logger级别时,则不被输出。因此想要消息成功输出,则需要满足以下条件
消息级别 >= max(全局组别,当前Logger级别)
其中 zerolog.SetGlobalLevel() 为全局级别,会影响所有Logger
package main
import (
"fmt"
"github.com/rs/zerolog"
"github.com/rs/zerolog/log"
)
func main() {
// 设置全局消息级别
zerolog.SetGlobalLevel(zerolog.ErrorLevel)
// 获取全局的消息级别
fmt.Printf("获取全局消息级别:%+v\n", zerolog.GlobalLevel())
fmt.Printf("默认Logger的消息级别为:%+v\n", log.Logger.GetLevel())
// 创建一个Logger,并获取相应的消息级别
l := log.Level(zerolog.WarnLevel)
fmt.Printf("新建Logger的级别为:%+v", l.GetLevel())
// 使用默认Logger 自定义每一个消息的级别
log.Info().Msg("默认Logger消息级别-INFO")
log.Error().Msg("默认Logger消息级别-ERROR")
log.Warn().Msg("默认Logger消息级别-WARNING")
log.Debug().Msg("默认Logger消息级别-DEBUG")
log.Trace().Msg("默认Logger消息级别-TRACE")
// 使用自定义Logger 自定义每一个消息的级别
l.Debug().Msg("自定义Logger消息级别-DEBUG") //不输出
l.Info().Msg("自定义Logger消息级别-INFO") // 不输出
l.Error().Msg("自定义Logger消息级别-ERROR") //输出
l.Warn().Msg("自定义Logger消息级别-WARNING") //输出
// 禁用日志输出
zerolog.SetGlobalLevel(zerolog.Disabled)
fmt.Printf("获取全局消息级别:%+v\n", zerolog.GlobalLevel())
log.Info().Msg("默认Logger消息级别-PANIC")
l.Error().Msg("自定义Logger消息级别-PANIC")
}
代码运行结果如下所示:
获取全局消息级别:error
默认Logger的消息级别为:trace
新建Logger的级别为:warn{"level":"error","time":"2025-01-01T00:06:15+08:00","message":"默认Logger消息级别-ERROR"}
{"level":"error","time":"2025-01-01T00:06:15+08:00","message":"自定义Logger消息级别-ERROR"}
获取全局消息级别:disabled
18.2.4 上下文
zerolog 的默认输出格式为JSON,但也可以自定义一些键值对字段,并在上下文中进行输出,示例代码如下所示:
package main
import (
"fmt"
"time"
"github.com/rs/zerolog"
"github.com/rs/zerolog/log"
)
func main() {
// 设置全局消息级别
zerolog.SetGlobalLevel(zerolog.InfoLevel)
// 获取全局的消息级别
fmt.Printf("获取全局消息级别:%+v\n", zerolog.GlobalLevel())
fmt.Printf("默认Logger的消息级别为:%+v\n", log.Logger.GetLevel())
log.Warn().Bool("SUCCESS", false).Str("原因", "未满足相应的条件").Msg("没有满足相应条件")
log.Info().Str("Username", "Surpass").Time("登录时间", time.Now()).Msg("用户登录成功")
}
代码运行结果如下所示:
获取全局消息级别:info
默认Logger的消息级别为:trace
{"level":"warn","SUCCESS":false,"原因":"未满足相应的条件","time":"2025-01-01T00:19:38+08:00","message":"没有满足相应条件"}
{"level":"info","Username":"Surpass","登录时间":"2025-01-01T00:19:38+08:00","time":"2025-01-01T00:19:38+08:00","message":"用户登录成功"}
18.2.5 错误日志
在zerolog中输出错误日志的示例代码如下所示:
package main
import (
"errors"
"fmt"
"time"
"github.com/rs/zerolog"
"github.com/rs/zerolog/log"
)
func main() {
// 设置全局消息级别
zerolog.SetGlobalLevel(zerolog.InfoLevel)
// 获取全局的消息级别
fmt.Printf("获取全局消息级别:%+v\n", zerolog.GlobalLevel())
fmt.Printf("默认Logger的消息级别为:%+v\n", log.Logger.GetLevel())
// 设置时间格式
zerolog.TimeFieldFormat = zerolog.TimeFormatUnixMs
log.Info().Str("Username", "Surpass").Time("登录时间", time.Now()).Msg("用户登录成功")
// 自定义错误日志
err := errors.New("Surpass自定义错误日志")
log.Error(). // 错误级别消息
Err(err). // 错误消息内容,即err字段
Send()
log.Fatal().Err(err).Send()
}
代码运行结果如下所示:
获取全局消息级别:info
默认Logger的消息级别为:trace
{"level":"info","Username":"Surpass","登录时间":1735662443578,"time":1735662443578,"message":"用户登录成功"}
{"level":"error","error":"Surpass自定义错误日志","time":1735662443578}
{"level":"fatal","error":"Surpass自定义错误日志","time":1735662443578}
exit status 1
18.2.6 自定义Logger
示例代码如下所示:
package main
import (
"errors"
"fmt"
"os"
"github.com/rs/zerolog"
"github.com/rs/zerolog/log"
)
func main() {
// 自定义Logger方式一:
loggerA := zerolog.New(os.Stdout).With().Timestamp().Caller().Logger().Level(zerolog.DebugLevel)
zerolog.TimeFieldFormat = zerolog.TimeFormatUnixMs
fmt.Println("自定义LoggerA的级别为:", loggerA.GetLevel())
err := errors.New("Surpass自定义错误")
loggerA.Error().Err(err).Send()
fmt.Println("")
// 自定义Logger方式二:
loggerB := log.With().Str("Product", "Surpass").Caller().Logger().Level(zerolog.InfoLevel)
fmt.Println("自定义LoggerB的级别为:", loggerB.GetLevel())
loggerB.Info().Msg("自定义的Logger")
}
代码运行结果如下所示:
自定义LoggerA的级别为: debug
{"level":"error","error":"Surpass自定义错误","time":1735664944449,"caller":"C:/Users/Surpass/Documents/GolangProjets/src/18/1808-zerolog-customer/main.go:18"}
自定义LoggerB的级别为: info
{"level":"info","Product":"Surpass","time":1735664944449,"caller":"C:/Users/Surpass/Documents/GolangProjets/src/18/1808-zerolog-customer/main.go:25","message":"自定义的Logger"}
18.2.7 日志写入文件
示例代码如下所示:
package main
import (
"os"
"path/filepath"
"github.com/rs/zerolog"
"github.com/rs/zerolog/log"
)
func main() {
zerolog.TimeFieldFormat = zerolog.TimeFormatUnixMs
curPath, _ := os.Getwd()
filename := filepath.Join(curPath, "zerolog.surpass.log")
f, err := os.OpenFile(filename, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0)
if err != nil {
log.Panic().Err(err).Send()
}
defer f.Close()
// 多分支写
multi := zerolog.MultiLevelWriter(f, os.Stdout)
logger := zerolog.New(multi).With().Timestamp().Logger()
logger.Info().Msg("日志同时写入文件和控制台")
}