Explorar o código

feat: HTTP 访问日志

runningwater hai 1 ano
pai
achega
5a91b17003
Modificáronse 5 ficheiros con 91 adicións e 1 borrados
  1. 76 0
      app/http/middlewares/logger.go
  2. 2 1
      bootstrap/route.go
  3. 3 0
      main.go
  4. 8 0
      pkg/helpers/helpers.go
  5. 2 0
      storage/logs/.gitignore

+ 76 - 0
app/http/middlewares/logger.go

@@ -0,0 +1,76 @@
+package middlewares
+
+import (
+	"bytes"
+	"io"
+	"time"
+
+	"github.com/gin-gonic/gin"
+	"github.com/runningwater/gohub/pkg/helpers"
+	"github.com/runningwater/gohub/pkg/logger"
+	"github.com/spf13/cast"
+	"go.uber.org/zap"
+)
+
+type responseBodyWriter struct {
+	gin.ResponseWriter
+	body *bytes.Buffer
+}
+
+func (r responseBodyWriter) Write(b []byte) (int, error) {
+	r.body.Write(b)
+	return r.ResponseWriter.Write(b)
+}
+
+
+// Logger 记录请求日志
+func Logger() gin.HandlerFunc {
+	return func(c *gin.Context) {
+
+		// 获取 response 内容
+		w := &responseBodyWriter{body: &bytes.Buffer{}, ResponseWriter: c.Writer}
+		c.Writer = w
+
+		// 获取请求数据
+		var requestBody []byte
+		if c.Request.Body != nil {
+			// 读取 c.Request.Body 内容
+			requestBody, _ = io.ReadAll(c.Request.Body)
+			// 重新赋值 c.Request.Body ,以供后续的其他方法读取
+			c.Request.Body = io.NopCloser(bytes.NewBuffer(requestBody))
+		}
+
+		// 设置开始时间
+		start := time.Now()
+		c.Next()
+
+		// 开始记录日志的逻辑
+		cost := time.Since(start)
+		responStatus := c.Writer.Status()
+
+		logFields := []zap.Field{
+			zap.Int("status", responStatus),
+			zap.String("request", c.Request.Method+c.Request.URL.String()),
+			zap.String("query", c.Request.URL.RawQuery),
+			zap.String("ip", c.ClientIP()),
+			zap.String("user-agent", c.Request.UserAgent()),
+			zap.String("errors", c.Errors.ByType(gin.ErrorTypePrivate).String()),
+			zap.String("time", helpers.MicrosecondsStr(cost)),	
+		}
+
+		// Body 内容
+		if c.Request.Method == "POST" || c.Request.Method == "PUT" || c.Request.Method == "DELETE" {
+			logFields = append(logFields, zap.String("RequestBody", string(requestBody)))
+			logFields = append(logFields, zap.String("ResponseBody", w.body.String()))
+		}
+
+		if responStatus > 400 && responStatus < 500  {
+			// 403 404 等客户端错误,使用 Warn 级别
+			logger.Warn("HTTP Warning "+ cast.ToString(responStatus), logFields...)
+		} else if responStatus >= 500 && responStatus < 600  {
+			logger.Error("HTTP Error "+ cast.ToString(responStatus), logFields...)
+		} else {
+			logger.Debug("HTTP Access Log", logFields...)
+		}
+	}
+}

+ 2 - 1
bootstrap/route.go

@@ -6,6 +6,7 @@ import (
 	"strings"
 
 	"github.com/gin-gonic/gin"
+	"github.com/runningwater/gohub/app/http/middlewares"
 	"github.com/runningwater/gohub/routes"
 )
 
@@ -39,7 +40,7 @@ func setup404Handler(router *gin.Engine) {
 
 func registerGlobalMiddleWare(router *gin.Engine) {
 	router.Use(
-		gin.Logger(),
+		middlewares.Logger(),
 		gin.Recovery(),
 	)
 }

+ 3 - 0
main.go

@@ -27,6 +27,9 @@ func main() {
 	// 初始化 Logger
 	bootstrap.SetupLogger()
 
+	// gin 框架设置为发布模式,线上环境需要设置
+	gin.SetMode(gin.ReleaseMode)
+
 	// Gin 框架初始化
 	r:= gin.New()
 

+ 8 - 0
pkg/helpers/helpers.go

@@ -2,7 +2,9 @@
 package helpers
 
 import (
+	"fmt"
 	"reflect"
+	"time"
 )
 
 // Empty 类似于 PHP 的 empty() 函数
@@ -28,4 +30,10 @@ func Empty(val any) bool {
         return v.IsNil()
     }
     return reflect.DeepEqual(val, reflect.Zero(v.Type()).Interface())
+}
+
+// MicrosecondsStr 将 time.Duration 类型(nano seconds 为单位)
+// 输出为小数点后 3 位的 ms (microsecond 毫秒,千分之一秒)
+func MicrosecondsStr(elapsed time.Duration) string {
+    return fmt.Sprintf("%.3fms", float64(elapsed.Nanoseconds())/1e6)
 }

+ 2 - 0
storage/logs/.gitignore

@@ -0,0 +1,2 @@
+*
+!.gitignore