gin: zap日志按天切分

一,代码

初始化一个全局变量用来写日志

package global

import (
	"fmt"
	"go.uber.org/zap"
	"go.uber.org/zap/zapcore"
	"os"
	"sync"
	"time"
)
//全局变量
var (
	LogFileAccess *os.File
	LoggerAccess *zap.Logger
)

//创建logger
func SetupAccessLogger() (error) {

	//判断当前日志目录是否存在,如不存在则创建
	var curDir = "./logs"
	if IsDirExists(curDir) == false {
		// 创建多级目录
		err := os.MkdirAll(curDir, 0755)
		if err != nil {
			fmt.Println(err)
		}
	}


	//得到当前日期
	var curDate = FormattedNow("200601021504")

	//现在得到一个iowriter
	filename := "./logs/access"+curDate+".log"
	LogFileAccess,_=os.OpenFile(filename,os.O_WRONLY|os.O_CREATE|os.O_APPEND,06666)

	fmt.Println("日志文件名:"+LogFileAccess.Name())

	//得到BufferedWriteSyncer
	asyncWriter := &zapcore.BufferedWriteSyncer{
		WS:            zapcore.AddSync(LogFileAccess),
		FlushInterval: time.Minute,    //1分钟后写入
		//FlushInterval: time.Second,    //1秒钟后写入
		Size: 512 * 1024,              //超过512 kB后写入,
	}
	//得到encoderconfig
	encoderConfig := zapcore.EncoderConfig{
		TimeKey:        "timestamp",
		LevelKey:       "level",
		NameKey:        "logger",
		CallerKey:      "caller",
		MessageKey:     "message",
		StacktraceKey:  "stacktrace",
		LineEnding:     zapcore.DefaultLineEnding,
		EncodeLevel:    zapcore.CapitalLevelEncoder,  // INFO -> INFO
		EncodeTime:     zapcore.ISO8601TimeEncoder,   // 时间格式化
		EncodeDuration: zapcore.StringDurationEncoder,
		EncodeCaller:   zapcore.ShortCallerEncoder,   // caller 压缩显示
	}

	//得到encoder
	encoder := zapcore.NewConsoleEncoder(encoderConfig)

	// 实现判断日志等级的interface
	infoLevel := zap.LevelEnablerFunc(func(lvl zapcore.Level) bool {
		return lvl >= zapcore.InfoLevel
	})

	//生成newCore
	//newCore:=zapcore.NewCore(encoder, asyncWriter, infoLevel)
	core := zapcore.NewTee(
		zapcore.NewCore(encoder, zapcore.AddSync(os.Stdout), infoLevel), //打印到控制台
		zapcore.NewCore(encoder, zapcore.AddSync(asyncWriter), infoLevel),   //打印到文件中
		//zapcore.NewCore(encoder, zapcore.AddSync(errorWriter), errorLevel),
	)
	//生成logger实例
	LoggerAccess=zap.New(core)

	defer LoggerAccess.Sync()

	return nil
}

//写日志,每次写前要判断一下当前时间
func AccessLoggerWrite(content string) {
	//得到当前日期
	var curDate = FormattedNow("200601021504")

	filename := "./logs/access"+curDate+".log"

	if filename == LogFileAccess.Name() {
		fmt.Println("日志文件名正确,直接写入")
		LoggerAccess.Info(content)
	} else {
		fmt.Println("日志文件名已改变,重新写入")
		//重新建立日志
		lock:=sync.Mutex{}
		lock.Lock()
		LoggerAccess.Sync()
		SetupAccessLogger()
		//写日志
		LoggerAccess.Info(content)
		defer lock.Unlock()
	}
}

用到的函数

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
}

中间件

package middleware

import (
	"bytes"
	"fmt"
	"github.com/gin-gonic/gin"
	"io"
	"mediabank/global"
	"strconv"
	"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",
		}
		fmt.Println(cfg.OutputPaths)
		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()

		content:= timeStr+" "+c.ClientIP()+" "+c.Request.Proto+" "+ c.Request.Method+" "+c.Request.Host
		content += " "+c.Request.URL.String()+"\n"
		content += "get参数:"+global.GetAllGetParams(c)+"\n"
		content += "post参数:"+global.GetAllPostParams(c)+"\n"
		content += "UserAgent:"+c.Request.UserAgent()+"\n"
		content += "requestHeader:"+requestHeaderStr+"\n"
		content += "requestBody:"+requestBody+"\n"

		content += "responseStatus:"+strconv.Itoa(responseStatus)+"\n"
		content += "responseHeader:"+responseHeaderStr+"\n"
		content += "responseBodySize:"+strconv.Itoa(responseBodySize)+"\n"
		content += "responseBody:"+responseBody+"\n"
		content += "duration:"+strconv.FormatInt(duration/1000,10)+"\n"
		global.AccessLoggerWrite(content)
	}
}

