# hiLogger **Repository Path**: hieroly/hi-logger ## Basic Information - **Project Name**: hiLogger - **Description**: 全AI构建的logger; 100% slog 兼容:嵌入 *slog.Logger; 自研轮转内核; 控制台 ANSI 分级多色; - **Primary Language**: Unknown - **License**: Apache-2.0 - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2026-10-24 - **Last Updated**: 2026-10-25 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # hiLogger 基于 Go 官方 `log/slog` 生态的高性能结构化日志库。 [English](README.EN.md) [![Go 1.26+](https://img.shields.io/badge/Go-1.26%2B-00ADD8?logo=go)](go.mod) [![License](https://img.shields.io/badge/License-Apache--2.0-blue)](LICENSE) [![Version](https://img.shields.io/badge/version-0.1.6-brightgreen)](CHANGELOG.md) - **前端 100% slog 兼容**:嵌入 `*slog.Logger`,`Info/Debug/Warn/Error` 等标准方法的源码位置(PC/行号)天然正确,用户代码零迁移成本;多 sink 广播基于标准库 `slog.MultiHandler`(Go 1.26+) - **自研轮转内核**:按大小 / 小时 / 天 / 月 / 混合(时间+大小)五种模式,支持备份数清理、过期删除、gzip 压缩归档;不依赖 lumberjack - **异步管道**:调用方 goroutine 编码 → 有界 channel → 单 writer goroutine 批量落盘;背压策略 `block / drop / drop_to_stderr` - **控制台 ANSI 分级多色**,支持运行时关闭与唤醒(原子开关,关闭期间零编码开销) - **时间格式**支持毫秒 / 微秒 / 纳秒精度,时区可配 - **Error+ 自动捕获调用栈**(过滤库内部帧,栈顶即用户代码);扩展 `Fatal/Panic` 级别 - ctx 透传(`trace_id/span_id`/自定义字段)、采样限流、敏感字段脱敏、Hook、内部指标 - 基线 **Go 1.26+**;配置支持 Options + TOML + `HILOGGER_` 环境变量 ## 安装 ```bash go get gitee.com/hieroly/hi-logger ``` 仓库地址: ```bash git clone https://gitee.com/hieroly/hi-logger.git ``` ### 国内网络提示 `go get` 会向 `sum.golang.org` 校验模块校验和。该节点在国内访问可能超时或返回 `Bad Gateway`,表现为: ``` verifying module: ... Get "https://sum.golang.org/lookup/gitee.com/hieroly/hi-logger@v0.1.0": Bad Gateway ``` 此时配置模块代理即可(代理会代查校验和): ```bash go env -w GOPROXY=https://goproxy.cn,direct ``` 不建议用 `GOSUMDB=off` 绕过 —— 那会失去校验和保护。 ### 依赖说明 **核心路径(编码、轮转、异步、采样、脱敏、栈捕获)只依赖标准库。** 唯一的三方依赖 `github.com/pelletier/go-toml/v2` 仅用于 `Load` / `LoadConfig` 两条配置加载入口;若你只用 `Options` 构造 logger,它不会进入你的二进制热路径 (未被引用的代码仍会被链接,但不会执行)。 ## 快速开始 ```go package main import ( "log/slog" "time" "gitee.com/hieroly/hi-logger" ) func main() { logger, err := hilogger.New( hilogger.WithLevel(slog.LevelDebug), hilogger.WithFile(true), hilogger.WithRotate(hilogger.RotateConfig{ Dir: "./logs", Mode: hilogger.ModeHybrid, Granularity: hilogger.ModeDaily, MaxSize: 100 << 20, MaxBackups: 30, MaxAge: 28 * 24 * time.Hour, Compress: true, }), ) if err != nil { panic(err) } defer logger.Close() logger.Info("hello", "module", "main") // 标准 slog 方法,行号正确 logger.Infof("user %s logged in", "alice") // printf 风格扩展 logger.Error("boom", "err", err) // Error+ 自动带调用栈 ctx := hilogger.With(context.Background(), "trace_id", "abc123") logger.InfoContext(ctx, "request done") // ctx 字段自动注入 logger.SetLevel(slog.LevelWarn) // 运行时热切换 logger.DisableConsole() // 关闭控制台(文件不受影响) logger.EnableConsole() // 随时唤醒 } ``` 接入标准库生态: ```go hilogger.SetDefault(logger) // 包级默认 + slog.SetDefault,第三方库的日志一并走 hiLogger log.SetOutput(hilogger.NewWriter(logger, slog.LevelInfo)) // 桥接标准 log 包 ``` 第三方框架接入(echo v5,完整可运行示例见 `examples/echo`): ```go e := echo.New() e.Logger = logger.Logger // 关键一行:echo v5 已全面转向 log/slog,零修改接入 e.Use(middleware.RequestLogger()) // 访问日志经 slog → hiLogger e.Use(middleware.Recover()) // panic 转 500 响应(日志本身已先落盘) e.GET("/", func(c *echo.Context) error { logger.InfoContext(c.Request().Context(), "hello", "path", c.Request().URL.Path) return c.String(http.StatusOK, "Hello, World!") }) ``` ## 配置(TOML) Options 是完整的能力超集,TOML 只是其序列化形式。参考模板见 [`configs/hilogger.example.toml`](configs/hilogger.example.toml): ```toml level = "info" encoder = "text" # 控制台编码:text|json|logfmt(文件固定 JSON) [time] format = "rfc3339ms" # rfc3339|rfc3339ms|rfc3339us|rfc3339ns|unix|unixmilli|unixmicro|custom timezone = "local" # local|utc|IANA 名 [file] enabled = true dir = "./logs" [file.rotate] mode = "hybrid" # size|hourly|daily|monthly|hybrid time_layout = "daily" # hybrid 的时间粒度 max_size = 100 # MB(与 lumberjack 同名同义) max_backups = 30 max_age = 28 # 天 compress = true [async] enabled = true queue_size = 8192 flush_interval = "100ms" on_overflow = "block" # block|drop|drop_to_stderr ``` 加载与覆盖: ```go logger, err := hilogger.Load("configs/hilogger.toml") logger, err := hilogger.Load("configs/hilogger.toml", hilogger.WithLevel(slog.LevelDebug)) ``` 优先级:**Options > 环境变量 > 文件 > 默认值**。环境变量为 `HILOGGER_` 前缀 + 表格路径(点转下划线、大写): ```bash HILOGGER_LEVEL=debug HILOGGER_FILE_ROTATE_MAX_SIZE=200 HILOGGER_CONSOLE_ENABLED=false ``` > Windows 注意:TOML 基本字符串(双引号)中反斜杠是转义符,路径请用正斜杠(`C:/logs/app`)或字面量字符串(单引号)。 ### 与标准库的行为一致性 hiLogger 的编码器与 `log/slog` 标准 Handler 行为**逐字节一致**,包括一些反直觉的细节: - 业务字段名与内置键(`time`/`level`/`msg`/`source`)冲突时,**输出重复键而非覆盖**——与标准库 `JSONHandler`/`TextHandler` 相同(如 `{"level":"INFO",...,"level":"CUSTOM"}`)。JSON 消费方需注意后一个值会覆盖前一个。 - `ReplaceAttr` 同样作用于内置键,可用于改写级别名或隐藏 `source`。 ## API 速览 ### 构造与配置 | Option | 说明 | |---|---| | `WithLevel(lv)` | 最低级别(`LevelTrace`/`LevelDebug`/…/`LevelFatal`) | | `WithConsole(bool)` / `WithColor(mode)` | 控制台开关 / 颜色模式(auto/always/never) | | `WithWriter(w)` | 控制台输出目标(默认 stdout) | | `WithFile(bool)` / `WithRotate(cfg)` / `WithLevelFile(lv, cfg)` | 文件输出 / 轮转配置 / 分级独立文件 | | `WithFileBuffered(bool)` | 文件写入缓冲(仅同步模式):32KB 缓冲 + 100ms 周期落盘,合并 write 系统调用 | | `WithAsync(bool)` / `WithAsyncConfig(q, b, iv, ov)` | 异步管道开关 / 完整参数 | | `WithEncoder(f)` / `WithTimeLayout(l)` / `WithTimezone(loc)` / `WithAddSource(bool)` | 编码与时间格式 | | `WithConsoleJSON(bool)` / `WithReplaceAttr(f)` | 控制台 JSON / 属性改写(slog 语义) | | `WithStack(mode, depth)` | 调用栈:`StackOff`/`StackAuto`/`StackAll` | | `WithSampling(interval, burst)` | 同点同消息采样限流 | | `WithRedactKeys(keys...)` / `WithRedactPatterns(res...)` | 敏感字段脱敏 | | `WithContextExtractor(f)` | 自定义 ctx → 字段提取器 | | `WithHook(h)` / `WithExitCode(code)` | Hook / Fatal 退出码 | ### Logger 方法 嵌入 `*slog.Logger`,标准方法(`Debug/Info/Warn/Error/Log/LogAttrs/With` 等)全部可用且 PC 行号正确;扩展方法: - Printf 风格:`Tracef` / `Debugf` / `Infof` / `Warnf` / `Errorf` - 终止语义:`Fatal` / `Fatalf`(写日志 → flush → `os.Exit`)、`Panic` / `Panicf`(写日志 → panic) - 运行时控制:`SetLevel` / `Level` / `EnableConsole` / `DisableConsole` / `ToggleConsole` / `ConsoleEnabled` - 生命周期:`Flush`(排空异步管道)/ `Close`(关闭并释放文件句柄) - 观测:`Metrics`(`Written`=进入 sink 链的记录数,每条计 1、与 sink 数量无关;采样抑制计 `Suppressed` 不计 `Written`;另有丢弃/字节/错误快照) ### 包级函数 `New` / `Load`(TOML+env)/ `NewFromConfig` / `Default` / `SetDefault`(同时 `slog.SetDefault`)/ `NewWriter`(桥接标准 `log` 包)/ `With` / `WithTraceID` / `WithSpanID` / `ParseLevel` / `ParseTimeFormat` / `ParseColorMode` / `ParseStackMode` / `ParseGranularity` / `ParseOverflow` ### 异步管道(公开 Sink) 需要把 hiLogger 的编码结果投递到**自定义落盘目标**时,可直接使用公开的异步管道: ```go type Sink interface { io.Writer; io.Closer } // *os.File / *hilogger.Rotator / 任意 io.WriteCloser 均可 p, err := hilogger.NewPipe(sink, 4096, 128, 100*time.Millisecond, hilogger.OverflowBlock, nil) if err != nil { /* ... */ } defer p.Close() p.Write([]byte(`{"level":"INFO","msg":"hello"}`+"\n")) // 投递 p.Flush() // 排空 ``` 管道关闭后再写入会返回 `hilogger.ErrPipeClosed`(不静默丢弃)。 与库内置文件 sink 的差别仅在于落盘目标由调用方指定;编码、批量合并、背压策略与内置路径完全一致。 ## 轮转命名规则 | 模式 | 活动文件 | 轮转后备份 | |---|---|---| | size | `app.log` | `app-2026-10-24T15-04-05.000.001.log`(复刻 lumberjack 命名:秒.毫秒.序号,序号防同毫秒撞名覆盖) | | hourly | `app-2026-10-24T15.log` | 窗口切换即轮转 | | daily | `app-2026-10-24.log` | 同上 | | monthly | `app-2026-10.log` | 同上 | | hybrid(daily+size) | `app-2026-10-24.log` | `app-2026-10-24.001.log`(窗口内序号) | ## 框架交互语义(重要) - **echo 的 `Recover()` 中间件会捕获 panic**:`logger.Panic()` 在 handler 内会被转成 500 响应而非进程崩溃(日志本身已先落盘);要穿透需移除 Recover 或外层自行 recover - `logger.Fatal()` 在请求处理内 = 直接杀进程,仅限关停路径 - 经中间件链路产生的访问日志,source 指向框架自身文件(slog 标准行为);自动调用栈会过滤 hiLogger 内部帧但保留中间件帧 - **并发写入的顺序语义**:文件 sink 由**单个 writer goroutine** 落盘,因此管道内的记录 严格有序;但"多个 goroutine 谁先写"本身由调度决定,**跨 goroutine 的交替顺序不保证全局一致**。 这是并发日志的固有语义(zap / slog 同样如此),不是乱序 bug。 若需要严格全局顺序,应在业务侧串行化后再打日志。 - **进程被强制终止会丢缓冲**:控制台输出被重定向时走 32KB bufio(见"性能"章节), 进程被 `SIGKILL` / Windows 强杀时,缓冲内未落盘的日志无法挽回。 收到 `SIGINT`/`SIGTERM` 时**务必调用 `logger.Close()` 或 `logger.Flush()`** (`Fatal`/`Panic` 内部已自动 Flush);文件 sink 走异步管道时同理。 文件 sink 若启用 `WithFileBuffered(true)`(未发布,仅同步模式), 强杀时最多丢 100ms 内的缓冲(默认关闭,语义不变)。 Windows 上请注意 Git Bash 的 `kill -TERM` 对原生进程无效, 触发优雅关闭请用 `python scripts/graceful-run.py `(CTRL_BREAK)。 - **`Close()` 之后的日志语义(关停排障必读,三种输出行为不同)**: 控制台输出**不受 Close 影响**,继续正常输出; 同步文件 sink 会报错(`Errors++` + 降级写 stderr,**可见**); 异步文件 sink 则**静默丢弃**——`slog.Handler` 接口没有错误回传通道, 管道 `Send` 走 closed 分支回收缓冲;该丢弃**计入 `Dropped` 指标** (v0.1.3 起,`Written` 与 `Dropped` 的差值即关闭后的丢失量)。 正确关停顺序:**停止业务 → `Flush()` → `Close()`**; 关闭后仍有残留 goroutine 打日志时,异步模式下这些记录会无声消失 (zap / lumberjack 生态同样要求"Close 是最后一个操作")。 ## 性能(本机基准,Go 1.27.3 / windows-amd64 / 16 核) | 基准 | ns/op | allocs/op | |---|---|---| | EncodeText(4 字段) | 140 | 0 | | EncodeText(彩色) | 140 | 0 | | EncodeJSON(4 字段) | 150 | 0 | | slog 原生 TextHandler(对照) | 408 | 0 | | 异步管道(编码+投递) | 724 | 4 | | 异步管道(16 核并行) | 818 | 4 | | 同步落盘(对照) | 2937 | 3 | | 同步落盘(`WithFileBuffered`,未发布) | 435 | 3 | | 控制台写文件(bufio,非 TTY) | 74 | 0 | | 控制台写文件(直写对照) | 2290 | 0 | | PC 缓存命中(addSource=true 热路径) | 25 | 0 | 编码路径零分配(时钟前缀缓存 + `sync.Pool` 缓冲复用),较 slog 原生 TextHandler 快约 **2.8 倍**; 异步管道较同步落盘约 4 倍吞吐,16 核并行下无扩展性退化。 **编码器热路径优化**(相对 0.1.0 基线,benchstat n=6,p<0.01): | 项 | 做法 | 收益 | |---|---|---| | 引号处理 | 单次遍历「边扫边写 + 回退」,纯可打印 ASCII 时绕开 `strconv.AppendQuote` 的 `IsPrint` 慢路径 | 文本 -52% | | JSON 转义 | 批量拷贝无需转义区间,取代逐字节 6 分支 switch | JSON -39% | | 级别前缀 | 7 个级别的完整前缀(含 ANSI)预生成为字节序列,省掉 map 查询与多次小 append | 含在文本收益内 | | 脱敏短路 | 未配置脱敏规则时跳过 `Redactor.ApplyAttr`(它是不可内联的跨包调用) | 含在文本收益内 | **非 ASCII 与字节一致性**:含非 ASCII 或控制字符的字符串一律回退标准库实现, 由 `compat_stdlib_test.go` 逐字节比对标准库 `slog.TextHandler` 守护。 **控制台 bufio**:输出被重定向(`./app > app.log`、管道、systemd 采集)时自动启用 32KB 缓冲, 把 N 次 `write` 系统调用合并为 1 次(实测 2290ns → 74ns,约 **31 倍**); 输出为 TTY 时保持直写以保证交互即时可见。缓冲有三条落盘路径 (写满 / 滞留超 200ms / 显式 `Flush`),且 `Fatal` 与 `Panic` 的退出路径都会先 `Flush`, 不会丢失最后一条(最关键的)日志。 **文件写入缓冲(`WithFileBuffered`,未发布,仅同步模式)**:与 go-logger 同机对比的评估发现, 同步模式每条日志一次 `write` 系统调用是主要开销(其 bufio 架构的优势几乎全部来自 syscall 合并)。 启用后写入先进入 32KB 缓冲,实测同步落盘 2937ns → 435ns(约 **6.8 倍**); 落盘路径有五条:缓冲将满 / 滞留超 100ms / 显式 `Flush` / `Close` 刷尾 / 轮转换页前。 默认关闭,保持「写入即落盘」的既有语义;异步管道已批量,该开关被忽略。 落盘失败可观测(`Metrics().Errors` + 首次 stderr 提示),不静默丢日志。 ## 测试 ```bash ./scripts/ci.sh # 全量:gofmt / build / vet / test / race / 集成测试 / examples / 注释覆盖 ./scripts/ci.sh --fast # 快速:跳过 race 与集成测试 ./scripts/ci.sh --api # 额外跑公开 API 零漂移双重校验 ``` 也可单独执行: ```bash go test -race -count=1 ./... # 含 PC 行号正确性、假时钟全模式轮转、并发压测 go test -tags=integration -count=1 ./test/ # 集成测试(真实写盘 / 轮转 / 采样窗口) go test -run='^$' -bench=. ./... go run ./tools/doccheck . # 导出符号 godoc 注释覆盖率(要求 100%) ``` 并发安全由 `race_test.go`(16 个用例)专门守护,覆盖:并发 AddHook、级别热切换、控制台开关、Flush/Close 交错、写入中关闭、采样并发、轮转高压、异步压缩去重、生产形态综合压力(并发写 + 动态级别 + 动态钩子 + 轮转 + 优雅关闭,并逐行校验 JSON 完整性)。 **关闭语义**:`Close()` 与 `Flush()` 可与写入并发调用,不会死锁;`Close` 幂等;关闭后继续写日志不会 panic(记录直接丢弃并回收缓冲)。 ## 源码结构 源码按「公开门面 + 内部实现」两层组织:根包只保留**公开 API**,实现细节下沉到 `internal/` 的专职子包(可见性由 Go 工具链强制,外部不可导入)。 根包文件划分**对齐 Go 官方 `log/slog` 的惯例**:包文档独立成 `doc.go`, `Logger` 与全局 `Default()`/`SetDefault()` 同处 `logger.go`,其余按**概念**分文件。 ``` 根包(10 个实现文件) ├── doc.go # 包文档(仅文档,无代码) ├── logger.go # Logger:嵌入 *slog.Logger + Infof/Fatal/Panic + Default/SetDefault ├── option.go # Option 集合 + settings → dispatch.Config 投影 ├── config.go # Config + TOML 加载 + HILOGGER_ 环境变量覆盖 ├── types.go # 公开类型别名与常量再导出 ├── context.go # ctx 透传入口(转发 internal/ctxprop) ├── sink.go # 公开 Sink 接口 + Pipe ├── stack.go # 栈模式别名与解析(转发 internal/stack) ├── bridge.go # io.Writer ↔ slog.Handler 桥接 └── version.go # const Version internal/(16 个子包) dispatch · encoding · rotate · async · console · stack · ctxprop sampling · redact · hook · level · metrics · pccache · clockcache · bufpool · attr 其他目录 configs/ 参考 TOML 模板 test/ 集成测试(//go:build integration) examples/ 5 个示例(独立 module) tools/ 开发期 Go 工具(5 个) scripts/ ci.sh + 优雅关闭脚本 .workflow/ Gitee Go 流水线 ``` > 完整的**逐文件**结构、每个子包的职责、测试文件分布与设计取舍, > 见 [docs/DESIGN.md §5](docs/DESIGN.md)(本文档不重复维护同一份结构,避免漂移; > 英文版见 [docs/DESIGN.EN.md §5](docs/DESIGN.EN.md))。 ### 版本与兼容性 - 版本号遵循 [SemVer 2.0.0](https://semver.org/lang/zh-CN/),运行时可由 `hilogger.Version` 读取。 - 当前处于 **0.x** 阶段:次版本号(0.**1**.0 → 0.**2**.0)递增可能包含不兼容变更, 补丁版本号只做向后兼容修复。 - 变更记录见 [CHANGELOG.md](CHANGELOG.md);安全策略见 [SECURITY.md](SECURITY.md); 贡献约定见 [CONTRIBUTING.md](CONTRIBUTING.md)。 ### 测试文件布局(Go 社区共识) 测试文件**与被测包同目录**,通过**包名区分内外测试**(这是 Go 官方推荐做法): | 包名 | 含义 | 访问权限 | | --- | --- | --- | | `package xxx` | 内部测试 | 可访问未导出符号(白盒) | | `package xxx_test` | 外部测试 | 只能访问导出符号(黑盒,模拟真实使用者) | 两种测试文件**同目录共存**,例如 `internal/pccache/` 下同时存在 `pccache_test.go`(`package pccache_test`,测导出 API)与 `pccache_internal_test.go`(`package pccache`,测 `c.mu` 等内部不变量)。 根目录的**集成测试**单独放在 `test/` 目录,并用构建标签隔离,避免拖慢默认测试流程: ```bash go test ./... # 默认:不含集成测试 go test -tags=integration ./... # 显式执行集成测试 ``` 集成测试从外部视角使用公开 API(`package integration` + `import "gitee.com/hieroly/hi-logger"`), 可同时验证「公开 API 足够支撑端到端使用」这一点。 公开类型通过**类型别名**再导出(如 `type Metrics = metrics.Counters`),外部代码 `hilogger.Metrics{}` 与之完全等价。 重构前后通过**双重口径**校验:**AST 符号集比对** + **外部消费者程序编译通过**(遍历 21 类公开 API 形态)。 > 本轮结构整理的 API 增量仅三项,均为**新增**且向后兼容: > `type Sink`(`io.Writer` + `io.Closer`,作为异步管道落盘目标)、 > `method Pipe.Write`(此前 `Pipe` 只能 `Flush`/`Close`,外部拿到也无法投递数据)、 > `var ErrPipeClosed`(管道关闭后写入会明确报错,而非静默丢弃)。 > 原本 `NewPipe` 首参是未导出的 `*fileSink`,外部根本无法调用——现已改为公开 `Sink` 接口, > `*os.File`、`*Rotator` 及任意 `io.WriteCloser` 均可直接作为落盘目标。 ## 生态适配验证 - `slog.SetDefault` / `MultiHandler` / `LogValuer` / `ReplaceAttr` / `WithGroup` 全契约 - echo v5 集成示例:`examples/echo`(`e.Logger = logger.Logger` 零修改接入,已实测跑通访问日志、级别热切换、panic 恢复、错误栈) - echo v5 + mustache 组合示例:`examples/webapp`(验证无日志接口第三方库的共存,已实测跑通模板渲染、错误桥接、ctx 双用) - 详见 `docs/DESIGN.md` §9.1 适配验证矩阵与 §10 场景详设 ### 第三方库接入实测结论 | 库 | 日志接口 | 接入方式 | |---|---|---| | echo v5 | 原生 `*slog.Logger`(`Echo.Logger` 字段,nil 回退 `slog.Default()`) | 赋值一行,零修改 | | mustache | 无(错误走返回值,内部不碰全局 logger) | 错误桥接:`ErrorContext(ctx, "template render failed", "err", err)` | > **mustache 的模块路径沿革**(实测核实):v1 及以前是 `github.com/cbroglie/mustache` > (最新 v1.4.2,已停止维护);**v2 起改为 `github.com/runZeroInc/mustache/v2`** > (最新 v2.1.0,因引入不兼容的 fluent API 而带 `/v2` 语义化后缀)。示例使用 v2: > `mustache.New().WithErrors(true).CompileString(tpl)` → `tmpl.Render(data)`。 ## 设计文档 [docs/DESIGN.md](docs/DESIGN.md) —— 需求映射、架构、核心设计决策、配置 schema、API 草案、文件结构(§5,唯一权威描述)、测试布局、风险登记。英文版:[docs/DESIGN.EN.md](docs/DESIGN.EN.md)。 ## 许可证 [Apache-2.0](LICENSE)。再分发时请保留 [NOTICE](NOTICE) 中的版权声明。 版权所有 2026 hieroly。