Golang | Web开发之Gin路由访问日志自定义输出实践

news2024/11/19 21:28:04

欢迎关注「全栈工程师修炼指南」公众号

点击 👇 下方卡片 即可关注我哟!

设为星标⭐每天带你 基础入门 到 进阶实践 再到 放弃学习

专注 企业运维实践、网络安全、系统运维、应用开发、物联网实战、全栈文章 等知识分享

  花开堪折直须折,莫待无花空折枝 


作者主页:[ https://www.weiyigeek.top ]  

博客:[ https://blog.weiyigeek.top ]

作者<开发安全运维>学习交流群,回复【学习交流群】即可加入


文章目录:

0x02 如何自定义 Gin 日志格式?

1.自定义定义路由日志的格式

2.自定义原生路由访问日志格式

3.使用 log 自定义日志并按天分隔保存到文件

0x02 如何自定义 Gin 日志格式?

1.自定义定义路由日志的格式

描述: 此处介绍如何定义路由日志的格式,而非使用默认的路由访问日志格式。
例如:默认的路由日志格式 GIN-debug] POST /foo --> main.main.func1 (3 handlers), 如果你想要以指定的格式(例如 JSON,key values 或其他格式)记录信息,则可以使用 gin.DebugPrintRouteFunc 指定格式。

package main

import (
	"github.com/gin-gonic/gin"
	"log"
	"net/http"
)

func main() {
  // gin 运行模式
  gin.SetMode(gin.DebugMode)

	r := gin.Default()
  
  // 关键点
	gin.DebugPrintRouteFunc = func(httpMethod, absolutePath, handlerName string, nuHandlers int) {
		log.Printf("endpoint %v %v %v %v\n", httpMethod, absolutePath, handlerName, nuHandlers)
	}

	r.POST("/foo", func(c *gin.Context) {
		c.JSON(http.StatusOK, "foo")
	})

	r.GET("/bar", func(c *gin.Context) {
		c.JSON(http.StatusOK, "bar")
	})

	r.GET("/status", func(c *gin.Context) {
		c.JSON(http.StatusOK, "ok")
	})

	// 监听并在 0.0.0.0:8080 上启动服务
	r.Run()
}

执行效果:
ad9d62e5fd58909bd9567babccda0584.png


偷偷的告诉你哟?极客全栈修炼】微信小程序已经上线了,

可直接在微信里面直接浏览博主博客了哟,后续将上线更多有趣的小工具。


2.自定义原生路由访问日志格式

描述: 此处是使用 gin.LoggerWithFormatter & gin.LogFormatterParams 实现自定义路由访问日志。

代码示例:

package main

import (
  "fmt"
  "io"
  "os"
  "time"

  "github.com/gin-gonic/gin"
)

func main() {
  // 强制日志颜色化
  gin.ForceConsoleColor()
  // 禁用控制台颜色
  // gin.DisableConsoleColor()

  // 默认为 debug 模式,设置为发布模式
  gin.SetMode(gin.ReleaseMode)

  // 将日志同时写入文件和控制台,请使用以下代码 f 表示文件,os,os.Stdout 表示终端
  f, _ := os.Create("gin.log")
  // 如果需要同时将日志写入文件和控制台,请使用以下代码。
  gin.DefaultWriter = io.MultiWriter(f, os.Stdout)

  // 生成gin实例,即 WSGI 应用程序
  r := gin.New()

  // 自定义日志格式
  // LoggerWithFormatter 中间件会将日志写入 gin.DefaultWriter
  // By default =>  gin.DefaultWriter = os.Stdout
  r.Use(gin.LoggerWithFormatter(func(param gin.LogFormatterParams) string {
    // 自定义格式
    return fmt.Sprintf("%s - [%s] \"%s %s %s %d %s \"%s\" %s\"\n",
      param.ClientIP,
      param.TimeStamp.Format(time.RFC1123),
      param.Method,
      param.Path,
      param.Request.Proto,
      param.StatusCode,
      param.Latency,
      param.Request.UserAgent(),
      param.ErrorMessage,
    )
  }))
  r.Use(gin.Recovery())

  // 声明了一个路由及对应的处理函数 (匿名函数)
  r.GET("/log", func(c *gin.Context) {
    c.JSON(200, gin.H{
      "code": "200", "msg": "Test gin logs", "data": "",
    })
  })
  r.Run()
}

执行效果:
46191b2069aceb2a187016e4ccc5a3b2.png

3.使用 log 自定义日志并按天分隔保存到文件

描述: 此处使用原生的 log 模块实现自定义路由日志,并载入到Gin的日志中间件中,实现终端与文件同时输出,输入的日志的文件按照天进行分隔,好了,废话不多说直接上代码:

日志中间件: middleware\Logger.go

package middleware

import (
	"fmt"
	"io"
	"log"
	"os"
	"time"

	"github.com/gin-gonic/gin"
)

// Logger 是一个自定义的日志中间件
func Logger() gin.HandlerFunc {
	// 日志文件路径
	logFilePath := "./logs/"
	// 日志文件名前缀
	logFileName := "weiyigeek"
	// 日志文件后缀
	logFileExt := "log"
	// 日志文件最大大小,单位为 MB
	logFileMaxSize := 1000
	// 日志文件切割的时间间隔,单位为天
	logFileSplitDays := 1

	// 检查日志目录是否存在,不存在则创建
	err := os.MkdirAll(logFilePath, os.ModePerm)
	if err != nil {
		log.Fatalf("Failed to create log directory: %v", err)
	}

	// 获取当前时间的年月日
	now := time.Now()
	year, month, day := now.Date()

	// 构造日志文件名
	logFileName = fmt.Sprintf("%s-%d-%02d-%02d", logFileName, year, month, day)

	// 打开日志文件
	logFile, err := os.OpenFile(
		fmt.Sprintf("%s/%s.%s", logFilePath, logFileName, logFileExt),
		os.O_WRONLY|os.O_APPEND|os.O_CREATE,
		0666,
	)
	if err != nil {
		log.Fatalf("Failed to open log file: %v", err)
	}

	// 设置日志输出
	writers := []io.Writer{
		logFile,
		os.Stdout}
	log.SetOutput(io.MultiWriter(writers...))
	log.SetFlags(log.Ldate | log.Ltime | log.LUTC)

	return func(c *gin.Context) {
		// 处理请求前记录日志
		// 开始时间
		startTime := time.Now()
		// 调用该请求的剩余处理程序
		c.Next()
		// 结束时间
		endTime := time.Now()
		// 执行时间
		latencyTime := endTime.Sub(startTime)

		// 请求IP
		clientIP := c.ClientIP()
		// remoteIP := c.RemoteIP()

		// 请求方式
		reqMethod := c.Request.Method
		// 请求路由
		reqUri := c.Request.RequestURI
		// 请求协议
		reqProto := c.Request.Proto
		// 请求来源
		repReferer := c.Request.Referer()
		// 请求UA
		reqUA := c.Request.UserAgent()

		// 请求响应内容长度
		resLength := c.Writer.Size()
		if resLength < 0 {
			resLength = 0
		}
		// 响应状态码
		statusCode := c.Writer.Status()

		log.Printf(
			"%s | %3d | %s %10s | \033[44;37m%-6s\033[0m %s %s  | %10v | \"%s\" \"%s\"",
			colorForStatus(statusCode),
			statusCode,
			colorForStatus(0),
			clientIP,
			// remoteIP,
			reqMethod,
			reqUri,
			reqProto,
			latencyTime,
			reqUA,
			repReferer,
		)

		// 判断日志文件是否需要切割
		fileInfo, err := logFile.Stat()
		if err != nil {
			log.Fatalf("Failed to get log file info: %v", err)
		}
		_, _, lastDay := endTime.AddDate(0, 0, -1*logFileSplitDays).Date()
		if fileInfo.Size() > int64(logFileMaxSize*1024*1024) {
			// 关闭当前日志文件
			logFile.Close()
			// 构造新的日志文件名
			logFileName = fmt.Sprintf("%s-%s", logFileName, time.Now().Format("2006-01-02-15"))

			// 创建新的日志文件
			logFile, err = os.OpenFile(
				fmt.Sprintf("%s/%s.%s", logFilePath, logFileName, logFileExt),
				os.O_WRONLY|os.O_APPEND|os.O_CREATE,
				0666,
			)
			if err != nil {
				log.Fatalf("Failed to create log file: %v", err)
			}
			// 设置日志输出
			log.SetOutput(logFile)
			log.SetFlags(log.Ldate | log.Ltime | log.Lshortfile)
		} else if fileInfo.ModTime().Day() == lastDay {
			// 关闭当前日志文件
			logFile.Close()

			// 构造新的日志文件名
			logFileName = fmt.Sprintf("%s-%s", logFileName, endTime.Format("2006-01-02"))

			// 创建新的日志文件
			logFile, err = os.OpenFile(
				fmt.Sprintf("%s/%s.%s", logFilePath, logFileName, logFileExt),
				os.O_WRONLY|os.O_APPEND|os.O_CREATE,
				0666,
			)
			if err != nil {
				log.Fatalf("Failed to create log file: %v", err)
			}
			// 设置日志输出
			log.SetOutput(logFile)
			log.SetFlags(log.Ldate | log.Ltime | log.Lshortfile)
		}
	}
}

// colorForStatus 根据 HTTP 状态码返回 ANSI 颜色代码
func colorForStatus(code int) string {
	switch {
	case code >= 200 && code < 300:
		return "\033[42;1;37m" // green
	case code >= 300 && code < 400:
		return "\033[34m" // blue
	case code >= 400 && code < 500:
		return "\033[33m" // yellow
	case code == 0:
		return "\033[0m" // cancel
	default:
		return "\033[31m" // red
	}
}

亲,文章就要看完了,不关注一下【全栈工程师修炼指南】吗?

460a49746716310aa47684db73dcf3ec.jpeg

入口文件: main.go

package main

import (
	"devopsapi/middleware"
	router "devopsapi/routers"
	"fmt"
	"log"
	"net/http"
	"time"

	"github.com/gin-gonic/gin"
	"golang.org/x/sync/errgroup"
)

// 处理属于同一总体任务的子任务的goroutine的集合
var (
	g errgroup.Group
)

func main() {
	// 指定 gin 运行模式
	gin.SetMode(global.App.Mode)

	// 返回一个新的空白Engine实例
	r := gin.New()

	// 设置日志中间件
	r.Use(middleware.Logger())

	// 加载自定义路由
	router.Load(r)

	// Linux、Mac 环境下使用 fvbock/endless 艰辛平滑重启
	// err := endless.ListenAndServe(fmt.Sprintf("%s:%d", global.App.Host, global.App.Port), r)
	// if err != nil || err != http.ErrServerClosed {
	// 	log.Println("err:", err)
	// }

	// W通用:开放监听运行Gin服务
	server := &http.Server{
		// Gin运行的监听端口
		Addr: ":8080",
		// 要调用的处理程序,http.DefaultServeMux如果为nil
		Handler: r,
		// ReadTimeout是读取整个请求(包括正文)的最长持续时间。
		ReadTimeout: 5 * time.Second,
		// WriteTimeout是超时写入响应之前的最长持续时间
		WriteTimeout: 10 * time.Second,
		// MaxHeaderBytes控制服务器解析请求标头的键和值(包括请求行)时读取的最大字节数 (通常情况下不进行设置)
		MaxHeaderBytes: 1 << 20,
	}

	// 创建 goroutine 中调用给定的函数
	g.Go(func() error {
		return server.ListenAndServe()
	})
  
  // goroutine 所有函数调用都返回,然后从中返回第一个非零错误(如果有的话)。
	if err := g.Wait(); err != nil {
		log.Fatal(err)
	}
}

执行结果:
日志实现格式: 2023/06/09 09:59:04 [42;1;37m | 200 | [0m 10.20.172.103 | [44;37mGET [0m /app/version HTTP/1.1 | 144.4µs | "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/113.0.0.0 Safari/537.36 Edg/113.0.1774.57" ""
5c1339cc387a4d6ed48f95dfbe1fdbdc.png

本文至此完毕,更多技术文章,尽情等待下篇好文!

原文地址: https://blog.weiyigeek.top/2023/6-2-745.html

如果此篇文章对你有帮助,请你将它分享给更多的人! 

06c5016b3c7abe2f68e09f0e1c10e523.gif

b170e278a18bbef9ffc999df98b935ca.png 学习书籍推荐 往期发布文章 b7f0ff7b0cd2bda1dc6c7c0cb1dbdedf.png

公众号回复【0008】获取【Ubuntu22.04安装与加固脚本】

公众号回复【10001】获取【WinServer安全加固脚本】

公众号回复【10002】获取【KylinOS银河麒麟安全加固脚本】

公众号回复【0011】获取【k8S二进制安装部署教程】

公众号回复【0014】获取【Nginx学习之路汇总】

公众号回复【0015】获取【Jenkins学习之路汇总】

公众号回复【10005】获取【adb工具刷抖音赚米】

 热文推荐  

  • Golang | Web开发之Gin框架快速入门基础实践

  • Go开发学习 | 如何快速读取json/yaml/ini等格式的配置文件使用示例

  • Go开发学习 | 如何使用Gomail.v2模块包发送邮箱验证码消息及附件学习记录

  • Go开发学习 | 如何使用日志记录模块包针对日志按天数、按大小分隔文件示例

  • 开发基础 | Golang语言的RESTfulAPI接口设计规范快速入门

欢迎长按(扫描)二维码 取更多渠道哟!

79e45baccb4ad8627618b14f43de35a7.gif

欢迎关注 【全栈工程师修炼指南】(^U^)ノ~YO

添加作者微信【weiyigeeker 】 一起学习交流吧!

关注回复【学习交流群】即可加入【安全运维沟通交流小群

温馨提示: 由于作者水平有限,本章错漏缺点在所难免,希望读者批评指正,若有问题或建议请在文章末尾留下您宝贵的经验知识,或联系邮箱地址

master@weiyigeek.top 或 关注公众号 [全栈工程师修炼指南] 留言。

点个【赞 + 在看】吧!

点击【"阅读原文"】获取更多有趣的知识!   

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

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

相关文章

2022届本科毕业生10大高薪专业:大数据专业进入top3

对于普通人来讲&#xff0c;报考一个高薪的职业还是重中之重。那么什么专业高薪呢&#xff0c;很多人觉得是程序员&#xff0c;但这是职业而不是大学专业&#xff0c;专业千千万&#xff0c;选什么好呢&#xff0c;接下来看一看。 最近国家统计局发布了2022年城镇单位就业人员…

MMU翻译的时候以哪种level去执行是什么意思

【问题】 以哪个el去执行是什么意思&#xff1f;执行这条指令就会切到切换指令里指定的el吗&#xff1f; 【回答】 在一个core中&#xff0c;至少有一下Translation regime&#xff0c;AT S12E2R, <Xt> 就是使用EL2 Translation regime完成地址翻译。 Secure EL1&…

Cadence Allegro PCB设计88问解析(二十八) 之 Allegro中dimension environment命令使用(添加及删除尺寸标注)

一个学习信号完整性仿真的layout工程师 最近看到关于Anti Etch的设置&#xff0c;因为本人之前在layout设计是使用过这个命令。后来去到别的公司就不用了&#xff0c;从网上看到说这个命令是用来负片设计的。在这里在说下正片和负片的概念&#xff1a; 正片&#xff1a;是指在a…

机器学习|监督学习|无监督学习|8:20~9:20

目录 一、监督学习(Supervised learning) ​​​​​​​2.1分类(classification) 2.2回归(regression) 泛化能力 Generalization Ability 欠拟合 过拟合 不收敛 2.3 K近邻算法 k近邻分类​ k近邻回归 KNN变种 二、无监督学习(Unsupervised learning) 2.1 聚类(c…

[迁移学习]域自适应代码解析

一、概述 代码来自&#xff1a;https://github.com/jindongwang/transferlearning&#xff0c;可以前往github下载代码&#xff0c;本文涉及的代码的位置为&#xff1a;Code->DeepDA。理论基础可以参见&#xff1a;[迁移学习]域自适应 整体网络结构如下&#xff1a;可以视为…

Win7下静态变量析构导致进程卡死无法退出问题解决

项目中在用户机器Win7系统上好几次出现进程卡死&#xff0c;无法退出&#xff0c;在用户机器上抓取了dump&#xff0c;发现是在DllMain函数中执行了静态变量的析构&#xff0c;这个静态变量析构的时候会使用std::condition_variable 类型的成员变量通知其他线程退出。同时本地在…

PDF怎样转换成长图?这个方法,超级简单!

在当今社会&#xff0c;PDF文档广泛应用于各个领域。然而&#xff0c;在某些情况下&#xff0c;我们可能需要将多个PDF页面合并成一个单独的长图&#xff0c;以便更方便地浏览、共享或嵌入到其他文件中。为了满足这一需求&#xff0c;记灵在线工具应运而生&#xff0c;它为我们…

一种全新的图像变换理论的实验(六)——研究目的替代DCT和小波

一、变换算法在图像视频中的核心作用 我们国产的变换算法是比较少的&#xff0c;基本上都是在小波、DCT和FFT上发展优化升级的应用。我之前的文章给出了一种基于加权概率模型的变换算法&#xff0c;该算法在一定的程度上能有效的保存低频数据。而且我基于该算法给出了一些新的…

微信小程序快速开发— TDesign模版初始化

最近有个商城类的小程序业务需要快速上线&#xff0c;看了一下微信官方的模版库&#xff0c;相中了TDesign&#xff0c;调研了半天&#xff0c;决定就从这个开始干。 调研的两个重点&#xff1a; 1、网络请求&#xff0c;即数据获取 2、模板本身存在些bug&#xff0c;如&…

从Kotlin中return@forEach了个寂寞

点击上方蓝字关注我&#xff0c;知识会给你力量 今天在Review&#xff08;copy&#xff09;同事代码的时候&#xff0c;发现了一个问题&#xff0c;想到很久之前&#xff0c;自己也遇到过这个问题&#xff0c;那么就来看下吧。首先&#xff0c;我们抽取最小复现代码。 (1..7).f…

Python 基于人脸识别的实验室智能门禁系统的设计与实现,附源码

1 简介 本基于人脸识别的实验室智能门禁系统通过大数据和信息化的技术实现了门禁管理流程的信息化的管理操作。平台的前台页面通过简洁的平台页面设计和功能结构的分区更好的提高用户的使用体验&#xff0c;没有过多的多余的功能&#xff0c;把所有的功能操作都整合在功能操作…

聚观早报|微软Xbox2023发布会汇总;苹果VisionPro头显低配版曝光

今日要闻&#xff1a;微软Xbox 2023发布会汇总&#xff1b;苹果Vision Pro头显低配版曝光&#xff1b;台积电在熊本县建设半导体工厂&#xff1b;苹果今年或能出货2.4亿台&#xff1b;中国含氯废塑料高效无害升级回收 微软Xbox 2023发布会汇总 6 月 12 日凌晨&#xff0c;微软…

Java 实战介绍 Cookie 和 Session 的区别

HTTP 是一种不保存状态的协议&#xff0c;即无状态协议&#xff0c;HTTP 协议不会保存请求和响应之间的通信状态&#xff0c;协议对于发送过的请求和响应都不会做持久化处理。 无状态协议减少了对服务压力&#xff0c;如果一个服务器需要处理百万级用户的请求状态&#xff0c;对…

Linux教程——Linux绝对路径和相对路径详解

在 Linux 中&#xff0c;简单的理解一个文件的路径&#xff0c;指的就是该文件存放的位置&#xff0c;只要我们告诉 Linux 系统某个文件存放的准确位置&#xff0c;那么它就可以找到这个文件。 指明一个文件存放的位置&#xff0c;有 2 种方法&#xff0c;分别是使用绝对路径和…

深度解读 KaiwuDB 的排序操作

一、单节点执行 在单节点环境执行一条简单的 SQL 语句 SELECT * FROM NATION ORDER BY N_NAME。NATION 是一张小表&#xff0c;只有 25 条记录&#xff1b;对第 2 列 N_NAME 进行升序排列。 1. 抽象语法树 上述示例中的 SQL 语句经过分析器解析后得到 AST&#xff0c;如下图…

(文章复现)面向配电网韧性提升的移动储能预布局与动态调度策略(2)-灾后调度matlab代码

参考文献&#xff1a; [1]王月汉,刘文霞,姚齐,万海洋,何剑,熊雪君.面向配电网韧性提升的移动储能预布局与动态调度策略[J].电力系统自动化,2022,46(15):37-45. 1.基本原理 1. 1 目标函数 在灾害发生后&#xff0c;配电网失去主网供电&#xff0c;设故障的持续时间可根据灾害…

基于SpringBoot+Vue的酒店管理系统设计与实现

博主介绍&#xff1a; 大家好&#xff0c;我是一名在Java圈混迹十余年的程序员&#xff0c;精通Java编程语言&#xff0c;同时也熟练掌握微信小程序、Python和Android等技术&#xff0c;能够为大家提供全方位的技术支持和交流。 我擅长在JavaWeb、SSH、SSM、SpringBoot等框架下…

代码随想录 二叉树 Java(二)

文章目录 &#xff08;*中等&#xff09;222. 完全二叉树的节点个数&#xff08;*简单&#xff09;110. 平衡二叉树&#xff08;*简单&#xff09;257. 二叉树的所有路径&#xff08;简单&#xff09;404. 左叶子之和&#xff08;简单&#xff09;513. 找树左下角的值&#xff…

设计模式的原则(一)

相信自己&#xff0c;无论自己到了什么局面&#xff0c;请一定要继续相信自己。 新的世界开始了&#xff0c;接下来&#xff0c;老蝴蝶带领大家学习一下设计模式。 我们先了解一下 设计原则 一.设计模式 一.一 设计原则 设计模式常用的七大原则: 单一职责原则接口隔离原则…

【项目】接入飞书平台

前言 项目有和飞书打通的需求&#xff0c;因为是第一次打通&#xff0c;摸索过程还是花了些时间的&#xff0c;现在相关笔记分享给大家。 步骤 1、熟悉开发文档 熟悉飞书的开发文档&#xff1a;开发文档 &#xff0c;找到你需要的接口&#xff0c;拿我为例&#xff0c;我需…