如何用Golang实现简单的日志系统

来源:AI编程作者:南京网站建设头衔:草根站长
导读:本期聚焦于小伙伴创作的《如何用Golang实现简单的日志系统》,敬请观看详情,探索知识的价值。以下视频、文章将为您系统阐述其核心内容与价值。如果您觉得《如何用Golang实现简单的日志系统》有用,将其分享出去将是对创作者最好的鼓励。

在Golang项目开发中,日志是排查问题、监控运行状态的核心工具。很多场景下我们不需要引入复杂的第三方日志库,自己实现一个简单的日志系统就能满足需求,还能根据项目特性做定制化调整。

如何用Golang实现简单的日志系统

核心需求分析

一个简单的日志系统需要覆盖以下几个基础功能:

  • 支持日志分级,比如DEBUG、INFO、WARN、ERROR四个级别,不同级别日志可以按需输出
  • 日志内容包含时间戳、日志级别、具体日志信息,方便后续检索
  • 支持输出到控制台和文件,文件日志需要按日期或者大小切割,避免单个文件过大
  • 并发场景下保证日志写入安全,不会出现内容错乱

基础结构与常量定义

首先我们定义日志级别和相关的基础结构,代码如下:

package logger

import (
	"fmt"
	"os"
	"sync"
	"time"
)

// 日志级别常量
const (
	DEBUG = iota
	INFO
	WARN
	ERROR
)

// 日志级别对应的字符串
var levelStr = map[int]string{
	DEBUG: "DEBUG",
	INFO:  "INFO",
	WARN:  "WARN",
	ERROR: "ERROR",
}

// Logger 日志结构体
type Logger struct {
	level    int       // 当前日志级别
	file     *os.File  // 日志文件句柄
	mu       sync.Mutex // 互斥锁保证并发安全
	filePath string    // 日志文件路径
}

初始化日志实例

我们需要一个初始化函数,用来创建日志实例,设置日志级别和日志文件路径:

// NewLogger 创建新的日志实例
// level: 日志级别,低于该级别的日志不会输出
// filePath: 日志文件路径,如果为空则只输出到控制台
func NewLogger(level int, filePath string) (*Logger, error) {
	l := &Logger{
		level:    level,
		filePath: filePath,
	}

	// 如果指定了日志文件路径,打开或创建文件
	if filePath != "" {
		file, err := os.OpenFile(filePath, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0644)
		if err != nil {
			return nil, fmt.Errorf("打开日志文件失败: %v", err)
		}
		l.file = file
	}
	return l, nil
}

核心日志写入方法

日志写入需要统一处理格式,同时输出到控制台和文件(如果配置了文件输出),并且要保证并发安全:

// writeLog 写入日志的核心方法
func (l *Logger) writeLog(level int, format string, args ...interface{}) {
	// 日志级别不够,直接返回
	if level < l.level {
		return
	}

	// 构造日志内容
	now := time.Now().Format("2006-01-02 15:04:05")
	levelName := levelStr[level]
	msg := fmt.Sprintf(format, args...)
	logContent := fmt.Sprintf("%s [%s] %sn", now, levelName, msg)

	// 加锁保证并发安全
	l.mu.Lock()
	defer l.mu.Unlock()

	// 输出到控制台
	fmt.Print(logContent)

	// 输出到文件
	if l.file != nil {
		_, err := l.file.WriteString(logContent)
		if err != nil {
			fmt.Printf("写入日志文件失败: %vn", err)
		}
	}
}

对外暴露的日志方法

为了方便使用,我们给四个日志级别分别提供对应的方法:

// Debug 输出DEBUG级别日志
func (l *Logger) Debug(format string, args ...interface{}) {
	l.writeLog(DEBUG, format, args...)
}

// Info 输出INFO级别日志
func (l *Logger) Info(format string, args ...interface{}) {
	l.writeLog(INFO, format, args...)
}

// Warn 输出WARN级别日志
func (l *Logger) Warn(format string, args ...interface{}) {
	l.writeLog(WARN, format, args...)
}

// Error 输出ERROR级别日志
func (l *Logger) Error(format string, args ...interface{}) {
	l.writeLog(ERROR, format, args...)
}

日志文件切割优化

上面的实现中日志会一直写入同一个文件,文件会越来越大,我们可以增加按日期切割的逻辑,每天生成一个新的日志文件:

