欢迎关注「全栈工程师修炼指南」公众号
点击 👇 下方卡片 即可关注我哟!
设为「星标⭐」每天带你 基础入门 到 进阶实践 再到 放弃学习!
专注 企业运维实践、网络安全、系统运维、应用开发、物联网实战、全栈文章 等知识分享
“ 花开堪折直须折,莫待无花空折枝。 ”
作者主页:[ 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()
}
执行效果:
偷偷的告诉你哟?【极客全栈修炼】微信小程序已经上线了,
可直接在微信里面直接浏览博主博客了哟,后续将上线更多有趣的小工具。
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()
}
执行效果:
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
}
}
亲,文章就要看完了,不关注一下【全栈工程师修炼指南】吗?
入口文件: 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" ""
本文至此完毕,更多技术文章,尽情等待下篇好文!
原文地址: https://blog.weiyigeek.top/2023/6-2-745.html
如果此篇文章对你有帮助,请你将它分享给更多的人!
学习书籍推荐 往期发布文章
公众号回复【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接口设计规范快速入门
欢迎长按(扫描)二维码 ,获取更多渠道哟!
欢迎关注 【全栈工程师修炼指南】(^U^)ノ~YO
添加作者微信【weiyigeeker 】 一起学习交流吧!
关注回复【学习交流群】即可加入【安全运维沟通交流小群】
温馨提示: 由于作者水平有限,本章错漏缺点在所难免,希望读者批评指正,若有问题或建议请在文章末尾留下您宝贵的经验知识,或联系邮箱地址
master@weiyigeek.top 或 关注公众号 [全栈工程师修炼指南] 留言。
点个【赞 + 在看】吧!
点击【"阅读原文"】获取更多有趣的知识!