Golang实践录:gin框架使用自定义日志模块

本文介绍在 Golang 的 gin 框架中使用自定义日志模块的一些方法。

背景

很早之前就实现并使用了自己封装的日志模块,但一直没有将gin框架内部的日志和日志模块结合。gin的日志都是在终端上打印的,排查问题不方便。趁五一假期,集中研究把此事了了。

实践

gin支持中间件,对于一些常用接口,也提供了自定义函数。本文主要是从这2方面着手。

据官方demo,在初始化时基本都使用如下语句初始化内部日志(Logger())和panic恢复功能(Recovery())。

router.Use(gin.Logger())
router.Use(gin.Recovery())

默认输出日志:

[GIN] 2024/05/08 - 09:09:13 | 200 |    1.082244ms |             ::1 | GET      "/info"

自定义Writer

gin对外提供了DefaultWriter,类型为io.Writer,默认输出终端,定义如下:

var DefaultWriter io.Writer = os.Stdout

因此可以修改该参数达到自定义输出日志的目的。代码如下:

f, _ := os.OpenFile(filepath.Join("./", "gin.log"), os.O_RDWR|os.O_APPEND|os.O_CREATE, 0666)
gin.DefaultWriter = io.MultiWriter(f)

上述代码在当前目录创建gin.log文件,将gin的日志输出到该文件,效果如下:

[GIN] 2024/05/08 - 09:10:54 | 200 |     844.357µs |             ::1 | GET      "/info"

评:自由度不够。

使用中间件

调用语句router.Use(gin.Logger())中的gin.Logger()实际可理解为中间件函数。函数定义:

func XXX() HandlerFunc {
...
}type HandlerFunc func(*Context)

自定义的中间件函数模板:

func XXXs() gin.HandlerFunc {return func(c *gin.Context) {
...// 下一处理c.Next()
...}
}

日志中间件实现如下:

