d3e71decf4
- X-Request-Id 请求头带合法 UUID 则沿用为 request_id(严格格式白名单防注入),否则自生成 12 位 hex - 响应头新增 X-Server-Received-Ms / X-Server-Sent-Ms 供前端时钟校准 - CORS Allow-Headers 增加 X-Request-Id,Expose-Headers 显式列出(credentials 下不认 *) Co-authored-by: Cursor <cursoragent@cursor.com>
184 lines
8.0 KiB
Markdown
184 lines
8.0 KiB
Markdown
# Seq 日志集成
|
||
|
||
HoTime 框架内置 Seq 日志推送支持。通过在 `config.json` 填写 `seqUrl` 即可激活。
|
||
**目标:用 Seq 集中检索替代翻 `logs/*.txt`**;本地 `logFile` 可并行保留。
|
||
|
||
---
|
||
|
||
## 配置项
|
||
|
||
在 `config.json` 中添加:
|
||
|
||
```json
|
||
"seqUrl": "http://127.0.0.1:5341",
|
||
"seqApiKey": ""
|
||
```
|
||
|
||
| 字段 | 默认值 | 说明 |
|
||
|---|---|---|
|
||
| `seqUrl` | 空(不激活) | Seq 服务地址,空值时功能静默不生效 |
|
||
| `seqApiKey` | 空 | API Key,免费单用户版留空 |
|
||
|
||
`instance` 字段由框架自动拼接为 `ip:port`(如 `192.168.1.10:8085`),无需手动填写。
|
||
|
||
激活后每个 HTTP 请求会自动:
|
||
|
||
- 确定 `request_id`(前端带合法 UUID 则沿用,否则自生成 12 位 hex),回写响应头 `X-Request-Id`
|
||
- 将 `sessionId` 前 12 位作为 `sid` 写入请求级日志(脱敏,避免把登录凭据写进 Seq)
|
||
- 业务日志、SQL 日志、访问日志均携带 `sid` / `request_id`
|
||
- **控制台自动降噪**(零额外配置):业务日志(`that.Log`)控制台放宽到 **Info+**(Debug/SQL 不上控制台),访问日志(`that.WebConnectLog`)控制台保持 **Warn+**(量大不上控制台);Debug/SQL/访问日志(Info 级)仍全量进 Seq 与文件
|
||
|
||
---
|
||
|
||
## 请求追踪与前端串联(共用 request_id)
|
||
|
||
前后端**共用一个 `request_id`**:前端为每次请求生成 UUID 并经 `X-Request-Id` 请求头带上,服务端严格校验(36 位 8-4-4-4-12 hex+连字符,统一小写)通过则直接沿用为本请求 `request_id`;非法或缺失(含浏览器直接访问、老前端)则服务端自生成 12 位 hex。最终值回写响应头 `X-Request-Id`,业务/SQL/访问日志统一携带——Seq 里一条 `request_id = '<uuid>'` 即可同时串出前端事件与后端全链路日志。
|
||
|
||
- 严格格式白名单杜绝日志注入(换行/引号/超长一律丢弃走自生成分支)
|
||
- 前端伪造/重复 UUID 只污染其自身请求的串联,不影响其他请求;接受该风险换取单字段简洁
|
||
|
||
同时每个请求回写**服务端收发时间**响应头,供前端做四时间点时钟校准(前端日志的 `@t` 对齐服务器时间轴):
|
||
|
||
- `X-Server-Received-Ms`:handler 入口时间(ms)
|
||
- `X-Server-Sent-Ms`:响应体写出前时间(ms,`context.View()` 设置;静态文件不带)
|
||
|
||
CORS 已相应放行/暴露:`Access-Control-Allow-Headers` 增加 `X-Request-Id`;`Access-Control-Expose-Headers` 由 `*` 改为显式 `X-Request-Id,X-Server-Received-Ms,X-Server-Sent-Ms`(credentials 模式下浏览器不认 `*`)。
|
||
|
||
**怎么测**:`request_trace_test.go` — `go test . -count=1 -run 'TestNormalizeRequestId|TestRequestIdSharing|TestViewSetsServerSentMsHeader'`。
|
||
|
||
---
|
||
|
||
## 会话追踪与 LogBind
|
||
|
||
框架在 `handler` 入口派生请求级 Logger,并浅拷贝 `Db` 将其 `Log` 指向同一 Logger,因此 **SQL 日志自动带会话字段**。字段会出现在同条日志的控制台/文件/Seq 出口上。
|
||
|
||
业务在 `SetConnectListener` 中按需绑定自定义字段(建议放在鉴权/守卫之前,这样未登录被拒的 Warning 也能带上维度字段):
|
||
|
||
```go
|
||
appIns.SetConnectListener(func(context *Context) bool {
|
||
if v := context.Session("user_id").ToCeilInt64(); v > 0 {
|
||
context.LogBind("user_id", v)
|
||
}
|
||
// 其他 session / 请求参数字段同理:有值再绑
|
||
return false
|
||
})
|
||
```
|
||
|
||
`LogBind` 后,本请求后续业务日志与 SQL 日志都会带上该字段。session 在 context 内有缓存,多次 `Session()` 只查一次库。字段名与取值由业务自行约定。
|
||
|
||
**不带会话字段的边界:**
|
||
|
||
- `fmt.Println` / stdout 捕获、panic、MySQL driver、定时任务等无请求上下文的日志
|
||
- 直接写 `that.Application.Log` 的旧代码(应改用 `that.Logger` 或 `that.LogBind` 后的请求级 Logger)
|
||
|
||
---
|
||
|
||
## 控制台降噪(配了 seqUrl)
|
||
|
||
| Logger | 控制台行为 |
|
||
|---|---|
|
||
| 业务日志(`that.Log`) | Info+(Info / Warn / Error,含 `Display` 非 0 的 Warn;Debug/SQL 不上控制台) |
|
||
| 访问日志(`that.WebConnectLog`) | Warn+(访问日志量大,Info 级不上控制台) |
|
||
|
||
| 出口 | 行为 |
|
||
|---|---|
|
||
| Seq | 按 `logLevel` 全量(Info/Debug/SQL/访问日志等) |
|
||
| 本地文件 | 与原先一致,不受控制台过滤影响 |
|
||
|
||
未配置 `seqUrl` 时控制台仍按 `logLevel` 全打。
|
||
|
||
框架不新增配置项;如需自定义某个 Logger 的控制台门槛,可在挂 Seq 后调用 `Logger.SetConsoleMinLevel(level zerolog.Level)`(仅影响该 Logger 的控制台出口,不影响 Seq/文件)。
|
||
|
||
---
|
||
|
||
## 客户端 IP 与地域
|
||
|
||
零配置。`ip` 选取优先级(跳过空值与回环 `127.0.0.1` / `::1`):
|
||
|
||
1. `EO-Connecting-IP`(EdgeOne 真实建连 IP)
|
||
2. `X-Forwarded-For` **从左到右第一个非回环**(反代误把 CDN 写入 `X-Real-IP` 时仍能落到客户端)
|
||
3. `X-Real-IP`(非回环)
|
||
4. `RemoteAddr`
|
||
|
||
访问日志另附 `ip_chain`:上述源头出现过的 IP 按**首次出现**顺序逗号拼接,**同一 IP 不重复**;若与 `ip` 相同则省略该字段。
|
||
|
||
请求头有 `EO-Client-IPCountry` 时,访问日志追加 `ip_country`(两位国家码);没有则不记。
|
||
|
||
---
|
||
|
||
## 架构原理
|
||
|
||
```
|
||
业务代码
|
||
│ that.Logger.Info().Msg("...") ← 请求级(含 sid/request_id)
|
||
│ Db.Query → SQL 日志 ← 同一请求级 Logger
|
||
│ fmt.Println("...") ← 捕获后无 sid
|
||
↓
|
||
multiWriter(hotimev1.5/log/logger.go)
|
||
├─ Console(挂 Seq 后:业务 Info+ / 访问日志 Warn+)→ 终端
|
||
├─ FileWriter → 本地文件(按需,全量)
|
||
└─ SeqWriter → Seq(全量)
|
||
│ Write() 只做 channel <- bytes,O(1) 非阻塞
|
||
↓
|
||
channel(容量 10000)
|
||
↓ 后台 goroutine
|
||
批量打包(100 条 或 500ms)
|
||
↓ HTTP POST(失败重试 1 次)
|
||
Seq 服务(CLEF 格式)
|
||
```
|
||
|
||
优雅停机时调用 `CloseSeq()` 冲刷残留批次,避免停机前后日志丢失。
|
||
|
||
---
|
||
|
||
## 字段映射(zerolog → CLEF)
|
||
|
||
| zerolog 字段 | Seq CLEF 字段 | 说明 |
|
||
|---|---|---|
|
||
| `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(前端合法 UUID 沿用,否则服务端 12 位 hex) |
|
||
| `ip` | `ip` | 客户端最佳 IP |
|
||
| `ip_chain` | `ip_chain` | 多源去重链路(与 `ip` 不同时才有) |
|
||
| `ip_country` | `ip_country` | 有 `EO-Client-IPCountry` 时 |
|
||
| `ua` | `ua` | 访问日志携带,User-Agent 原文截断 200 字符(按 rune 安全截断) |
|
||
| 其余自定义字段 | 原字段名 | `LogBind` 追加的字段原样保留 |
|
||
| — | `instance` | `ip:port` |
|
||
| — | `source` | stdout / stderr / panic 等 |
|
||
|
||
---
|
||
|
||
## 搜索语法速查
|
||
|
||
| 目标 | 查询语句 |
|
||
|---|---|
|
||
| 按会话追踪 | `sid = 'abc123def456'` |
|
||
| 按单次请求(前后端同 id 串联) | `request_id = '6f0a1b2c-3d4e-4f5a-8b6c-7d8e9f0a1b2c'`(服务端自生成时为 12 位 hex) |
|
||
| 关键词 | 直接输入,如 `支付失败` |
|
||
| 日志级别 | `@l = 'Error'` |
|
||
| 特定实例 | `instance = '192.168.1.10:8085'` |
|
||
| 地域 | `ip_country = 'CN'` |
|
||
| stdout | `source = 'stdout'` |
|
||
| panic | `source = 'panic'` |
|
||
| 组合 | `@l = 'Error' and sid = 'abc123def456'` |
|
||
| 关键字组合其他条件 | `@Message like '%支付失败%' and @l = 'Warning' and instance = '192.168.1.10:8085'` |
|
||
|
||
---
|
||
|
||
## 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`
|
||
|
||
---
|
||
|
||
## 相关文档
|
||
|
||
- [QuickStart_快速上手.md](QuickStart_快速上手.md) — 其中的 `that.Log` 是业务 logs 表,与本文 Seq 推送不同
|
||
- [GracefulShutdown_优雅停机.md](GracefulShutdown_优雅停机.md)
|
||
- [文档索引](README.md)
|