go语言日志管理

go语言日志管理

1.1 日志库的介绍

日志库在软件开发中具有重要作用,它用于记录程序运行时的关键信息、错误信息和调试信息,以便于开发者在开发、测试和生产环境中进行故障排查、问题定位和性能优化。以下是 Go 日志库的一些主要作用:

  1. 记录关键信息:日志库用于记录应用程序的关键信息,例如请求处理的开始和结束时间、用户操作行为、系统状态等。这些信息对于追踪应用程序的运行情况和分析问题非常有帮助。
  2. 错误追踪和故障排查:当应用程序发生错误或异常时,日志库能够记录相关的错误信息,包括错误类型、错误堆栈信息、错误的上下文等。这些日志可以帮助开发者快速定位问题并进行故障排查。
  3. 调试和性能优化:日志库可以记录调试信息,例如函数调用栈、变量的值、代码执行路径等,以帮助开发者理解和调试程序。此外,通过记录性能相关的日志,开发者可以进行性能分析和优化,找出性能瓶颈并进行改进。
  4. 审计和安全性:日志库可以记录用户操作行为、系统事件和安全相关的信息,用于审计、合规性和安全性分析。这些日志可以追踪用户的行为,识别潜在的安全威胁,并提供证据用于安全审计和调查。
  5. 错误报警和监控:日志库通常与错误报警和监控系统集成,当出现严重错误或异常时,自动触发报警机制,通知相关人员进行处理。这有助于实时监控应用程序的健康状态和故障情况。

综上所述,日志库在软件开发中扮演着关键的角色,帮助开发者记录、追踪和分析应用程序的运行情况,解决问题和提升性能。使用适当的日志库可以提供丰富的日志功能和灵活的配置选项,以满足不同场景下的需求。

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 个字段:

PackageTimeTime % to zapObjects Allocated
⚡ zap1744 ns/op+0%5 allocs/op
⚡ zap (sugared)2483 ns/op+42%10 allocs/op
zerolog918 ns/op-47%1 allocs/op
go-kit5590 ns/op+221%57 allocs/op
slog5640 ns/op+223%40 allocs/op
apex/log21184 ns/op+1115%63 allocs/op
logrus24338 ns/op+1296%79 allocs/op
log1526054 ns/op+1394%74 allocs/op

使用已经具有 10 个上下文字段的记录器记录消息:

PackageTimeTime % to zapObjects Allocated
⚡ zap193 ns/op+0%0 allocs/op
⚡ zap (sugared)227 ns/op+18%1 allocs/op
zerolog81 ns/op-58%0 allocs/op
slog322 ns/op+67%0 allocs/op
go-kit5377 ns/op+2686%56 allocs/op
apex/log19518 ns/op+10013%53 allocs/op
log1519812 ns/op+10165%70 allocs/op
logrus21997 ns/op+11297%68 allocs/op

记录一个静态字符串,没有任何上下文或printf-style 模板:

PackageTimeTime % to zapObjects Allocated
⚡ zap165 ns/op+0%0 allocs/op
⚡ zap (sugared)212 ns/op+28%1 allocs/op
zerolog95 ns/op-42%0 allocs/op
slog296 ns/op+79%0 allocs/op
go-kit415 ns/op+152%9 allocs/op
standard library422 ns/op+156%2 allocs/op
apex/log1601 ns/op+870%5 allocs/op
logrus3017 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")

	// ... 其他日志记录操作
}

运行结果:

image-20230609115944022

在上面的示例中,我们使用 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")

	// ... 其他日志记录操作
}

运行结果:

image-20230609120117697

在上面的示例中,我们使用 zap.NewProduction() 创建了一个生产环境的 Logger,然后使用 logger.Info()logger.Warn() 方法记录日志消息。

请注意,与 SugaredLogger 不同,Logger 直接提供了各种日志级别的方法,如 Info()Warn()Error() 等。这些方法接受一个字符串参数,表示要记录的日志消息。

1.7 zap自定义logger

在实际开发项目中,我们要做的第一个更改是把日志写入文件,而不是打印到应用程序控制台。因此,我们将使用zap.New()方法来手动传递所有配置,而不是使用像zap.NewProduction()这样的预置方法来创建logger。观察如下源码可知实现这个方法我们需要创建Core, 实现zapcore.NewCore()方法的三个配置项EncoderWriteSyncerLogLevel

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")
}

运行结果:

image-20230609125259839

上面的代码示例中,我们定义了 InitLogger 函数来初始化日志记录器。它使用 getLogWriter 函数获取日志写入器,使用 getEncoder 函数获取日志编码器,并创建一个 zapcore.Core。然后,我们使用 zap.New 创建一个新的 zap.Logger,并通过 zap.AddCaller 添加了函数调用信息,最后通过 logger.Sugar() 创建了一个 zap.SugaredLogger

main 函数中,我们调用 InitLogger 初始化日志记录器。然后使用 sugarLogger 记录不同级别的日志消息,例如 InfoWarnError。这些日志消息将根据配置写入到指定的日志文件中。

请注意,代码中使用了 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")
}

运行结果:

image-20230609130607453

在上面的代码示例中,我们使用了 gopkg.in/natefinch/lumberjack.v2 包来实现日志的切割和归档。在 getLogWriter 函数中,我们创建了一个 lumberjack.Logger,并指定了日志文件的路径、最大大小、保留的旧日志文件数量和最大天数等配置。然后,我们使用 zapcore.AddSynclumberjack.Logger 转换为 zapcore.WriteSyncer 接口,作为日志记录器的写入目标。

通过这种配置,Lumberjack 将根据指定的大小和时间参数进行日志文件的切割和归档,确保日志文件不会无限增长,并且可以保留一定的历史日志文件。

如果本篇文章对你有用,欢迎点赞转发关注 倾心全栈成长录 公众号,一起交流学习进步。相关的代码资料公众号发送关键字 zap

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值