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
浙公网安备 33010602011771号