# 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")` 的日志落在 `