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

news2024/11/26 12:22:18

目录

摘要

痛点

正文

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 的日志。

实现效果:

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/643637.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

DAY21——二叉树part7

1.二叉搜索树最小差值 二叉搜索树中序遍历得到有序的数字序列&#xff0c;记录前一个节点 class Solution {TreeNode pre;int result Integer.MAX_VALUE;public int getMinimumDifference(TreeNode root) {if(rootnull)return 0;traversal(root);return result;}private voi…

【Java算法题】剑指offer_数据结构之03队列栈

前言 刷题链接&#xff1a; https://www.nowcoder.com/exam/oj/ta?page2&tpId13&type265 原定于5.30写完队列&栈&#xff0c;超时了14天&#xff08;2周&#xff09;&#xff0c;于6.13完成。 刷算法题到现在得出一个心得&#xff0c;万事开头难。没刷之前总觉得…

django的项目结构介绍

目录 django的安装django项目创建django项目启动django项目关闭django项目个个文件分析核心文件 manage.py项目内部文件start01/start01setting文件的讲解 django的安装 pip install django检测安装后的版本 python -m django --versiondjango项目创建 django安装以后 会自动…

面向对象的多态

7. 面向对象特征三&#xff1a;多态性 概念 多态是面向对象程序设计&#xff08;OOP&#xff09;的一个重要特征&#xff0c;指同一个实体同时具有多种形式&#xff0c;即同一个对象&#xff0c;在不同时刻&#xff0c;代表的对象不一样&#xff0c;指的是对象的多种形态。 变…

作为软件工程师对Docker的认知和看法

文章目录 前言一、Docker是什么&#xff1f;二、Docker和Virtual Machine的区别三、Docker架构1. Client2. Docker Host3. Registry 四、Docker在实际应用中的好处配置环境网络和卷更新管理 总结 前言 两年前我还没有接触过Docker&#xff0c;也不理解Docker在自动化工程应用中…

k8s如何使用ceph rbd块存储(静态供给、存储类动态供给)

目录 前言安装ceph集群ceph集群创建rbd块存储rbd块存储不支持ReadWriteManyk8s配置rbd块存储&#xff08;静态供给&#xff09;创建secret创建pv创建pvck8s节点安装客户端依赖包部署pod查看pod验证是否持久化 k8s配置rbd块存储&#xff08;动态供给&#xff09;查看官网ceph集群…

mysql 最常用的一些语句

1 数据库相关操作 CREATE DATABASE IF NOT EXISTS daily-test DEFAULT CHARSET utf8 COLLATE utf8_general_ci&#xff1b; drop database daily_test; use daily_test 具体操作如下图上所示&#xff1a; 2 mysql常用数据类型 MySQL 数据类型 | 菜鸟教程 3 数据库表相关操作…

Stimulsoft Forms.WEB 23.2.6 Crack

Stimulsoft Forms.WEB 发布 创建、编辑、填写、发布和分发交互式表单。 2023 年 6 月 13 日 - 9:34 新产品 特征 您可以为几乎任何目的创建任何类型的文档 - 发票和支票、各种调查问卷和工作表、订单、简历等等。用户收到可编辑的 PDF 格式的完成模板&#xff0c;可以在任何现代…

英伟达开发板学习系列---国产【Jetson Xavier NX】系统安装及基础配置

1. 前言 最近新买了Jetson Xavier NX, 和之前英伟达原厂的NX的区别在于国产Jetson Xavier NX 是核心板使用的是英伟达的&#xff0c;扩展板是国产的。具体详情如下&#xff1a; 官方NX和国产NX详情区别 2. 设置系统从固态硬盘启动 官方NX出厂是直接将SD卡&#xff08;64/12…

Redis GEO地理位置信息的应用

Redis GEO地理位置信息的应用 Redis GEO概述应用场景Redis GEO命令GEO命令演示 Redis GEO实现附近人的功能基础类API接口接口实现执行测试 Redis GEO 概述 Redis的GEO操作是一种基于地理位置信息进行操作的功能。它使用经度和纬度坐标来表示地理位置&#xff0c;支持存储地理位…

