Gin框架原生方式切割日志,Go语言原生日志切割

目录

摘要

痛点

正文

1.分析 io.Writer 接口

2.实现 io.Writer 接口

3.将它作为原生输出

4.将它作为 Gin 框架的输出


摘要

自定义一个日志输出,将go语言和gin框架的日志自动按天拆分。本文通过实现io.Writer接口的方式,替换原生和gin框架的默认Writer,并植入了自定义的逻辑。该示例只讲述了如何按天切分日志,如果需要更多定制的内容,可以很方便的改写demo代码。

痛点

网络上没有原生日志切割相关的内容,动不动就讲引入第三方库。例如logrus,但我不想为了一个很简单的需求,即“按天记录日志”,去引入一整个库。这个需求的原因是,在linux下运行的nohup.out文件总是积累的很快,删了看不到日志,不删又怕哪天堵满磁盘。

正文

注意:本文内容的顺序与思考顺序不同,是按知识点排序的。

1.分析 io.Writer 接口

以下代码来自 go1.19,在 io包 [ io.go:90 ]左右的内容

// Writer is the interface that wraps the basic Write method.
//
// Write writes len(p) bytes from p to the underlying data stream.
// It returns the number of bytes written from p (0 <= n <= len(p))
// and any error encountered that caused the write to stop early.
// Write must return a non-nil error if it returns n < len(p).
// Write must not modify the slice data, even temporarily.
//
// Implementations must not retain p.
type Writer interface {
	Write(p []byte) (n int, err error)
}

可以见到,Writer 这个接口非常的简单,只包含了一个 Write 函数,接受一个二进制切片,返回一个数字和一个异常。注释似乎是说:返回的数字是写成功的长度,如果和输入的切片长度不等,应当返回一个异常。

2.实现 io.Writer 接口

import (
	"io"
	"os"
	"time"
)

// 自定义writer的对象
var myWriter dateFileWriter

// 当前要写入日志的文件
var targetFile *os.File

// 自定义一个writer专门用于写日志
type dateFileWriter struct {
	io.Writer
}

// 为自定义writer实现Write接口
func (b *dateFileWriter) Write(p []byte) (n int, err error) {
	return targetFile.Write(p)
}

// RefreshLogFileUsage 刷新指向的日志文件
func RefreshLogFileUsage() {

	fileName := time.Now().Format("2006_01_02") + ".log"

	tryFile, err := os.OpenFile(fileName, os.O_APPEND|os.O_WRONLY, 0777)
	if err != nil {
		os.WriteFile(fileName, []byte(""), 0777)
		targetFile, _ = os.OpenFile(fileName, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0777)
	} else {
		targetFile = tryFile
	}
}

// MyLogWriter 返回自定义的 writer
func MyLogWriter() io.Writer {
	RefreshLogFileUsage()
	return &myWriter
}

首先我们定义一个文件指针 targetFile,这就是我们自定义 Writer 会写入内容的文件。

然后我们自定义一个 dateFileWriter,实现了 io.Writer 接口,做的事情非常简单,就是把接收的内容直接写入到指向的文件里面去。

我的需求是按日期拆分,所以定义了一个辅助函数 RefreshLogFileUsage(),它会判断当前日期的日志是否存在,如果不存在,他会新建一个以当前日期命名的文件。需要用定时任务在每天凌晨触发这个函数,才能实现完整的功能。如果你没有引入过定时任务,也可以在每次写入之前执行这个函数。

所以这段代码的逻辑是:一个 Writer 接口的实现会接收一些内容,将他们写入指定的文件,可以手动修改这个指定的文件,以实现根据运行时间将日志写到不同文件的需求。

同理你可以自己扩展一些其他内容,例如拆分 error 和 info 的输出文件等。

3.将它作为原生输出

你只需要在项目伊始执行这段代码

log.SetOutput(MyLogWriter())

将原生 log 包的输出指定为自己的实现类就可以了。

注意,fmt不能这样操作

另外,如果你希望log的内容同时输出在控制台和日志文件中,需要这样改写

log.SetOutput(io.MultiWriter(os.Stdout,MyLogWriter()))

这里可以顺便看下 io.MultiWriter() 是干啥的,在 io 包 [ multi.go:120 ] 左右

// MultiWriter creates a writer that duplicates its writes to all the
// provided writers, similar to the Unix tee(1) command.
//
// Each write is written to each listed writer, one at a time.
// If a listed writer returns an error, that overall write operation
// stops and returns the error; it does not continue down the list.
func MultiWriter(writers ...Writer) Writer {
	allWriters := make([]Writer, 0, len(writers))
	for _, w := range writers {
		if mw, ok := w.(*multiWriter); ok {
			allWriters = append(allWriters, mw.writers...)
		} else {
			allWriters = append(allWriters, w)
		}
	}
	return &multiWriter{allWriters}
}

可以看到这个 Writer 就是接收了多个 Writer,他可以把写入行为分发给多个 Writer 同时执行。

注意:原生的 log 包可以随时通过 SetOutput 函数修改日志输出行为,也就是说,如果你只想处理原生日志,无需自定义一个结构体,只需要在需要的时候通过 SetOutput 修改文件指向就行了。

4.将它作为 Gin 框架的输出

参见上面这条 注意 中的内容,显然,Gin框架的输出行为不能随时改变,他的函数是这个

// 设置 gin 默认的日志输出
gin.DefaultWriter = MyLogWriter()

// 创建 gin 实例
Router = gin.Default()

重点:一旦 gin 的实例被创建,就不能通过 gin.DefaultWriter() 这种方式修改他的日志输出了。我简单的看了一下,gin.Default() 函数会创建一个 Engine 对象,日志作为一个中间件被设置在了这个对象里。而 Engine 对象在运行时支持添加中间件,并不支持删除或者修改中间件。

此处的不支持修改其实不严谨,但我确实没找到 Gin 提供的修改这些中间件相关的函数,但其实他们是支持修改的,只不过呢,操作起来可能有些麻烦。你可以通过这种方式找到这些中间件,当然也包括需要动态修改的日志 Writer

你的gin实例.RouterGroup.Handlers

这里是一个 HandlerFunc 接口的数组,感觉这样改起来应该很复杂,所以我不建议通过这种方式实现。

所以咱们就老老实实的,在 gin 的实例被创建前,设置默认输出到我们自定义的 Writer,然后通过动态修改我们自己对象的方式,实现动态输出 gin 的日志。

实现效果:

 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

控场的朴哥

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值