go语言日志管理
文章目录
1.1 日志库的介绍
日志库在软件开发中具有重要作用,它用于记录程序运行时的关键信息、错误信息和调试信息,以便于开发者在开发、测试和生产环境中进行故障排查、问题定位和性能优化。以下是 Go 日志库的一些主要作用:
- 记录关键信息:日志库用于记录应用程序的关键信息,例如请求处理的开始和结束时间、用户操作行为、系统状态等。这些信息对于追踪应用程序的运行情况和分析问题非常有帮助。
- 错误追踪和故障排查:当应用程序发生错误或异常时,日志库能够记录相关的错误信息,包括错误类型、错误堆栈信息、错误的上下文等。这些日志可以帮助开发者快速定位问题并进行故障排查。
- 调试和性能优化:日志库可以记录调试信息,例如函数调用栈、变量的值、代码执行路径等,以帮助开发者理解和调试程序。此外,通过记录性能相关的日志,开发者可以进行性能分析和优化,找出性能瓶颈并进行改进。
- 审计和安全性:日志库可以记录用户操作行为、系统事件和安全相关的信息,用于审计、合规性和安全性分析。这些日志可以追踪用户的行为,识别潜在的安全威胁,并提供证据用于安全审计和调查。
- 错误报警和监控:日志库通常与错误报警和监控系统集成,当出现严重错误或异常时,自动触发报警机制,通知相关人员进行处理。这有助于实时监控应用程序的健康状态和故障情况。
综上所述,日志库在软件开发中扮演着关键的角色,帮助开发者记录、追踪和分析应用程序的运行情况,解决问题和提升性能。使用适当的日志库可以提供丰富的日志功能和灵活的配置选项,以满足不同场景下的需求。
1.2 GO的默认logger
Go语言的标准库中自带了log
包,它提供了简单的日志功能。使用log.Println()
、log.Printf()
等函数进行日志输出。默认会输出到控制台。
package main
import (
"log"
)
func main() {
log.Println("This is a log message")
log.Printf("This is a formatted log message with parameter: %s", "parameter value")
log.Fatalln("This is a log that will trigger fatal")
log.Panicln("This is a log that will trigger a panic")
}
在代码中,log 包提供了几个常用的函数来输出日志:
Println
:用于输出普通日志消息,类似于 fmt.Println,输出到标准输出。Printf
:用于输出格式化的日志消息,类似于 fmt.Printf,输出到标准输出。Fatalln
:用于输出日志消息并调用 os.Exit(1) 终止程序执行,表示发生了严重错误。Panicln
:用于输出日志消息并触发 panic,表示发生了无法恢复的错误。
这些日志函数会输出包含日期、时间和日志消息的日志行,并在行末尾添加换行符。日志输出默认会写入标准输出,可以通过 log 包的其他函数进行配置,如设置输出位置、格式、前缀等。
1.3 自定义logger
1.3.1 创建新的日志器
type Logger struct {
file *os.File
}
func NewLogger(filename string) (*Logger, error) {
file, err := os.OpenFile(filename, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666)
if err != nil {
return nil, err
}
return &Logger{file: file}, nil
}
在上述代码中,我们定义了一个 Logger
结构体,其中包含一个文件句柄用于写入日志文件。通过 NewLogger
函数创建一个新的日志记录器,该函数接受日志文件的路径作为参数,并返回一个 Logger
实例和一个可能的错误。
1.3.2 写入日志并格式化
type Logger struct {
file *os.File
}
func (l *Logger) Log(message string) {
timestamp := time.Now().Format("2006-01-02 15:04:05")
logLine := fmt.Sprintf("[%s] %s\n", timestamp, message)
l.file.WriteString(logLine)
}
Logger
结构体中的 Log
方法用于写入日志消息到日志文件中。它接受一个字符串参数 message
,将其格式化为日志行并写入文件。
1.3.3 关闭日志文件
type Logger struct {
file *os.File
}
func (l *Logger) Close() error {
return l.file.Close()
}
在方法的实现中,它调用了 l.file.Close()
方法来关闭 Logger
结构体中的 file
成员变量。这里假设 Logger
结构体中有一个名为 file
的文件对象,该对象具有 Close()
方法用于关闭文件。
1.3.4 完整示例
要实现自定义的日志记录器(Logger)可以使用 Go 标准库的 log 包或自行编写一个日志库。以下是一个简单示例,展示了如何自定义一个基本的日志记录器:
package main
import (
"fmt"
"os"
"time"
)
type Logger struct {
file *os.File
}
func NewLogger(filename string) (*Logger, error) {
file, err := os.OpenFile(filename, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666)
if err != nil {
return nil, err
}
return &Logger{file: file}, nil
}
func (l *Logger) Log(message string) {
timestamp := time.Now().Format("2006-01-02 15:04:05")
logLine := fmt.Sprintf("[%s] %s\n", timestamp, message)
l.file.WriteString(logLine)
}
func (l *Logger) Close() error {
return l.file.Close()
}
func main() {
logger, err := NewLogger("app.log")
if err != nil {
fmt.Printf("Failed to create logger: %v\n", err)
return
}
defer logger.Close()
logger.Log("This is a log message")
logger.Log("This is another log message")
}
在上述代码中,我们定义了一个 Logger
结构体,其中包含一个文件句柄用于写入日志文件。通过 NewLogger
函数创建一个新的日志记录器,该函数接受日志文件的路径作为参数,并返回一个 Logger
实例和一个可能的错误。
Logger
结构体中的 Log
方法用于写入日志消息到日志文件中。它接受一个字符串参数 message
,将其格式化为日志行并写入文件。
在 main
函数中,我们首先创建一个日志记录器实例,并通过调用 Log
方法写入两条日志消息。最后,我们使用 defer
语句确保在程序退出前关闭日志文件。
请注意,上述示例只是一个简单的示例,演示了如何自定义一个基本的日志记录器。在实际项目中,可能需要更多的功能和选项,例如日志级别、日志格式化、日志轮转等。如果需要更复杂的日志功能,建议使用现有的日志库,如 zap、logrus、zerolog 等,它们提供了更多的功能和配置选项,能够满足更多的需求。
1.4 zap日志库介绍
zap
是一个高性能的日志库,由 Uber 开源并维护。它具有低分配(allocation)、低开销(overhead)和强大的日志级别控制等特点。在实际开发中,被广泛用于Go项目中。
根据Uber-go Zap
的文档,它的性能比类似的结构化日志包更好——也比标准库更快。 以下是Zap发布的基准测试信息:
记录一条消息和 10 个字段:
Package | Time | Time % to zap | Objects Allocated |
---|---|---|---|
⚡ zap | 1744 ns/op | +0% | 5 allocs/op |
⚡ zap (sugared) | 2483 ns/op | +42% | 10 allocs/op |
zerolog | 918 ns/op | -47% | 1 allocs/op |
go-kit | 5590 ns/op | +221% | 57 allocs/op |
slog | 5640 ns/op | +223% | 40 allocs/op |
apex/log | 21184 ns/op | +1115% | 63 allocs/op |
logrus | 24338 ns/op | +1296% | 79 allocs/op |
log15 | 26054 ns/op | +1394% | 74 allocs/op |
使用已经具有 10 个上下文字段的记录器记录消息:
Package | Time | Time % to zap | Objects Allocated |
---|---|---|---|
⚡ zap | 193 ns/op | +0% | 0 allocs/op |
⚡ zap (sugared) | 227 ns/op | +18% | 1 allocs/op |
zerolog | 81 ns/op | -58% | 0 allocs/op |
slog | 322 ns/op | +67% | 0 allocs/op |
go-kit | 5377 ns/op | +2686% | 56 allocs/op |
apex/log | 19518 ns/op | +10013% | 53 allocs/op |
log15 | 19812 ns/op | +10165% | 70 allocs/op |
logrus | 21997 ns/op | +11297% | 68 allocs/op |
记录一个静态字符串,没有任何上下文或printf
-style 模板:
Package | Time | Time % to zap | Objects Allocated |
---|---|---|---|
⚡ zap | 165 ns/op | +0% | 0 allocs/op |
⚡ zap (sugared) | 212 ns/op | +28% | 1 allocs/op |
zerolog | 95 ns/op | -42% | 0 allocs/op |
slog | 296 ns/op | +79% | 0 allocs/op |
go-kit | 415 ns/op | +152% | 9 allocs/op |
standard library | 422 ns/op | +156% | 2 allocs/op |
apex/log | 1601 ns/op | +870% | 5 allocs/op |
logrus | 3017 ns/op | +1728% | 23 allocs/op |
1.5 zap日志库安装
go get -u go.uber.org/zap
1.6 zap日志记录器
Zap 提供了两种类型的日志记录器,分别是 Sugared Logger 和 Logger。
1.6.1 Sugared Logger
Sugared Logger
是对 Logger 的一个封装, 提供了更简洁、易用的 API,支持占位符格式化日志信息,可以直接传递参数。自动地对日志参数进行延迟求值,只有在需要记录日志时才会进行计算,避免了性能开销。支持记录键值对格式的上下文信息,方便添加额外的关键信息。相对于 Logger,性能略低,因为它需要进行额外的格式化处理和参数计算。缺乏对日志级别的细粒度控制,无法像 Logger 那样直接设置级别,只能通过 Logger 的级别来控制。
1.6.2 Sugared Logger的使用
以下是一个示例代码,展示了如何使用 SugaredLogger
:
package main
import (
"go.uber.org/zap"
)
func main() {
logger, err := zap.NewProduction()
if err != nil {
panic(err)
}
defer logger.Sync()
sugar := logger.Sugar()
sugar.Infof("This is a log message")
sugar.Warnf("This is a warning message")
// ... 其他日志记录操作
}
运行结果:
在上面的示例中,我们使用 zap.NewProduction()
创建了一个生产环境的 Logger
,然后使用 logger.Sugar()
方法创建了一个 SugaredLogger
,并赋值给 sugar
变量。接下来,我们可以使用 sugar
对象来记录日志消息,如 sugar.Infof()
和 sugar.Warnf()
。
1.6.3 Logger
Logger
提供了更低级别、高性能的日志记录功能,适用于对性能有较高要求的场景。没有额外的参数计算和格式化处理,性能比 Sugared Logger 更好。可以更精细地控制日志的输出级别和格式,包括时间戳、日志级别、字段等。相对于 Sugared Logger,使用起来更加复杂,需要手动构建字段和参数。不支持直接的占位符格式化,需要手动构建字段和参数。
1.6.4 Logger的使用
package main
import (
"go.uber.org/zap"
)
func main() {
logger, err := zap.NewProduction()
if err != nil {
panic(err)
}
defer logger.Sync()
logger.Info("This is an info log message")
logger.Warn("This is a warning log message")
// ... 其他日志记录操作
}
运行结果:
在上面的示例中,我们使用 zap.NewProduction()
创建了一个生产环境的 Logger
,然后使用 logger.Info()
和 logger.Warn()
方法记录日志消息。
请注意,与 SugaredLogger
不同,Logger
直接提供了各种日志级别的方法,如 Info()
、Warn()
、Error()
等。这些方法接受一个字符串参数,表示要记录的日志消息。
1.7 zap自定义logger
在实际开发项目中,我们要做的第一个更改是把日志写入文件,而不是打印到应用程序控制台。因此,我们将使用zap.New()
方法来手动传递所有配置,而不是使用像zap.NewProduction()
这样的预置方法来创建logger。观察如下源码可知实现这个方法我们需要创建Core, 实现zapcore.NewCore()
方法的三个配置项Encoder
,WriteSyncer
,LogLevel
。
func NewCore(enc Encoder, ws WriteSyncer, enab LevelEnabler) Core {
return &ioCore{
LevelEnabler: enab,
enc: enc,
out: ws,
}
}
1.7.1创建自定义Encoder
根据你的需求选择适当的编码器。例如,可以使用 zapcore.NewJSONEncoder
创建一个 JSON 编码器,或者使用 zapcore.NewConsoleEncoder
创建一个控制台编码器。
func getEncoder() zapcore.Encoder {
encoderConfig := zap.NewProductionEncoderConfig()
encoderConfig.EncodeTime = zapcore.ISO8601TimeEncoder
encoderConfig.EncodeLevel = zapcore.CapitalLevelEncoder
return zapcore.NewConsoleEncoder(encoderConfig)
}
以上代码修改了编码器的时间编码方式为 zapcore.ISO8601TimeEncoder
,这将时间格式化为 ISO8601 标准格式。还修改了编码器的日志级别编码方式为 zapcore.CapitalLevelEncoder
,这将日志级别使用大写字母进行编码。
1.7.2创建自定义WriteSyncer
func getLogWriter() zapcore.WriteSyncer {
file, _ := os.Create("./logs.log")
return zapcore.AddSync(file)
}
以上代码它将使用指定的文件将日志写入到 “logs.log” 文件中。
1.7.3 logLevel自定义级别
以下为zap源码内置的日志级别:
const (
// DebugLevel logs are typically voluminous, and are usually disabled in
// production.
DebugLevel Level = iota - 1
// InfoLevel is the default logging priority.
InfoLevel
// WarnLevel logs are more important than Info, but don't need individual
// human review.
WarnLevel
// ErrorLevel logs are high-priority. If an application is running smoothly,
// it shouldn't generate any error-level logs.
ErrorLevel
// DPanicLevel logs are particularly important errors. In development the
// logger panics after writing the message.
DPanicLevel
// PanicLevel logs a message, then panics.
PanicLevel
// FatalLevel logs a message, then calls os.Exit(1).
FatalLevel
_minLevel = DebugLevel
_maxLevel = FatalLevel
// InvalidLevel is an invalid value for Level.
//
// Core implementations may panic if they see messages of this level.
InvalidLevel = _maxLevel + 1
)
1.7.4 完整示例
package main
import (
"go.uber.org/zap"
"go.uber.org/zap/zapcore"
"os"
)
var sugarLogger *zap.SugaredLogger
// InitLogger 初始化日志记录器
func InitLogger() {
writeSyncer := getLogWriter()
encoder := getEncoder()
core := zapcore.NewCore(encoder, writeSyncer, zapcore.DebugLevel)
logger := zap.New(core, zap.AddCaller())
sugarLogger = logger.Sugar()
}
// getEncoder 获取日志编码器
func getEncoder() zapcore.Encoder {
encoderConfig := zap.NewProductionEncoderConfig()
encoderConfig.EncodeTime = zapcore.ISO8601TimeEncoder
encoderConfig.EncodeLevel = zapcore.CapitalLevelEncoder
return zapcore.NewConsoleEncoder(encoderConfig)
}
// getLogWriter 获取日志写入器
func getLogWriter() zapcore.WriteSyncer {
file, _ := os.Create("./logs.log")
return zapcore.AddSync(file)
}
func main() {
// 初始化日志记录器
InitLogger()
// 示例日志记录
sugarLogger.Info("This is an info log")
sugarLogger.Warn("This is a warning log")
sugarLogger.Error("This is an error log")
}
运行结果:
上面的代码示例中,我们定义了 InitLogger
函数来初始化日志记录器。它使用 getLogWriter
函数获取日志写入器,使用 getEncoder
函数获取日志编码器,并创建一个 zapcore.Core
。然后,我们使用 zap.New
创建一个新的 zap.Logger
,并通过 zap.AddCaller
添加了函数调用信息,最后通过 logger.Sugar()
创建了一个 zap.SugaredLogger
。
在 main
函数中,我们调用 InitLogger
初始化日志记录器。然后使用 sugarLogger
记录不同级别的日志消息,例如 Info
、Warn
和 Error
。这些日志消息将根据配置写入到指定的日志文件中。
请注意,代码中使用了 os.Create
创建了一个名为 “logs.log” 的文件,并将其用作日志文件的写入目标。如果需要修改日志文件的路径和名称,请相应地更改 getLogWriter
函数中的文件路径。
1.8Lumberjack日志切割
由于Zap本身不支持切割归档日志文件,因此引入了Lumberjack进行日志切割功能。
1.9 安装Lumberjack
要使用 Lumberjack 进行日志切割和归档,你需要先安装该库。可以使用以下命令在 Go 项目中添加 Lumberjack 依赖:
go get gopkg.in/natefinch/lumberjack.v2
1.10 zap中使用
要在zap中加入Lumberjack支持,我们需要修改WriteSyncer
代码。我们将按照下面的代码修改getLogWriter()
函数:
func getLogWriter() zapcore.WriteSyncer {
lumberjackLogger := &lumberjack.Logger{
Filename: "./logs.log", // 日志文件路径和名称
MaxSize: 100, // 每个日志文件的最大大小(MB)
MaxBackups: 5, // 保留的旧日志文件的最大数量
MaxAge: 30, // 保留的旧日志文件的最大天数
Compress: true, // 是否启用压缩
}
return zapcore.AddSync(lumberjackLogger)
}
1.11 完整代码示例
package main
import (
"go.uber.org/zap"
"go.uber.org/zap/zapcore"
"gopkg.in/natefinch/lumberjack.v2"
)
var sugarLogger *zap.SugaredLogger
// InitLogger 初始化日志记录器
func InitLogger() {
writeSyncer := getLogWriter()
encoder := getEncoder()
core := zapcore.NewCore(encoder, writeSyncer, zapcore.DebugLevel)
logger := zap.New(core, zap.AddCaller())
sugarLogger = logger.Sugar()
}
// getEncoder 获取日志编码器
func getEncoder() zapcore.Encoder {
encoderConfig := zap.NewProductionEncoderConfig()
encoderConfig.EncodeTime = zapcore.ISO8601TimeEncoder
encoderConfig.EncodeLevel = zapcore.CapitalLevelEncoder
return zapcore.NewConsoleEncoder(encoderConfig)
}
// getLogWriter 获取日志写入器
func getLogWriter() zapcore.WriteSyncer {
lumberjackLogger := &lumberjack.Logger{
Filename: "./logs.log", // 日志文件路径和名称
MaxSize: 1, // 每个日志文件的最大大小(MB)
MaxBackups: 5, // 保留的旧日志文件的最大数量
MaxAge: 30, // 保留的旧日志文件的最大天数
Compress: false, // 是否启用压缩
}
return zapcore.AddSync(lumberjackLogger)
}
func main() {
// 初始化日志记录器
InitLogger()
defer sugarLogger.Sync()
for i := 0; i < 10000; i++ {
sugarLogger.Info("倾心...")
}
// 示例日志记录
sugarLogger.Info("This is an info log")
sugarLogger.Warn("This is a warning log")
sugarLogger.Error("This is an error log")
}
运行结果:
在上面的代码示例中,我们使用了 gopkg.in/natefinch/lumberjack.v2
包来实现日志的切割和归档。在 getLogWriter
函数中,我们创建了一个 lumberjack.Logger
,并指定了日志文件的路径、最大大小、保留的旧日志文件数量和最大天数等配置。然后,我们使用 zapcore.AddSync
将 lumberjack.Logger
转换为 zapcore.WriteSyncer
接口,作为日志记录器的写入目标。
通过这种配置,Lumberjack 将根据指定的大小和时间参数进行日志文件的切割和归档,确保日志文件不会无限增长,并且可以保留一定的历史日志文件。
如果本篇文章对你有用,欢迎点赞转发关注 倾心全栈成长录
公众号,一起交流学习进步。相关的代码资料公众号发送关键字 zap