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}

 

posted @ 2025-09-20 09:46  刘宏缔的架构森林  阅读(14)  评论(0)    收藏  举报