feat(lyra)!: 添加结构化日志与请求追踪
- 配置 slog JSON 输出和日志级别 - 添加请求 ID、访问日志及上下文日志工具 - 拆分启动初始化并统一启动失败处理 BREAKING CHANGE: 配置文件必须提供 log.level,缺失或非法将导致启动失败
This commit is contained in:
@@ -8,6 +8,7 @@ import (
|
||||
|
||||
type Config struct {
|
||||
App AppConfig `mapstructure:"app"`
|
||||
Log LogConfig `mapstructure:"log"`
|
||||
HTTP HTTPConfig `mapstructure:"http"`
|
||||
Database DatabaseConfig `mapstructure:"database"`
|
||||
Cache CacheConfig `mapstructure:"cache"`
|
||||
@@ -18,6 +19,9 @@ type AppConfig struct {
|
||||
Env string `mapstructure:"env"`
|
||||
}
|
||||
|
||||
type LogConfig struct {
|
||||
Level string `mapstructure:"level"`
|
||||
}
|
||||
type HTTPConfig struct {
|
||||
Host string `mapstructure:"host"`
|
||||
Port int `mapstructure:"port"`
|
||||
|
||||
@@ -2,11 +2,11 @@ package handler
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"lyra/internal/api"
|
||||
"lyra/internal/cache"
|
||||
"lyra/internal/httpx"
|
||||
"lyra/internal/model"
|
||||
"lyra/internal/util"
|
||||
"net/http"
|
||||
|
||||
"time"
|
||||
@@ -35,21 +35,21 @@ func (h *TestHandler) Hello() gin.HandlerFunc {
|
||||
Value: categories,
|
||||
TTL: 5 * time.Minute,
|
||||
}); err != nil {
|
||||
slog.ErrorContext(ctx.Request.Context(), "缓存分类列表失败")
|
||||
util.Logger(ctx.Request.Context()).Error("设置缓存分类列表失败")
|
||||
return
|
||||
}
|
||||
testResult, _, err := h.cache.Get[[]*model.Category](ctx.Request.Context(), "test:categories")
|
||||
if err != nil {
|
||||
slog.ErrorContext(ctx.Request.Context(), "获取缓存分类列表失败")
|
||||
util.Logger(ctx.Request.Context()).Error("获取缓存分类列表失败")
|
||||
}
|
||||
if len(testResult) == 0 {
|
||||
slog.ErrorContext(ctx.Request.Context(), "缓存分类列表为空")
|
||||
util.Logger(ctx.Request.Context()).Error("缓存分类列表为空")
|
||||
} else {
|
||||
for _, v := range testResult {
|
||||
fmt.Printf("%+v\n", v)
|
||||
}
|
||||
}
|
||||
|
||||
util.Logger(ctx.Request.Context()).Info("success")
|
||||
httpx.OK(ctx, toCategoryResponses(categories))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"lyra/internal/util"
|
||||
"time"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
func AccessLogMiddleware() gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
start := time.Now()
|
||||
|
||||
c.Next()
|
||||
|
||||
ctx := c.Request.Context()
|
||||
|
||||
util.Logger(ctx).InfoContext(
|
||||
ctx,
|
||||
"http request",
|
||||
"method", c.Request.Method,
|
||||
"path", c.Request.URL.Path,
|
||||
"status", c.Writer.Status(),
|
||||
"latency_ms", time.Since(start).Milliseconds(),
|
||||
"client_ip", c.ClientIP(),
|
||||
"user_agent", c.Request.UserAgent(),
|
||||
"response_size", c.Writer.Size(),
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"context"
|
||||
"lyra/internal/pkg/constant"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
func RequestIDMiddleware() gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
requestID := c.GetHeader("X-Request-ID")
|
||||
|
||||
if requestID == "" {
|
||||
requestID = uuid.NewString()
|
||||
}
|
||||
|
||||
c.Header("X-Request-ID", requestID)
|
||||
|
||||
ctx := context.WithValue(c.Request.Context(), constant.RequestIDKey, requestID)
|
||||
|
||||
c.Request = c.Request.WithContext(ctx)
|
||||
|
||||
c.Next()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
package constant
|
||||
|
||||
type contextKey string
|
||||
|
||||
const RequestIDKey contextKey = "request_id"
|
||||
@@ -0,0 +1,17 @@
|
||||
package util
|
||||
|
||||
import (
|
||||
"context"
|
||||
"log/slog"
|
||||
"lyra/internal/pkg/constant"
|
||||
)
|
||||
|
||||
func Logger(ctx context.Context) *slog.Logger {
|
||||
logger := slog.Default()
|
||||
|
||||
if requestID, ok := ctx.Value(constant.RequestIDKey).(string); ok {
|
||||
logger = logger.With(string(constant.RequestIDKey), requestID)
|
||||
}
|
||||
|
||||
return logger
|
||||
}
|
||||
Reference in New Issue
Block a user