routes中调用

package routes

import (
	"bytes"
	"embed"
	"fmt"
	"github.com/gin-gonic/gin"
	"html/template"
	"io"
	"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
}

//404,找不到路径时的处理
func HandleNotFound(c *gin.Context) {
	fmt.Println("请求的地址未找到,uri: ", c.Request.RequestURI)
	fmt.Println("stack: ", string(debug.Stack()))
	//global.NewResult(c).ErrorCode(404,"资源未找到",nil)
	ReturnToUser(c,404,"资源未找到")
	return
}

//500,内部发生异常时的处理
func Recover(c *gin.Context) {
	defer func() {
		if err := recover(); err != nil {
			//打印错误堆栈信息
			timeStr := time.Now().Format("2006-01-02 15:04:05")
			fmt.Println("当前时间:", timeStr)
			fmt.Println("当前访问path:", c.FullPath())
			fmt.Println("当前完整地址:", c.Request.URL.String())
			fmt.Println("当前协议:", c.Request.Proto)
			fmt.Println("当前get参数:",global.GetAllGetParams(c))
			fmt.Println("当前post参数:",global.GetAllPostParams(c))
			fmt.Println("当前访问方法:", c.Request.Method)
			fmt.Println("当前访问Host:", c.Request.Host)
			fmt.Println("当前IP:",c.ClientIP())
			fmt.Println("当前浏览器:",c.Request.UserAgent())
			fmt.Println("发生异常:", err)
			//global.Logger.Errorf("stack: %v",string(debug.Stack()))
			debug.PrintStack()
			//log
			content:= timeStr+" "+c.ClientIP()+" "+c.Request.Proto+" "+ c.Request.Method+" "+c.Request.Host
			content += " "+c.Request.URL.String()+"\n"
			content += "get参数:"+global.GetAllGetParams(c)+"\n"
			content += "post参数:"+global.GetAllPostParams(c)+"\n"
			content += "UserAgent:"+c.Request.UserAgent()+"\n"
			requestHeader := c.Request.Header
			requestHeaderStr:=fmt.Sprintf("%v",requestHeader)
			content += "requestHeader:"+requestHeaderStr+"\n"


			//请求体 body
			requestBody := ""
			b, errR := c.GetRawData()
			if errR != nil {
				requestBody = "failed to get request body"
			} else {
				requestBody = string(b)
			}
			c.Request.Body = io.NopCloser(bytes.NewBuffer(b))
			fmt.Println("请求体:",requestBody)

			content += "requestBody:"+requestBody+"\n"
			content += "err:"+fmt.Sprintf("%v",err)+"\n"
			content += string(debug.Stack())+"\n"
			global.AccessLoggerWrite(content)
			//return
			//global.NewResult(c).ErrorCode(500,"服务器内部错误",nil)
			ReturnToUser(c,500,"服务器内部错误")
		}
	}()
	//继续后续接口调用
	c.Next()
}

func ReturnToUser(c *gin.Context,code int,msg string) {
	if c.Request.Header.Get("X-Requested-With") == "XMLHttpRequest" {
		global.NewResult(c).ErrorCode(code,msg,nil)
	} else {
		c.HTML(200, "sysinfo.html", gin.H{
			"Title": "Gin 模板示例",
			"Code": code,
			"Message": msg,
		})
	}
}

 

二,效果:

2025-09-09T14:03:40.706+0800	INFO	2025-09-09 14:03:40 192.168.219.1 HTTP/1.1 GET 192.168.219.3:8080 /media/detail?a=1&b=3
get参数:key:a,value:1
key:b,value:3

post参数:
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] 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>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Gin 模板示例</title>
    <link rel="stylesheet" href="/static/css/global.css">
    <script src="/static/js/jquery-3.7.1.min.js"></script>
</head>
<body>
<h1>欢迎来到 Gin 的世界!</h1>
<button onclick="getName()">获取当前用户名字</button>
<script>
    function getName() {
        var paramsData = {
            a:1,
            b:2
        }
        var url = "/media/user";
        $.ajax({
            type: 'GET',
            url: url,
            data: paramsData,
            dataType: 'json',
            success: function(data) {

                console.log("成功");
                console.log(data);
                if (data.hasOwnProperty('name')) {
                    alert('name:'+data.name)
                } else {
                    alert('数据获取失败')
                }
            },
            error: function(jqXHR, textStatus, errorThrown) {
                console.log("失败");

                console.error('Error: ' + textStatus + ' - ' + errorThrown);
            }
        });
    }

</script>
</body>
</html>
duration:70

 

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