Files
loggerx/readme.md
T
2026-09-13 21:53:37 +08:00

164 lines
6.9 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# loggerx
基于 Go 原生 `log` 封装的日志库:支持按时间切分文件、同步/异步落盘、channel 分目录、Gin 中间件。
```go
log := loggerx.NewLogger(ctx, loggerx.SetDir("./log"), loggerx.SetToConsole())
defer log.Close() // 退出前务必调用,否则最后一批日志(最多 32KB)不会落盘
log.Info(ctx, "hello")
log.Infof(ctx, "hello %s", "world")
```
## 用法
### 创建与选项
```go
log := loggerx.NewLogger(ctx,
loggerx.SetDir("./log"), // 日志目录,默认 ./log
loggerx.SetToConsole(), // 同时输出到控制台
loggerx.SetDays(7), // 保留天数,默认 7;<=0 表示不删除
loggerx.SetTimeZone(time.FixedZone("CST", 8*3600)), // 时区,默认 time.Local
loggerx.SetFileSplit(loggerx.FileSplitTimeE), // 时间切割方式,默认按天
loggerx.SetSizeSplit(64<<20), // 单文件上限 64MB,超过则滚动归档
loggerx.SetCompress(true), // 归档是否压成 .gz,默认 true
loggerx.SetCompressLevel(gzip.BestCompression), // 压缩级别 1~9,默认 BestSpeed
loggerx.SetFlushInterval(200*time.Millisecond), // 定时刷盘,默认关闭
loggerx.SetEscapeHTML(false), // 是否转义 HTML,默认 true
loggerx.SetGID(false), // 是否记录 goroutine id,默认 true
loggerx.SetTraceField("trace_id"), // trace 字段名,默认 trace_id
loggerx.SetErrorToInfo(), // error 是否同时写入 info 日志
loggerx.SetExpandData("app", "order"), // 每条日志追加固定字段
loggerx.SetExtraDriver(f, hooks), // 额外落盘驱动(实现 io.Writer 即可)
loggerx.SetPrintFile(false), // 不写文件,只走驱动
)
```
### 定时刷盘:把崩溃丢失窗口压到最小
默认不刷盘时,日志攒在 32KB 内存缓冲里,进程被 `kill -9` 最多丢 32KB。
开启定时刷盘后,丢失量收敛为「一个间隔内产生的日志量」:
```go
log := loggerx.NewLogger(ctx,
loggerx.SetDir("./log"),
loggerx.SetFlushInterval(200*time.Millisecond), // 每 200ms 刷一次
)
```
刷盘由后台协程完成,失败只记日志、不会中断写入;`Close()` 会等它退出。
### 按大小切割 + 自动压缩归档
```go
log := loggerx.NewLogger(ctx,
loggerx.SetDir("./log"),
loggerx.SetSizeSplit(64<<20), // 每个文件最多 64MB
loggerx.SetCompress(true), // 滚动后压成 .gz(默认开)
)
```
滚动过程(不阻塞写入):
1. 关掉当前句柄(关句柄会先清空缓冲,归档内容因此完整)
2. 改名成 `2026-09-13_info_3.log`(先改名,任何时刻文件都存在,崩溃也不丢)
3. 后台协程压缩成 `2026-09-13_info_3.log.gz`,成功后删掉未压缩文件
细节保证:
- **序号全局递增**,且跳过已存在的文件,不覆盖历史归档;跨进程重启也从目录里续号
- **单条日志超长**(比上限还大)不会死循环,最坏就是该文件超限
- **归档失败**(如权限问题)时继续写原文件,宁可文件大一点也不丢日志
- `Close()` 会等压缩协程收尾,返回后读归档不会读到半截 `.gz`
- 过期清理(`SetDays`)同时作用于 `.log``.gz`
### 文件切割
| 取值 | 目录/文件名形态 |
| --- | --- |
| `FileSplitNone` | `info.log` |
| `FileSplitTimeA` | `2026/09/13/15_info.log` |
| `FileSplitTimeB` | `2026/09/13_info.log` |
| `FileSplitTimeC` | `2026/09-13_info.log` |
| `FileSplitTimeD` | `2026-09-13-15_info.log` |
| `FileSplitTimeE` | `2026-09-13_info.log`(默认) |
### channel 分目录
```go
log.Channel("order").Info(ctx, "下单") // 落到 ./log/order/2026-09-13_info.log
log.Channel("pay").Error(ctx, "支付失败")
```
### 同步 / 异步
```go
log.Info(ctx, "默认同步") // 写入内存缓冲(32KB 满才落盘)
log.WriteAsync().Info(ctx, "这条异步") // 交给后台协程消费
loggerx.SetWriteAsync() // 全局异步
```
异步是**有界阻塞队列**(容量 1000):队列满时写入方会等待,不会丢日志。
`Close()` 会等队列排空并把缓冲刷盘。
### 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)) // 记录请求与响应
```
### 与标准库 log 互通
`NewLogger` 会把全局 `log` 的输出接管到该实例,同时继承 `io.Writer`,可以直接传给任何需要 `io.Writer` 的地方:
```go
log.SetOutput(loggerx.NewLogger(ctx, loggerx.SetDir("./log")))
```
## 落盘行为与保证
- **缓冲**:每条日志先写进 32KB 内存缓冲,写满才 `write` 一次系统调用。
- **完整性**:以下四种方式都会把缓冲落盘 —— 缓冲写满、`Close()``MustSync()``SetFlushInterval` 定时器。
- **崩溃语义**:进程被 `kill -9`、panic 未恢复、断电时,**最多丢失最后 32KB** 未落盘的日志;
开启 `SetFlushInterval` 后,丢失量收敛到「一个间隔内产生的日志量」。
- **关闭语义**`Close()` 之后该实例不再接受写入(返回 `loggerx: 日志已关闭`);
会等异步队列排空、等压缩归档收尾;重复调用安全;不泄漏文件句柄。
- **写失败**:写文件失败会重试一次(重开句柄);磁盘满等持续失败时该条日志会丢,
错误通过 `io.Writer` 语义返回给调用方,建议对 `Write`/`MustSync`/`Close` 的返回值做检查。
- **channel 隔离**`Channel("x")` 的日志落在 `<dir>/x/` 子目录,同步与异步模式都成立。
## 性能
本机(Windows / 16 核)实测,Go 1.26
| 场景 | 单条耗时 | 分配 |
| --- | --- | --- |
| `SetGID(false)` + 只走驱动 | 3.4 µs | 14 |
| 默认(含 gid,写文件) | 10.8 µs | 15 |
| 并行写入(16 goroutine | 19.6 µs | 17 |
单条 10.8 µs 中约 4.3 µs 花在采集 goroutine id 上。对延迟敏感、日志量大的场景建议
`SetGID(false)`,可省掉约 40% 开销。开启 `SetSizeSplit` 后每条日志会多一次
`Stat`(约 0.6 µs)用于判断是否该滚动;不配置大小切割则没有这笔开销。
## 开发计划
1. [X] 自动清除过期的日志文件(`.log``.gz` 都清)
2. [X] 支持日志文件压缩(滚动归档自动 gzip)
3. [X] 支持日志文件切割
4. [ ] 支持日志文件归档到对象存储
5. 支持多种文件分割类型
1. [X] 按照时间分割
2. [X] 按照文件大小分割(`SetSizeSplit`
3. [ ] 按照日志行数分割
6. [ ] 支持日志级别过滤(`SetFormat("text")` 尚未生效)
7. [X] 异步落盘(按实例隔离,不再是全局队列)
8. [X] 支持是否转义 HTML
9. [X] 支持定时刷盘(`SetFlushInterval`