湖南大学CS-2020期末考试解析

【特别注意】 答案来源于@wolf 是我在备考时自己做的,仅供参考,若有不同的地方欢迎讨论。 【试卷评析】 有必要一做。 【试卷与答案】 1.简答题(10 分) 假设一个基于 IEEE 浮点格式的 10 位浮点表示,有 1 个符号位,4 个阶码位(k=4)和 5 个 尾数位(n=5)。 (…

湖南大学CS-2018期末考试解析

【特别注意】 答案来源于@wolf 是我在备考时自己做的,仅供参考,若有不同的地方欢迎讨论。 【试卷评析】 有必要一做。 【试卷与答案】 一、选择题(每题 2 分,共 10 分) 1. 0x12345678 存放在采用小端存储的机器上,地址为 0x100 到

湖南大学CS-2017(另一张)期末考试解析

【特别注意】 答案来源于wolf 是我在备考时自己做的&#xff0c;仅供参考&#xff0c;若有不同的地方欢迎讨论。 【试卷评析】 有必要一做。 【试卷与答案】 由于这张试卷没有电子版&#xff0c;我就直接拍我自己的作答了

八大排序算法之归并排序(递归实现+非递归实现)

目录 一.归并排序的基本思想 归并排序算法思想(排升序为例) 二.两个有序子序列(同一个数组中)的归并(排升序) 两个有序序列归并操作代码: 三.归并排序的递归实现 递归归并排序的实现:(后序遍历递归) 递归函数抽象分析: 四.非递归归并排序的实现 1.非递归归并排序算法…

C:\Users\BC>conda -V ‘conda‘ 不是内部或外部命令,也不是可运行的程序 或批处理文件。

C:\Users\BC>conda -V ‘conda’ 不是内部或外部命令&#xff0c;也不是可运行的程序 或批处理文件。 注意&#xff01;&#xff1a;Anaconda安装路径和Scripts路径&#xff0c;两个都添加进去Path 解释&#xff1a;将 Anaconda 安装路径和 Scripts 路径都添加到系统的 PA…

css属性计算过程

CSS 属性计算过程 你是否了解 CSS 的属性计算过程呢&#xff1f; 有的同学可能会讲&#xff0c;CSS属性我倒是知道&#xff0c;例如&#xff1a; p{color : red; }上面的 CSS 代码中&#xff0c;p 是元素选择器&#xff0c;color 就是其中的一个 CSS 属性。 但是要说 CSS 属…

丢失d3dcompiler47.dll怎么办,这个五个修复方法都可以解决

打开游戏或者软件的时候&#xff0c;电脑提示由于找不到d3dcompiler_47.dll&#xff0c;无法继续执行此代码怎么办&#xff0c;其实修复起来不难。首先需要先知道怎么是dll文件&#xff0c;dll文件可以简单的把库文件看成一种代码仓库&#xff0c;它提供给使用者一些可以直接拿…

【学习笔记】Spring Cloud

1、Spring Cloud简介&#xff1a; 成熟的微服务框架&#xff0c;定位为开发人员提供工具&#xff0c;以快速构建分布式系统 2、Spring Cloud核心组件 服务注册中心&#xff1a;Spring Cloud Netflix Eureka&#xff0c;会启动一个Eureka Serve&#xff0c;把其他的组件作为E…

Git分布式版本控制系统

Githttps://git-scm.com/ 1. Git简介 Git是一个开源的分布式版本控制系统&#xff0c;可以有效、高速地处理从很小到非常大的项目版本管理。分布式相比于集中式的最大区别在于开发者可以提交到本地&#xff0c;每个开发者通过克隆&#xff08;git clone&#xff09;&#xff…

梳理Retrofit的知识体系

作者&#xff1a;RainyJiang 在学习Retrofit后&#xff0c;由于它本身就是OKHttp的封装&#xff0c;面试中也经常会被一起问到&#xff1b;单纯的解析它的源码学习难免会有点无从下手&#xff0c;往往让人抓不住重点&#xff0c;学习效率并不是很高&#xff0c;本文从提出几个问…