Go语言自定义日志库实现:按小时滚动与多级别控制
Golang标准日志库提供的Print、Fatal、Panic等方法缺乏常见的Debug、Info、Error等分级,操作起来不够直观。为了解决这一问题,可以基于标准库封装一个支持多级日志输出和文件自动滚动的自定义日志库,例如命名为ylog。
初始需求
设计该日志库主要满足以下核心需求:
- 文件管理:将日志持久化到磁盘,实现按月建立文件夹、按小时自动切换单个日志文件。
- 分级控制:支持Trace、Debug、Info、Warn、Error、Fatal六种日志级别,且允许程序动态设置输出的最低阈值。
期望的使用方式极为简洁,能够直接调用包级函数:
ylog.SetLevel(LevelInfo)
ylog.Debug("I am a debug log.")
ylog.Info("I am a Info log.")技术实现
第1步:定义结构体以管理状态
需要创建一个包含上下文信息的结构体,用于维护当前打开的文件指针、级别以及线程安全锁:
type FileLogger struct {
lastHour int64 // 记录创建日志文件时的小时数,用于检测是否需要切换文件
file *os.File // 当前的日志文件实例
Level LogLevel // 当前生效的日志级别
mu sync.Mutex // 互斥体,防止多goroutine并发写日志导致的重复创建问题
iLogger *log.Logger // 内嵌的标准日志库实例
Path string // 日志保存的最上层目录路径第2步:定义日志级别并封装输出方法
将日志级别定义为整型枚举(从0到5),数值越高表示级别越高。若设置为LevelInfo,则Info及以上的级别会被输出。
type LogLevel int const ( LevelTrace LogLevel = iota LevelDebug LevelInfo LevelWarn LevelError LevelFatal )
针对每个级别编写对应的方法。以下是Info级别的实现逻辑,其他级别同理:
func (l *FileLogger) CanInfo() bool {
return l.Level <= LevelInfo
}
func (l *FileLogger) Info(v ...any) {
if l.CanInfo() { // 1. 判断是否达到输出级别
l.ensureFile() // 2. 确保日志文件已就绪
v = append([]any{"Info "}, v...) // 3. 将级别前缀加入消息
l.iLogger.Output(2, fmt.Sprintln(v...)) // 4. 调用底层Output方法输出其中 Output 方法的第一个参数设为2,代表回溯深度,用于获取当前调用此方法的代码文件名。
第3步:实现文件自动切换逻辑(ensureFile)
利用标准库 log.Logger 的 SetOutput 接口(只要实现了 io.Writer 即可接入)。在 ensureFile 中处理文件的路径分配与时钟判断:
func (l *FileLogger) ensureFile() (err error) {
currentTime := time.Now()
if l.file == nil { // 首次启动或实例刚初始化
l.mu.Lock()
defer l.mu.Unlock()
if l.file == nil { // 双重检查锁模式
l.file, err = createFile(&l.Path, ¤tTime)
l.iLogger.SetOutput(l.file)
l.iLogger.SetFlags(log.Lshortfile | log.Ldate | log.Ltime | log.Lmicroseconds)
l.lastHour = getTimeHour(¤tTime)
}
return
}
// 如果当前时间与上次创建文件的时间所在小时不同
currentHour := getTimeHour(¤tTime)
if l.lastHour != currentHour {
l.mu.Lock()
defer l.mu.Unlock()
if l.lastHour != currentHour { // 再次确认未发生并发变更
_ = l.file.Close() // 关闭旧文件
l.file, err = createFile(&l.Path, ¤tTime) // 创建新小时文件
l.iLogger.SetOutput(l.file)
l.iLogger.SetFlags(log.Llongfile | log.Ldate | log.Ltime)
l.lastHour = currentHour // 更新时钟状态
}
}
return
}第4步:构建全局默认实例
为了让包可以直接被调用,需要在顶层创建一个默认的 std 实例,并在其对应的公开函数中直接复用这一实例的代码,避免嵌套调用造成堆栈深度计算错误:
var stdPath = "logs"
var std = NewFileLogger(LevelInfo, stdPath)
func Trace(v ...any) {
if std.CanTrace() {
std.ensureFile()
v = append([]any{"Trace"}, v...)
std.iLogger.Output(2, fmt.Sprintln(v...))
}
}最终用户无需手动实例化 logger,直接在代码顶部导入使用即可:
ylog.SetLevel(LevelInfo)
ylog.Debug("I am a debug log.")
ylog.Info("I am a Info log.")总结
通过上述步骤,成功构建了具备六层级管理和按小时滚动功能的基础日志组件。虽然标准日志库功能有限,但通过简单的二次封装就能极大提升日常开发效率。后续还可以在此基础上增加JSON格式输出、Kafka发送以及更复杂的异步缓冲等功能。
以上就是Go语言实现支持六种级别的日志库的详细内容,更多关于Go日志库开发优化的资料请关注本站其它相关文章!
本站声明
本文内容仅供参考,不代表本站观点。转载或引用请注明出处。如发现内容问题,请通过联系我们页面反馈。













