5.8 KiB
5.8 KiB
@go/log
Maintainer Statement: 本项目完全由 AI 维护。任何改动均遵循代码质量与性能的最佳实践。
🎯 设计哲学
@go/log 旨在提供高性能、零摩擦的异步日志系统。其核心目标是:
- 极致高性能:采用 Meta-Driven Positional Array (元数据驱动定长数组) 架构。日志以单行 JSON 数组 (
[...]) 形式落盘,消除 Key 冗余与装箱开销,性能提升数倍。 - 架构解耦:元数据外置于
.log.meta.json。日志包仅负责高速序列化,可视化由外部工具或Viewable接口根据元数据动态渲染。 - 零摩擦入口:自动识别环境上下文(应用名、IP等),无需手动构建。
- 语义脱敏:内置敏感信息(如手机号、密钥)的自动脱敏。
- 高度可扩展:支持多种写入渠道(文件切分、Elasticsearch批量传输)。
📦 安装
go get apigo.cc/go/log
💡 快速开始
import "apigo.cc/go/log"
// 默认 logger (通过 log.json 或环境变量配置)
func main() {
log.Info("服务启动", "port", 8080)
log.Error("数据库连接失败", "db", "mysql")
// 创建带 traceId 的新 logger 实例
logger := log.New("trace-xyz-123")
}
⚙️ 配置 (Configuration)
本包深度集成 @go/config,支持多种灵活的配置方式,优先级从高到低:
- 环境变量 (最高优先级)
- 环境特定文件 (
env.json/env.yml,需增加层级log:) - 基础配置文件 (
log.json/log.yml)
1. 配置文件 (log.json)
在项目根目录创建 log.json 或 log.yml:
{
"name": "my-cool-app",
"level": "info",
"file": "logs/app.log",
"splitTag": ".2006-01-02",
"sensitive": "phone,password,secret,token,key"
}
2. 环境变量 (最高优先级)
任何配置都可以通过环境变量覆盖,变量名规则为 LOG_ + 字段名。
# 覆盖日志级别和输出文件
export LOG_LEVEL=debug
export LOG_FILE=console
配置项说明
name: 应用名称 (默认读取 DISCOVER_APP 或从go.mod自动识别)。level: 日志级别 (debug,info,warning,error)。file: 输出目标。console: 直接输出到控制台(默认)。path/to/file.log: 输出到指定文件。es://...或ess://...: 输出到 Elasticsearch。
splitTag: 文件切分格式,仅当file为文件路径时有效。- 语法遵循 Go 标准的
time.Format布局,如".2006-01-02"(按天切分),".2006-01-02-15"(按小时切分)。
- 语法遵循 Go 标准的
truncations: 堆栈信息截断前缀(多个以逗号分隔,默认截断github.com/,golang.org/,/apigo.cc/)。sensitive: 需要自动脱敏的字段名(多个以逗号分隔,不区分大小写),默认处理phone,password,secret,token,key。
🛠 API 指南
核心功能
-
分级记录
Debug,Info,Warning,Error—— 标准日志方法,支持message+ 变长extra参数。
-
通用记录 (
Log)Log(LogEntry)—— 记录自定义结构的日志。
-
独立可视化工具 (
logv)- 安装:
go install apigo.cc/go/log/logv@latest - 使用:
tail -f app.log | logv或tail -f app.log | logv -json。
- 安装:
自定义日志扩展 (规范)
为保证高性能与内存安全,扩展自定义日志类型必须遵循以下规范:
-
定义结构体
- 必须嵌入
log.BaseLog(或其子类,如log.ErrorLog)。 - 索引 (
pos) 规范:0-6由BaseLog。- 业务字段从
6开始紧凑递增编号 (pos:6,pos:7, ...),如果删除了某个字段请留空 pos 以实现向前兼容。 - 如果继承自
ErrorLog等,则业务字段应从7开始(查询父类最大值 + 1)。 Extra固定使用pos:1000,CallStacks固定使用pos:1001(它们会被自动平移到数组末尾)。
- 必须嵌入
-
实现
Reset()方法 (强制)- 必须重写
Reset()方法以初始化/清空数据避免对象池复用时产生脏数据。 Reset()方法中必须首先调用父级的Reset()(如l.BaseLog.Reset())。- 安全保障: 若未重写
Reset,RegisterType将在启动时 Panic,以防止对象池复用时产生脏数据。 - 建议: map / slice 类型建第一次初始化一个容量,之后使用 clear() 方法清空数据避免内存重复分配。
- 必须重写
-
注册模型
- 在
init()中调用log.RegisterType("my-type", MyLog{})完成注册。
- 在
示例: DBErrorLog
package main
import "apigo.cc/go/log"
// 1. 定义结构体 (字段从 pos:6 开始)
type DBErrorLog struct {
log.ErrorLog // 嵌入 ErrorLog,自动获得 Error 和 CallStacks 字段
DB string `log:"pos:7,color:blue"`
SQL string `log:"pos:8"`
Args []any `log:"pos:9"`
UsedTime float32 `log:"pos:10,color:cyan"`
}
// 2. 实现 Reset() 方法 (强制)
func (l *DBErrorLog) Reset() {
l.ErrorLog.Reset() // 必须先调用父级 Reset
l.DB = ""
l.SQL = ""
if l.Args == nil {
l.Args = make([]any, 0, 10)
} else {
clear(l.Args) // 清空内容
l.Args = l.Args[:0] // 清空长度
}
l.UsedTime = 0
}
// 3. 注册
func init() {
log.RegisterType("dbError", &DBErrorLog{})
}
// 4. 使用示例
func LogDBError(logger *log.Logger, db, sql string, args []any, err error, usedTime float32) {
entry := log.GetEntry[DBErrorLog]()
// 自动填充基础字段和 ErrorLog 字段
logger.FillError(&entry.ErrorLog, err.Error())
// 填充自定义字段
entry.DB = db
entry.SQL = sql
entry.Args = append(entry.Args, args...)
entry.UsedTime = usedTime
logger.Log(entry)
}
🧪 验证状态
测试全部通过,异步写入与性能达标。
详见:TEST.md