func filterLogs() gin.HandlerFunc {return func(c *gin.Context) {// Start timerstart := time.Now()path := c.Request.URL.Path// Process requestc.Next()// Stop timerlatency := time.Now().Sub(start)clientIP := c.ClientIP()method := c.Request.MethodstatusCode := c.Writer.Status()klog.Printf("middleware | %3d | %13v | %15s | %-7s %#v\n", statusCode, latency,clientIP, method, path)}
}调用:
router.Use(filterLogs())

注:klog.Printf为自封装的日志模块输出函数。源码可参考klog项目。

效果如下:

[2024-05-08 09:44:34 515] [INFO] middleware | 404 |         738ns |             ::1 | GET     "/info"

评:参数gin框架的日志中间件实现简洁版本,自由度较大。可在此函数中再做其它处理。如统计某path的次数,等。

使用日志格式化回调函数

gin中自带了日志格式化的函数LoggerWithFormatter,该函数参数为函数,原型为type LogFormatter func(params LogFormatterParams) string。其本意是可以自定义输出的日志的字段内容,因此返回值为字符串,默认输出还是使用gin.DefaultWriter

因此,可以在该函数中添加日志的打印,但不返回字符串,这样gin框架就不会输出日志了。代码如下:

router.Use(gin.LoggerWithFormatter(func(param gin.LogFormatterParams) string {klog.Printf("| %3d | %13v | %15s | %-7s %#v\n%s", param.StatusCode, param.Latency,param.ClientIP, param.Method, param.Path, param.ErrorMessage)return ""
}))

效果如下:

[2024-05-08 09:44:34 516] [INFO] | 200 |    1.580895ms |             ::1 | GET     "/info"

评:利用现有的中间件,自实现部分代码较简洁。可在此函数中再做其它处理。如统计某path的次数,等。

Recovery中间件实现

gin框架默认的Recovery处理函数gin.Recovery()。与上类似,输出信息也是使用内部日志模块的。为保存可能出现的panic信息,因此需要重新实现。由于原代码已经很完备,只是日志与需求不符,因此并无大改,具体代码如下:

// panic日志记录 替换gin的Recovery函数
func filterRecovery() gin.HandlerFunc {return func(c *gin.Context) {defer func() {if err := recover(); err != nil {// Check for a broken connection, as it is not really a// condition that warrants a panic stack trace.var brokenPipe boolif ne, ok := err.(*net.OpError); ok {if se, ok := ne.Err.(*os.SyscallError); ok {if strings.Contains(strings.ToLower(se.Error()), "broken pipe") ||strings.Contains(strings.ToLower(se.Error()), "connection reset by peer") {brokenPipe = true}}}stack := com.GetStack(3)httpRequest, _ := httputil.DumpRequest(c.Request, false)headers := strings.Split(string(httpRequest), "\r\n")for idx, header := range headers {current := strings.Split(header, ":")if current[0] == "Authorization" {headers[idx] = current[0] + ": *"}}headersToStr := strings.Join(headers, "\r\n")if brokenPipe {klog.Printf("%s\n%s", err, headersToStr)} else {klog.Printf("[Recovery] panic recovered:\n%s\n%s", err, stack)}if brokenPipe {// If the connection is dead, we can't write a status to it.c.Error(err.(error)) // nolint: errcheckc.Abort()}}}()c.Next()}
}使用:
router.Use(filterRecovery())

小结

本文介绍的几种方法,基本上都可达到使用自定义日志的目的。笔者实际工程中使用中间件形式,可定制性较高。

附:gin框架日志源码跟踪

外部使用:router.Use(gin.Logger())
--> LoggerWithConfig--> 如未指定formatter,则用defaultLogFormatter--> 如未指定日志输出Write,则用DefaultWriter--> 记录无需要输出的path,存放于skip--> 有请求,遍历skip,如找不到,则组装LogFormatterParams,使用fmt.Fprint输出日志。

从上述流程可以看出,可以通过指定formatter在框架里内嵌自定义的函数,由于会调用fmt.Fprint,因此formatter不返回字符串。当然,可实现自己的中间件替换gin.Logger()

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

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

相关文章

YOLOv8预测流程-原理解析[目标检测理论篇]

接下来是我最想要分享的内容,梳理了YOLOv8预测的整个流程,以及训练的整个流程。 关于YOLOv8的主干网络在YOLOv8网络结构介绍-CSDN博客介绍了,为了更好地介绍本章内容,还是把YOLOv8网络结构图放在这里,方便查看。 1.前言…

1053: 输出利用先序遍历创建的二叉树中的指定结点的度

解法&#xff1a; c语言 #include<iostream> #include<vector> using namespace std; typedef struct tNodes{char val;tNodes* left, * right; }* tNode;void creat(tNode& t) {char ch;cin >> ch;if (ch #) t NULL;else {t new tNodes;t->val …

2024高安全个人密码本程序源码,贴身密码管家-随机密码备忘录二代密码

项目概述&#xff1a; 在这个网络高度发展的时代&#xff0c;每个人都需要上网&#xff0c;而上网就不可避免地需要使用账号和密码。 在众多账号的情况下&#xff0c;你是否还在为复杂难记的密码感到烦恼&#xff1f;现在只需要记录一次&#xff0c; 就可以随时查看你的密码…

微信小程序绕开wx.uploadFile,上传图片,(直接把图片上传到服务器)

用wx.request是可以上传文件的&#xff0c;区别在于需要通过路径读取文件&#xff0c;上传文件&#xff0c; 而wx.uploadFile的方法只需要用路径&#xff0c;无需读取文件 wx.chooseMedia({count: 1,mediaType: [image], // 只允许选择图片sourceType: [album, camera], // 可…

AI跟踪报道第41期-新加坡内哥谈技术-本周AI新闻:本周Al新闻: 准备好了吗?事情即将変得瘋狂

每周跟踪AI热点新闻动向和震撼发展 想要探索生成式人工智能的前沿进展吗&#xff1f;订阅我们的简报&#xff0c;深入解析最新的技术突破、实际应用案例和未来的趋势。与全球数同行一同&#xff0c;从行业内部的深度分析和实用指南中受益。不要错过这个机会&#xff0c;成为AI领…

Python函数基础详解(狠狠爱住)

函数的定义 在Python中&#xff0c;函数定义是指创建函数并定义其行为的过程。 一个简单的函数定义示例&#xff1a; def greet(name):"""这是一个向用户打招呼的函数"""print(f"Hello, {name}!")greet("Alice")在上面的…

【matlab基础知识代码】(十八)无约束最优化问题

min下面的x称为优化向量或者是决策变量 匿名函数法 >> f(x)(x(1)^2-2*x(1))*exp(-x(1)^2-x(2)^2-x(1)*x(2)); x0[0; 0]; [x,b,c,d]fminsearch(f,x0), x 0.6111 -0.3056 b -0.6414 c 1 d 包含以下字段的 struct: iterations: 72 funcCount: 137 algor…

STM32:GPIO输入输出

文章目录 1、GPIO介绍1.1 GPIO的基本结构1.1 GPIO的位结构 2、 GPIO工作模式3、GPIO标准外设库接口函数3.1 RCC接口函数3.2 GPIO接口函数3.2.1 GPIO的读取函数3.2.1 GPIO的写入函数 4、GPIO的初始化 1、GPIO介绍 GPIO&#xff08;General Purpose Input Output&#xff09;通用…

bugfix:若依框架下对某个url接口权限放行,登陆后调用却提示403无权限

引言 在构建基于若依(RuoYi)框架的现代Web应用时&#xff0c;API接口的安全与权限管理是不可或缺的一环。Spring Security作为业界广泛采用的安全框架&#xff0c;为若依提供了强大的权限控制能力。然而&#xff0c;在实际开发过程中&#xff0c;开发者常会遇到登录用户访问特…

synchronized对象锁和类锁

一、什么是对象锁和类锁 顾名思义 对象锁可以锁住同一对象下synchronized修饰的方法&#xff0c;但不会影响其他对象。synchronized修饰普通方法就为对象锁。 类锁static synchronized修饰一个方法&#xff0c;当一个线程在执行该方法时&#xff0c;其他任何线程用任何对象调…

软件测试之 接口测试 Postman使用

接口测试 URL HTTP协议 HTTP 请求部分 HTTP响应部分 Postman使用 界面介绍 这里 注意 如果你无法访问 那么 captchaImage这个打错了&#xff0c;给的资料中是错误的地址 https://kdtx-test.itheima.net/api/captchaImage登录接口 科大天下 第一个接口的登录设置 https://kd…

使用map类型的参数在mapper.xml中使用案例

使用map类型的参数在mapper.xml中使用案例 简介&#xff1a;在常见的开发中&#xff0c;对于参数的装载一般使用map类型方式&#xff0c;这样可以避免创建很多参数实体类&#xff0c;不管嵌套多层的数据参数都可以通过map拿取&#xff0c;对于嵌套多层的map&#xff0c;我们需…

如何在Java中使用异常处理机制

如何在Java中使用异常处理机制在Java中&#xff0c;异常处理是一种在程序运行时识别和响应错误情况的机制。它允许程序在出现异常时继续执行&#xff0c;而不是崩溃。Java异常处理机制的核心概念包括try、catch、finally、throw和throws关键字。下面详细介绍这些概念&#xff0…

已知哈夫曼节点个数,求哈夫曼字符编码数

哈夫曼编码(Huffman Coding)是一种用于无损数据压缩的嫡编码(权编码)算法。 在哈夫曼树中&#xff0c;每个叶子节点都代表一个字符&#xff0c;而节点的权重通常代表字符的频率。在哈夫曼编码中&#xff0c;每个字符都会被赋予一个二进制编码。为了获得这些编码&#xff0c;我…

云渲染动画300帧需要多久呢?瑞云渲染为你揭秘

在动画制作过程中&#xff0c;渲染的速度非常关键。对于一个项目需要渲染的300帧来说&#xff0c;由于硬件的限制&#xff0c;许多公司的设备可能无法快速完成这项任务。此时&#xff0c;借助云渲染服务的强大计算能力&#xff0c;可以显著减少完成时间&#xff0c;从而提速整个…

独孤思维:副业拉新排名第一很牛吗

01 30年河东&#xff0c;30年河西。 我从来不觉得&#xff0c;自己星球拉新第一&#xff0c;或者小报童直播一场卖80份有多牛。 因为比我牛比的&#xff0c;比我拉新多的大佬多了去了。 这个5月&#xff0c;比我潜在爆发的牛人也有很多。 过去的历史成绩&#xff0c;代表当…

【Java基础】初识正则表达式

正则表达式只适用于字符串 匹配matches 实际使用的是String类中定义的方法boolean matches(String regex) public static void piPei( ){String regex"[1][356789]\\d{9}";boolean boo"14838384388".matches(regex);System.out.println(boo); }验证qq号…

QtConcurrent::run操作界面ui的注意事项(2)

前面的“QtConcurrent::run操作界面ui的注意事项&#xff08;1&#xff09;”&#xff0c;末尾说了跨线程的问题&#xff0c;引出了Qt千好万好&#xff0c;就是跨线程不好。下面是认为的最简单的解决办法&#xff1a;使用QMetaObject::invokeMethod&#xff08;相比较信号-槽&a…

构建无服务器数仓(二)Apache DolphinScheduler 集成以及 LOB 粒度资源消费分析

引言 在数据驱动的世界中&#xff0c;企业正在寻求可靠且高性能的解决方案来管理其不断增长的数据需求。本系列博客从一个重视数据安全和合规性的 B2C 金融科技客户的角度来讨论云上云下混合部署的情况下如何利用亚马逊云科技云原生服务、开源社区产品以及第三方工具构建无服务…

18 分页:介绍

目录 简单例子 页表存在哪里 列表中究竟有什么 分页&#xff1a;也很慢 内存追踪 小结 在解决大多数空间管理问题上面&#xff0c;操作系统有两种方法&#xff1a; 第一种就是将空间分割成不同长度的分片&#xff0c;类似于虚拟内存管理中的分段&#xff0c;但是这个方法…