// rotateFile 按日期切割日志文件
func (l *Logger) rotateFile() error {
	l.mu.Lock()
	defer l.mu.Unlock()

	// 关闭旧文件
	if l.file != nil {
		l.file.Close()
	}

	// 生成新的文件名,按日期区分
	now := time.Now().Format("20060102")
	newFilePath := fmt.Sprintf("%s_%s.log", l.filePath, now)
	file, err := os.OpenFile(newFilePath, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0644)
	if err != nil {
		return fmt.Errorf("打开新日志文件失败: %v", err)
	}
	l.file = file
	return nil
}

// 可以在每次写入前检查日期,或者启动一个定时任务每天切割
// 这里以定时任务为例,每天凌晨切割日志
func (l *Logger) StartRotate() {
	go func() {
		for {
			now := time.Now()
			// 计算到明天凌晨的时间
			nextDay := time.Date(now.Year(), now.Month(), now.Day()+1, 0, 0, 0, 0, now.Location())
			time.Sleep(nextDay.Sub(now))

			// 执行切割
			if err := l.rotateFile(); err != nil {
				fmt.Printf("日志切割失败: %vn", err)
			}
		}
	}()
}

使用示例

下面是使用该日志系统的完整示例:

package main

import (
	"test/logger" // 替换为你的logger包路径
)

func main() {
	// 初始化日志,设置INFO级别,日志文件路径为./app.log
	log, err := logger.NewLogger(logger.INFO, "./app.log")
	if err != nil {
		panic(err)
	}
	defer func() {
		if log.File != nil { // 这里需要在Logger结构体中增加File的导出方法,或者调整结构
			log.File.Close()
		}
	}()

	// 启动日志按日切割
	log.StartRotate()

	// 写入不同级别的日志
	log.Debug("这是一条DEBUG日志,不会输出")
	log.Info("这是一条INFO日志,会输出")
	log.Warn("这是一条WARN日志")
	log.Error("这是一条ERROR日志")

	// 模拟并发场景下的日志写入
	for i := 0; i < 5; i++ {
		go func(index int) {
			log.Info("并发写入的日志,序号: %d", index)
		}(i)
	}

	// 等待 goroutine 执行完成,实际项目中可以用sync.WaitGroup
	time.Sleep(time.Second)
}

注意事项

  • 如果需要在Logger结构体中导出文件句柄,可以给Logger增加一个GetFile方法,避免直接导出字段
  • 日志文件路径如果需要支持相对路径,建议转换为绝对路径后再操作,避免路径错误
  • 如果项目日志量很大,可以进一步优化写入逻辑,比如增加缓冲区批量写入,减少IO次数
  • 正式项目中可以根据需求增加更多功能,比如日志压缩、按大小切割、日志上报等

Golang日志系统日志分级文件写入并发安全修改时间:2026-06-11 08:57:31

免责声明:​ 已尽一切努力确保本网站所含信息的准确性。网站内容多为原创整理与精心编撰,观点力求客观中立。本站旨在免费分享,内容仅供个人学习、研究或参考使用。若引用了第三方作品,版权归原作者所有。如内容涉及您的权益,请联系我们处理。
内容垂直聚焦
专注技术核心技术栏目,确保每篇文章深度聚焦于实用技能。从代码技巧到架构设计,为用户提供无干扰的纯技术知识沉淀,精准满足专业提升需求。
知识结构清晰
覆盖从开发到部署的全链路。AI、前端、编程、数据库、服务器、建站、系统层层递进,构建清晰学习路径,帮助用户系统化掌握开发与运维所需的核心技术。
深度技术解析
拒绝泛泛而谈,深入技术细节与实践难点。无论是数据库优化还是服务器配置,均结合真实场景与代码示例进行剖析,致力于提供可直接应用于工作的解决方案。
专业领域覆盖
精准对应开发生命周期。从前端界面到后端编程,从数据库操作到服务器运维,形成完整闭环,一站式满足全栈工程师和运维人员的技术需求。
即学即用高效
内容强调实操性,步骤清晰、代码完整。用户可根据教程直接复现和应用于自身项目,显著缩短从学习到实践的距离,快速解决开发中的具体问题。
持续更新保障
专注既定技术方向进行长期、稳定的内容输出。确保各栏目技术文章持续更新迭代,紧跟主流技术发展趋势,为用户提供经久不衰的学习价值。