gin: 用zap记录访问日志
一,zap库安装
$ go get -u go.uber.org/zap
go: added go.uber.org/multierr v1.11.0
go: added go.uber.org/zap v1.27.0
二,代码
accesslog
package middleware
import (
"bytes"
"fmt"
"github.com/gin-gonic/gin"
"go.uber.org/zap"
"io"
"mediabank/global"
"os"
"time"
)
type responseWriter struct {
gin.ResponseWriter
b *bytes.Buffer
}
//重写 Write([]byte) (int, error) 方法
func(w responseWriter)Write(b []byte)(int, error) {
//向一个bytes.buffer中写一份数据来为获取body使用
w.b.Write(b)
//完成gin.Context.Writer.Write()原有功能
return w.ResponseWriter.Write(b)
}
func AccessLog() gin.HandlerFunc {
return func(c *gin.Context) {
//请求 header
requestHeader := c.Request.Header
requestHeaderStr:=fmt.Sprintf("%v",requestHeader)
fmt.Println("请求头:",requestHeaderStr)
//请求体 body
requestBody := ""
b, err := c.GetRawData()
if err != nil {
requestBody = "failed to get request body"
} else {
requestBody = string(b)
}
c.Request.Body = io.NopCloser(bytes.NewBuffer(b))
fmt.Println("请求体:",requestBody)
writer := responseWriter{
c.Writer,
bytes.NewBuffer([]byte{}),
}
c.Writer = writer
beginTime := time.Now().UnixNano()
c.Next()
endTime := time.Now().UnixNano()
duration:=endTime-beginTime
//判断当前日志目录是否存在
var curDir = "./logs"
if global.IsDirExists(curDir) == false {
// 创建多级目录
err := os.MkdirAll(curDir, 0755)
if err != nil {
fmt.Println(err)
}
}
//得到当前日期
var curDate = global.FormattedNow("20060102")
cfg := zap.NewProductionConfig()
cfg.OutputPaths = []string{
"./logs/access_"+curDate+".log",
//"stderr",
"stdout",
}
logger,err := cfg.Build()
if err!=nil {
fmt.Println("日志启动出错:",err.Error())
return
}
defer logger.Sync()
timeStr := time.Now().Format("2006-01-02 15:04:05")
//响应状态码
responseStatus := c.Writer.Status()
//响应 header
responseHeader := c.Writer.Header()
responseHeaderStr := fmt.Sprintf("%v",responseHeader)
//响应体大小
responseBodySize := c.Writer.Size()
//响应体 body
responseBody := writer.b.String()
logger.Info("记录一条日志",
zap.String("time", timeStr),
zap.String("ip", c.ClientIP()),
zap.String("proto", c.Request.Proto),
zap.String("method", c.Request.Method),
zap.String("host", c.Request.Host),
zap.String("url", c.Request.URL.String()),
zap.String("get_params", global.GetAllGetParams(c)),
zap.String("post_params", global.GetAllPostParams(c)),
zap.String("UserAgent", c.Request.UserAgent()),
//请求头,请求体
zap.String("requestHeader", requestHeaderStr),
zap.String("requestBody", requestBody),
//响应头、响应体
zap.Int("responseStatus", responseStatus),
zap.String("responseHeader", responseHeaderStr),
zap.Int("responseBodySize", responseBodySize),
zap.String("responseBody", responseBody),
//时长,微秒
zap.Int64("duration", duration/1000),
)
}
}
global/functions.go
package global
import (
"github.com/gin-gonic/gin"
"os"
"strings"
"time"
)
//格式化当前时间
func FormattedNow(format string) string {
// 获取当前时间
now := time.Now()
// 格式化时间
formatted := now.Format(format)
return formatted
}
//判断目录是否存在
func IsDirExists(path string) bool {
_, err := os.Stat(path)
if err != nil {
if os.IsNotExist(err) {
return false
}
}
return true
}
//得到所有get参数
func GetAllGetParams(c *gin.Context) (string) {
params := c.Request.URL.Query()
resStr := ""
// 遍历并打印所有参数及其值
for key, values := range params {
for _, value := range values {
resStr = resStr+"key:"+key+",value:"+value+"\n"
}
}
return resStr
}
//得到所有post参数
func GetAllPostParams(c *gin.Context) (string) {
c.Request.ParseMultipartForm(32 << 20)
resStr := ""
for k, v := range c.Request.PostForm {
resStr = resStr+"key:"+k+",value:"+strings.Join(v, ",")+"\n"
}
return resStr
}
routes.go 启用日志中间件
package routes
import (
"embed"
"fmt"
"github.com/gin-gonic/gin"
"html/template"
"io/fs"
"mediabank/controller"
"mediabank/global"
"mediabank/middleware"
"net/http"
"runtime/debug"
"time"
)
func Routes(embedFs embed.FS) *gin.Engine {
router := gin.Default()
//全局使用访问日志
//router.Use(middleware.AccessLog())
//处理找不到路由
router.NoRoute(HandleNotFound)
router.NoMethod(HandleNotFound)
//处理发生异常
router.Use(Recover)
//1, 加载模板文件
tmpl := template.Must(template.New("").ParseFS(embedFs, "templates/**/*.html"))
router.SetHTMLTemplate(tmpl)
//2, 加载静态文件
fp, _ := fs.Sub(embedFs, "static")
router.StaticFS("/static", http.FS(fp))
//3,加载favicon
//static是存放favicon.ico的目录
faviconHandler := http.FileServer(http.FS(fp))
router.GET("/favicon.ico", func(c *gin.Context) {
faviconHandler.ServeHTTP(c.Writer, c.Request)
})
//media,使用自定义的访问日志中间件
media := controller.NewMediaController()
mediaGroup := router.Group("/media").Use(middleware.AccessLog())
{
mediaGroup.GET("/detail", media.Detail)
mediaGroup.GET("/list", media.List)
mediaGroup.GET("/user", media.User)
}
return router
}
三,测试效果
{"level":"info","ts":1757388627.6531723,"caller":"middleware/accesslog.go:98","msg":"记录一条日志",
"time":"2025-09-09 11:30:27","ip":"192.168.219.1","proto":"HTTP/1.1","method":"GET",
"host":"192.168.219.3:8080","url":"/media/detail","get_params":"","post_params":"",
"UserAgent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/139.0.0.0 Safari/537.36",
"requestHeader":"map[Accept:[text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7] Accept-Encoding:[gzip, deflate] Accept-Language:[zh-CN,zh;q=0.9,en-US;q=0.8,en;q=0.7,ja;q=0.6] Cache-Control:[max-age=0] Connection:[keep-alive] Cookie:[PHPSID=e239cbb4d5ddd941f501d2d3a9cca3d5] Upgrade-Insecure-Requests:[1] User-Agent:[Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/139.0.0.0 Safari/537.36]]",
"requestBody":"","responseStatus":200,"responseHeader":"map[Content-Type:[text/html; charset=utf-8]]",
"responseBodySize":1224,"responseBody":"<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n <meta charset=\"UTF-8\">\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n <title>Gin 模板示例</title>\n <link rel=\"stylesheet\" href=\"/static/css/global.css\">\n
<script src=\"/static/js/jquery-3.7.1.min.js\"></script>\n</head>\n<body>\n<h1>欢迎来到 Gin 的世界!</h1>\n<button onclick=\"getName()\">获取当前用户名字</button>\n<script>\n function getName() {\n
var paramsData = {\n a:1,\n b:2\n }\n var url = \"/media/user\";\n $.ajax({\n type: 'GET',\n url: url,\n data: paramsData,\n dataType: 'json',\n success: function(data) {\n\n console.log(\"成功\");\n console.log(data);\n
if (data.hasOwnProperty('name')) {\n alert('name:'+data.name)\n } else {\n alert('数据获取失败')\n }\n },\n
error: function(jqXHR, textStatus, errorThrown) {\n console.log(\"失败\");\n\n console.error('Error: ' + textStatus + ' - ' + errorThrown);\n }\n });\n }\n\n</script>\n</body>\n</html>","duration":257}
浙公网安备 33010602011771号