feat(log): Seq 会话追踪、控制台降噪与推送质量增强
请求日志自动带 sid/request_id,支持 LogBind;挂 Seq 后控制台仅 Warn+,Seq 仍全量;并修毫秒时间戳、@m、失败重试与停机 flush。 Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
+89
-95
@@ -19,7 +19,61 @@ HoTime 框架内置 Seq 日志推送支持。通过在 `config.json` 填写 `seq
|
||||
| `seqUrl` | 空(不激活) | Seq 服务地址,空值时功能静默不生效 |
|
||||
| `seqApiKey` | 空 | API Key,免费单用户版留空 |
|
||||
|
||||
`instance` 字段由框架自动拼接为 `ip:port`(如 `192.168.1.10:8085`),无需手动填写,同机多进程靠端口区分,跨服务器靠 IP 区分,支持未来集群扩展。
|
||||
`instance` 字段由框架自动拼接为 `ip:port`(如 `192.168.1.10:8085`),无需手动填写。
|
||||
|
||||
激活后每个 HTTP 请求会自动:
|
||||
|
||||
- 生成 `request_id`(12 位 hex),回写响应头 `X-Request-Id`
|
||||
- 将 `sessionId` 前 12 位作为 `sid` 写入请求级日志(脱敏,避免把登录凭据写进 Seq)
|
||||
- 业务日志、SQL 日志、访问日志均携带 `sid` / `request_id`
|
||||
- **控制台自动降噪为 Warn+**(Info/Debug/SQL/访问日志仍进 Seq 与文件,零额外配置)
|
||||
|
||||
---
|
||||
|
||||
## 会话追踪与 LogBind
|
||||
|
||||
框架在 `handler` 入口派生请求级 Logger,并浅拷贝 `Db` 将其 `Log` 指向同一 Logger,因此 **SQL 日志自动带会话字段**。字段会出现在同条日志的控制台/文件/Seq 出口上。
|
||||
|
||||
业务在 `SetConnectListener` 鉴权通过后绑定(xbc `main.go`):
|
||||
|
||||
```go
|
||||
// app 鉴权通过后
|
||||
context.LogBind("user_id", context.Session("user_id").ToCeilInt64())
|
||||
|
||||
// admin 鉴权通过后(或 session 已有 admin_id)
|
||||
context.LogBind("admin_id", context.Session("admin_id").ToCeilInt64())
|
||||
```
|
||||
|
||||
`LogBind` 后,本请求后续业务日志与 SQL 日志都会带上该字段。
|
||||
|
||||
**不带会话字段的边界:**
|
||||
|
||||
- `fmt.Println` / stdout 捕获、panic、MySQL driver、定时任务等无请求上下文的日志
|
||||
- 直接写 `that.Application.Log` 的旧代码(应改用 `that.Logger` 或 `that.LogBind` 后的请求级 Logger)
|
||||
|
||||
---
|
||||
|
||||
## 控制台降噪(配了 seqUrl)
|
||||
|
||||
| 出口 | 行为 |
|
||||
|---|---|
|
||||
| 控制台 | 仅 Warn / Error(含 `Display` 非 0 的 Warn) |
|
||||
| Seq | 按 `logLevel` 全量(Info/Debug/SQL/访问日志等) |
|
||||
| 本地文件 | 与原先一致,不受控制台过滤影响 |
|
||||
|
||||
未配置 `seqUrl` 时控制台仍按 `logLevel` 全打。
|
||||
|
||||
---
|
||||
|
||||
## 客户端 IP 与地域
|
||||
|
||||
零配置,优先级:
|
||||
|
||||
1. `X-Real-IP`(EdgeOne / 反代透传的真实 IP)
|
||||
2. `X-Forwarded-For` **第一段**
|
||||
3. `RemoteAddr`
|
||||
|
||||
请求头有 `EO-Client-IPCountry` 时,访问日志追加 `ip_country`(两位国家码);没有则不记。
|
||||
|
||||
---
|
||||
|
||||
@@ -27,26 +81,24 @@ HoTime 框架内置 Seq 日志推送支持。通过在 `config.json` 填写 `seq
|
||||
|
||||
```
|
||||
业务代码
|
||||
│ l.Info().Msg("...") ← HoTime Logger 正常调用路径
|
||||
│ fmt.Println("...") ← 被 redirectStdout 捕获后转入同一路径
|
||||
│ that.Logger.Info().Msg("...") ← 请求级(含 sid/request_id)
|
||||
│ Db.Query → SQL 日志 ← 同一请求级 Logger
|
||||
│ fmt.Println("...") ← 捕获后无 sid
|
||||
↓
|
||||
multiWriter(hotimev1.5/log/logger.go)
|
||||
├─ ConsoleWriter → 彩色终端输出(不变)
|
||||
├─ FileWriter → 本地日志文件(按需,logFile 配置)
|
||||
└─ SeqWriter
|
||||
├─ Console(挂 Seq 后 Warn+)→ 终端
|
||||
├─ FileWriter → 本地文件(按需,全量)
|
||||
└─ SeqWriter → Seq(全量)
|
||||
│ Write() 只做 channel <- bytes,O(1) 非阻塞
|
||||
↓
|
||||
channel(容量 10000)
|
||||
↓ 后台 goroutine
|
||||
批量打包(100 条 或 500ms)
|
||||
↓ HTTP POST
|
||||
↓ HTTP POST(失败重试 1 次)
|
||||
Seq 服务(CLEF 格式)
|
||||
```
|
||||
|
||||
**关键特性:**
|
||||
- `SeqWriter.Write()` 仅向 channel 投递字节即返回,**绝不阻塞** web 请求处理 goroutine
|
||||
- channel 满时(Seq 宕机/网络故障)新日志被丢弃并计数,主服务完全不受影响
|
||||
- HTTP POST 设 5s 超时,失败仅打印到 stderr
|
||||
优雅停机时调用 `CloseSeq()` 冲刷残留批次,避免停机前后日志丢失。
|
||||
|
||||
---
|
||||
|
||||
@@ -54,82 +106,16 @@ multiWriter(hotimev1.5/log/logger.go)
|
||||
|
||||
| zerolog 字段 | Seq CLEF 字段 | 说明 |
|
||||
|---|---|---|
|
||||
| `time` | `@t` | 时间,自动转 ISO 8601 格式 |
|
||||
| `level` | `@l` | 级别,映射为 Debug/Information/Warning/Error/Fatal |
|
||||
| `message` / `msg` | `@mt` | 消息正文 |
|
||||
| `caller` | `caller` | 调用位置,原样保留 |
|
||||
| 其余自定义字段 | 原字段名 | 原样保留,可在 Seq 中直接查询 |
|
||||
| — | `instance` | 框架自动注入,值为 `port` 配置(如 `"8085"`) |
|
||||
| — | `source` | fmt.Println 等捕获的输出标记为 `stdout` |
|
||||
|
||||
---
|
||||
|
||||
## 单机多进程实例区分
|
||||
|
||||
框架启动时自动获取本机出口 IP,拼接为 `ip:port` 格式作为 `instance`:
|
||||
|
||||
```
|
||||
单机多进程:
|
||||
192.168.1.10:8085 ─┐
|
||||
192.168.1.10:8086 ─┼─ HTTP CLEF ──→ Seq
|
||||
192.168.1.10:8087 ─┘
|
||||
|
||||
多服务器集群:
|
||||
192.168.1.10:8085 ─┐
|
||||
192.168.1.11:8085 ─┼─ HTTP CLEF ──→ Seq(中央日志服务器)
|
||||
192.168.1.12:8085 ─┘
|
||||
```
|
||||
|
||||
Seq 中按实例筛选:
|
||||
- 单台机器所有进程:`instance like '192.168.1.10%'`
|
||||
- 精确到某个进程:`instance = '192.168.1.10:8085'`
|
||||
|
||||
---
|
||||
|
||||
## stdout / stderr 全量捕获
|
||||
|
||||
`SetConfig()` 中自动 `redirectStdout` + `redirectStderr`,并桥接 **logrus**(微信 SDK)到同一管道。
|
||||
|
||||
| 写法 | Seq 字段 | 说明 |
|
||||
|------|----------|------|
|
||||
| `fmt.Println` / `log.Println` | `source=stdout` | **不受 `logLevel` 影响**,`logLevel=0` 也会进 Seq |
|
||||
| 写 `os.Stderr` 的包 | `source=stderr` | 同上,Error 级别 |
|
||||
| `logrus.Info`(wechat) | `source=stdout` | redirect 后 `logrus.SetOutput` 桥接 |
|
||||
| `that.Log.Info/Error...` | 结构化字段 | 经 `multiWriter` → SeqWriter |
|
||||
| MySQL driver 内部错误 | `source=mysql-driver` | `SetLogger` 适配器 |
|
||||
| 框架 `recover` 到的 panic | `source=panic` + `stack` | 代码层原因与调用栈 |
|
||||
|
||||
单行日志上限约 **10MB**(适配 `GetReqMap` 打整包 body);超长会截断并标注 `...(truncated)`。
|
||||
|
||||
**已知不进 Seq(文档边界):**
|
||||
|
||||
- 未 `recover`、进程直接崩溃的 runtime 栈(写 fd2,未做 Dup2)
|
||||
- 达梦驱动自有文件日志(vendor 独立写盘)
|
||||
- `seqUrl` 挂上之前的极早期引导日志
|
||||
|
||||
---
|
||||
|
||||
## 捕获矩阵速查
|
||||
|
||||
| 来源 | 进 Seq? | 检索示例 |
|
||||
|------|----------|----------|
|
||||
| `that.Log.*` | 是 | `@mt like '%关键词%'` |
|
||||
| `log.Println` / `fmt.Println` | 是 | `source = 'stdout'` |
|
||||
| logrus / 标准 `log` | 是 | `source = 'stdout'` |
|
||||
| stderr 重定向 | 是 | `source = 'stderr'` |
|
||||
| recover panic | 是 | `source = 'panic'` |
|
||||
| MySQL driver | 是 | `source = 'mysql-driver'` |
|
||||
| 队列满丢弃 | 否(计数) | 终端可见 `[seq] queue full` |
|
||||
|
||||
---
|
||||
|
||||
## Seq 安装
|
||||
|
||||
Seq 提供 Windows MSI 安装包和 Docker 镜像,单机免费,无外部数据库依赖:
|
||||
|
||||
- Windows:[https://datalust.co/download/seq](https://datalust.co/download/seq),安装后自动注册为 Windows 服务
|
||||
- Docker:`docker run -d --restart always --name seq -p 5341:80 -e ACCEPT_EULA=Y datalust/seq`
|
||||
- 访问 `http://localhost:5341` 使用 Web UI
|
||||
| `time` | `@t` | 毫秒精度 ISO 8601 |
|
||||
| `level` | `@l` | Debug/Information/Warning/Error/Fatal |
|
||||
| `message` / `msg` | `@m` | 消息正文(不用 `@mt`,避免 `{xxx}` 被当模板) |
|
||||
| `caller` | `caller` | 调用位置 |
|
||||
| `sid` | `sid` | sessionId 前 12 位 |
|
||||
| `request_id` | `request_id` | 单次请求 id |
|
||||
| `ip_country` | `ip_country` | 有 `EO-Client-IPCountry` 时 |
|
||||
| 其余自定义字段 | 原字段名 | `LogBind` 追加的字段原样保留 |
|
||||
| — | `instance` | `ip:port` |
|
||||
| — | `source` | stdout / stderr / panic 等 |
|
||||
|
||||
---
|
||||
|
||||
@@ -137,15 +123,23 @@ Seq 提供 Windows MSI 安装包和 Docker 镜像,单机免费,无外部数
|
||||
|
||||
| 目标 | 查询语句 |
|
||||
|---|---|
|
||||
| 关键词搜索 | 直接输入,如 `支付失败` |
|
||||
| 按会话追踪 | `sid = 'abc123def456'` |
|
||||
| 按单次请求 | `request_id = 'fedcba987654'` |
|
||||
| 关键词 | 直接输入,如 `支付失败` |
|
||||
| 日志级别 | `@l = 'Error'` |
|
||||
| 特定实例 | `instance = '8085'` |
|
||||
| stdout 来源 | `source = 'stdout'` |
|
||||
| panic 恢复 | `source = 'panic'` |
|
||||
| 请求体调试 | `请求参数GetReqMap` 或 `source = 'stdout'` |
|
||||
| 调用位置 | `caller like '%order.go%'` |
|
||||
| 组合查询 | `@l = 'Error' and instance = '8086' and @mt like '%超时%'` |
|
||||
| 日期范围 | 右上角时间选择器,支持精确到秒 |
|
||||
| 特定实例 | `instance = '192.168.1.10:8085'` |
|
||||
| 地域 | `ip_country = 'CN'` |
|
||||
| stdout | `source = 'stdout'` |
|
||||
| panic | `source = 'panic'` |
|
||||
| 组合 | `@l = 'Error' and sid = 'abc123def456'` |
|
||||
|
||||
---
|
||||
|
||||
## Seq 安装
|
||||
|
||||
- Windows:[https://datalust.co/download/seq](https://datalust.co/download/seq)
|
||||
- Docker:`docker run -d --restart always --name seq -p 5341:80 -e ACCEPT_EULA=Y datalust/seq`
|
||||
- 访问 `http://localhost:5341`
|
||||
|
||||
---
|
||||
|
||||
|
||||
Reference in New Issue
Block a user