feat(log): Gin 结构化 JSON 访问日志 + 生产默认 release 模式(去掉启动路由 dump)

=== 一、结构化 JSON 访问日志(middleware/accesslog.go)===
gin.Default() → gin.New() + AccessLog() + gin.Recovery()
  - 一行一个 JSON:ts/level/type/method/path/status/latency_ms/bytes/ip/ua
  - 自动分级:5xx=error / 4xx=warn / 其余=info
  - jwt 中间件写入的 userId/username 一并输出,便于审计
  - 健康检查与 /assets 静音(探针每 10s 一次会淹没真实请求)
  - Recovery 显式加回(gin.Default 里包含它)

⚠️ Go map 序列化按 key 字母序,ts 会排到最后 → LogJSON 手工拼装,
   把 ts/level/type/msg 固定在最前,其余排序输出。

=== 二、生产默认 release 模式 ===
代码里此前**完全没有 gin.SetMode**,一直跑在 debug:
  - 每次启动 dump 100+ 行路由表([GIN-debug] GET /api/v1/...)
  - 每请求有额外开销,且把完整路由暴露在日志里
现在:GIN_MODE 未显式设置且 DEBUG!=true 时自动 SetMode(ReleaseMode),
需要调试仍可用 DEBUG=true 或 GIN_MODE=debug 覆盖。

=== 三、实测 ===
  GIN-debug 路由 dump 行数: 0 ✅(修复前 100+)
  JSON 访问日志:
    {"ts":"2026-10-27T14:49:28.53Z","level":"info","type":"access","bytes":828,
     "ip":"172.19.16.92","latency_ms":173.5,"method":"POST",
     "path":"/api/v1/auth/login","status":200,"ua":"curl/8.4.0"} ✅
  带用户信息: ...,"user_id":1,"username":"admin" ✅
  health 日志行数: 0 ✅
  总日志 15 行 / JSON 7 行 / 非 JSON 8 行(全部是启动日志)
  health ok、Promtail 12/12、lokistack 1/1 ✅

镜像:lghost/lokistack:latest + v0.1 = sha256:8b25b0a29efa
This commit is contained in:
opsx committed 2026-10-27 14:49:45 +00:00
1 parent 02705382f1
commit 7b600f7eee
3 files changed
+151 -1

No files matched your search

+11
View File
@@ -5,10 +5,14 @@ import (
"errors"
"log"
"net/http"
"os"
"os/signal"
"strings"
"syscall"
"time"
"github.com/gin-gonic/gin"
"lokistack/config"
"lokistack/repository"
"lokistack/routers"
@@ -19,6 +23,13 @@ import (
func main() {
cfg := config.GetConfig()
// gin 默认是 debug 模式:每次启动会把上百行路由表 dump 到日志里,
// 且每个请求有额外开销。生产默认切 release,需要调试时显式设
// DEBUG=true 或 GIN_MODE=debug 覆盖。
if os.Getenv("GIN_MODE") == "" && !strings.EqualFold(os.Getenv("DEBUG"), "true") {
gin.SetMode(gin.ReleaseMode)
}
log.Println("Initializing database...")
if err := repository.InitDB(); err != nil {
log.Fatalf("Failed to init DB: %v", err)