This commit is contained in:
yun
2026-09-14 00:14:30 +08:00
parent 316c6420a7
commit 7e1b474823
20 changed files with 1885 additions and 166 deletions
+74 -5
View File
@@ -27,6 +27,10 @@ log := loggerx.NewLogger(ctx,
loggerx.SetFlushInterval(200*time.Millisecond), // 定时刷盘,默认关闭
loggerx.SetEscapeHTML(false), // 是否转义 HTML,默认 true
loggerx.SetGID(false), // 是否记录 goroutine id,默认 true
loggerx.SetFormat(loggerx.FormatJSON), // 输出格式:FormatJSON(默认) / FormatText
loggerx.SetPrefix("[order-svc] "), // 每行日志前缀,便于区分来源
loggerx.SetMinLevel(loggerx.LevelInfo), // 最低输出级别,默认 LevelDebug(全输出)
loggerx.SetErrorHandler(onLogError), // 日志库自身故障回调(磁盘满/句柄失效等)
loggerx.SetTraceField("trace_id"), // trace 字段名,默认 trace_id
loggerx.SetErrorToInfo(), // error 是否同时写入 info 日志
loggerx.SetExpandData("app", "order"), // 每条日志追加固定字段
@@ -105,22 +109,85 @@ loggerx.SetWriteAsync() // 全局异步
### Gin 中间件
```go
g := gin.Default()
log := loggerx.NewLogger(context.Background(), loggerx.SetToConsole())
defer log.Close()
g.Use(middleware.SetGinTraceIdByLogger(log)) // 读取/生成 trace_id
g.Use(middleware.SetGinParams(log)) // 记录请求与响应
g := gin.New()
g.Use(gin.Recovery())
g.Use(middleware.SetGinTraceId(log)) // 读取/生成 trace id,并写回响应头 X-Trace-Id
g.Use(middleware.SetGinParams(log)) // 记录请求与响应(含 body,截断到 1000 字节)
g.GET("/ping", func(c *gin.Context) {
// trace id 同时注入 gin.Context 与 request context,两种写法日志里都能带上
log.Info(c, "via gin ctx")
log.Info(c.Request.Context(), "via request ctx")
c.JSON(200, gin.H{"trace_id": middleware.GetTraceId(c.Request.Context(), log.GetTraceField())})
})
```
trace id 的行为:
- 优先取请求头 `X-Trace-Id`(上游透传,跨服务串起同一条链路),没有才生成
- 生成后写回响应头,客户端/下游能拿到同一个 id
- 上下文里的 key 用自定义类型(不是裸 string),避免与第三方库撞键
- 需要自定义头名时用 `middleware.SetGinTraceIdByKey("trace_id", "X-Request-Id")`
### 与标准库 log 互通
`NewLogger` 会把全局 `log` 的输出接管到该实例,同时继承 `io.Writer`,可以直接传给任何需要 `io.Writer` 的地方:
默认**不接管**全局 `log`(只 import 本包不会产生任何副作用)。
需要把老代码里的 `log.Printf` 也收进日志文件时,显式打开:
```go
log := loggerx.NewLogger(ctx, loggerx.SetDir("./log"), loggerx.SetAsGlobalLog())
defer log.Close() // Close 会把全局 log 还原成接管前的 writer
```
`*Logger` 本身实现 `io.Writer`,也可以直接塞给任何需要 writer 的地方:
```go
log.SetOutput(loggerx.NewLogger(ctx, loggerx.SetDir("./log")))
```
### 输出格式与前缀
```go
// JSON(默认):每行一条合法 JSON,带 level 字段,推荐给采集端,也是性能最好的一种
{"level":"info","time":"2026-09-13 14:07:39.731954","file":"/main.go:20","func":"main","gid":"7","content":["hello"]}
// FormatText:单行紧凑文本,适合人直接看
level=info time=2026-09-13 14:07:39.731954 file=/main.go:20 func=main gid=7 content=hello
```
两者都支持 `SetPrefix`,前缀加在行首(`[order-svc] level=info ...`)。
text 格式下值里含空格/引号时会用 `%q` 包起来,保证一行一条且不歧义。
实测(本机 16 核):默认 JSON 约 10.2 µs/条,text 约 12.1 µs/条。
text 并非更快 —— 它走的是逐字段拼接,而 JSON 走的是已高度优化的 `encoding/json`
选 text 的理由是「人读着方便」,不是性能。
### 级别过滤
```go
loggerx.SetMinLevel(loggerx.LevelInfo) // 生产常用:Debug 直接丢弃,连 JSON 都不序列化
loggerx.SetMinLevel(loggerx.LevelOff) // 全关
```
### 把日志库自身的故障暴露出来
磁盘满、句柄失效、归档失败、刷盘超时这类问题默认只体现在返回值里,而调用方通常忽略返回值。
注册回调后可以接到告警或一个「不会失败」的输出:
```go
loggerx.NewLogger(ctx,
loggerx.SetDir("./log"),
loggerx.SetErrorHandler(func(err error) {
fmt.Fprintln(os.Stderr, "loggerx:", err)
}),
)
```
回调在写日志的调用栈上同步执行,务必保持轻量,且不要在里面再调用同一实例的日志方法。
## 落盘行为与保证
- **缓冲**:每条日志先写进 32KB 内存缓冲,写满才 `write` 一次系统调用。
@@ -157,7 +224,9 @@ log.SetOutput(loggerx.NewLogger(ctx, loggerx.SetDir("./log")))
1. [X] 按照时间分割
2. [X] 按照文件大小分割(`SetSizeSplit`
3. [ ] 按照日志行数分割
6. [ ] 支持日志级别过滤(`SetFormat("text")` 尚未生效
6. [X] 支持日志级别过滤(`SetMinLevel`
7. [X] 异步落盘(按实例隔离,不再是全局队列)
8. [X] 支持是否转义 HTML
9. [X] 支持定时刷盘(`SetFlushInterval`
10. [X] 支持 text / json 两种输出格式(`SetFormat`)与行前缀(`SetPrefix`
11. [ ] 支持采样(高频日志降采样)