174 lines
5.8 KiB
Markdown
174 lines
5.8 KiB
Markdown
# @go/log
|
||
|
||
> **Maintainer Statement:** 本项目完全由 AI 维护。任何改动均遵循代码质量与性能的最佳实践。
|
||
|
||
## 🎯 设计哲学
|
||
|
||
`@go/log` 旨在提供高性能、零摩擦的异步日志系统。其核心目标是:
|
||
|
||
* **极致高性能**:采用 **Meta-Driven Positional Array (元数据驱动定长数组)** 架构。日志以单行 JSON 数组 (`[...]`) 形式落盘,消除 Key 冗余与装箱开销,性能提升数倍。
|
||
* **架构解耦**:元数据外置于 `.log.meta.json`。日志包仅负责高速序列化,可视化由外部工具或 `Viewable` 接口根据元数据动态渲染。
|
||
* **零摩擦入口**:自动识别环境上下文(应用名、IP等),无需手动构建。
|
||
* **语义脱敏**:内置敏感信息(如手机号、密钥)的自动脱敏。
|
||
* **高度可扩展**:支持多种写入渠道(文件切分、Elasticsearch批量传输)。
|
||
|
||
## 📦 安装
|
||
|
||
```bash
|
||
go get apigo.cc/go/log
|
||
```
|
||
|
||
## 💡 快速开始
|
||
|
||
```go
|
||
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`,支持多种灵活的配置方式,优先级从高到低:
|
||
|
||
1. **环境变量** (最高优先级)
|
||
2. **环境特定文件** (`env.json` / `env.yml`,需增加层级 `log:`)
|
||
3. **基础配置文件** (`log.json` / `log.yml`)
|
||
|
||
### 1. 配置文件 (`log.json`)
|
||
|
||
在项目根目录创建 `log.json` 或 `log.yml`:
|
||
|
||
```json
|
||
{
|
||
"name": "my-cool-app",
|
||
"level": "info",
|
||
"file": "logs/app.log",
|
||
"splitTag": ".2006-01-02",
|
||
"sensitive": "phone,password,secret,token,key"
|
||
}
|
||
```
|
||
|
||
### 2. 环境变量 (最高优先级)
|
||
|
||
任何配置都可以通过环境变量覆盖,变量名规则为 `LOG_` + `字段名`。
|
||
|
||
```bash
|
||
# 覆盖日志级别和输出文件
|
||
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"` (按小时切分)。
|
||
* `truncations`: 堆栈信息截断前缀(多个以逗号分隔,默认截断 `github.com/`, `golang.org/`, `/apigo.cc/`)。
|
||
* `sensitive`: 需要自动脱敏的字段名(多个以逗号分隔,不区分大小写),默认处理 `phone,password,secret,token,key`。
|
||
|
||
## 🛠 API 指南
|
||
|
||
### 核心功能
|
||
|
||
1. **分级记录**
|
||
* `Debug`, `Info`, `Warning`, `Error` —— 标准日志方法,支持 `message` + 变长 `extra` 参数。
|
||
|
||
2. **通用记录 (`Log`)**
|
||
* `Log(LogEntry)` —— 记录自定义结构的日志。
|
||
|
||
3. **独立可视化工具 (`logv`)**
|
||
* **安装**: `go install apigo.cc/go/log/logv@latest`
|
||
* **使用**: `tail -f app.log | logv` 或 `tail -f app.log | logv -json`。
|
||
|
||
### 自定义日志扩展 (规范)
|
||
|
||
为保证高性能与内存安全,扩展自定义日志类型必须遵循以下规范:
|
||
|
||
1. **定义结构体**
|
||
* 必须嵌入 `log.BaseLog` (或其子类,如 `log.ErrorLog`)。
|
||
* **索引 (`pos`) 规范**:
|
||
* `0`-`6` 由 `BaseLog`。
|
||
* 业务字段从 `6` 开始紧凑递增编号 (`pos:6`, `pos:7`, ...),如果删除了某个字段请留空 pos 以实现向前兼容。
|
||
* 如果继承自 `ErrorLog` 等,则业务字段应从 `7` 开始(查询父类最大值 + 1)。
|
||
* `Extra` 固定使用 `pos:1000`,`CallStacks` 固定使用 `pos:1001` (它们会被自动平移到数组末尾)。
|
||
|
||
2. **实现 `Reset()` 方法 (强制)**
|
||
* **必须**重写 `Reset()` 方法以初始化/清空数据避免对象池复用时产生脏数据。
|
||
* `Reset()` 方法中必须首先调用父级的 `Reset()` (如 `l.BaseLog.Reset()`)。
|
||
* **安全保障**: 若未重写 `Reset`,`RegisterType` 将在启动时 **Panic**,以防止对象池复用时产生脏数据。
|
||
* **建议**: map / slice 类型建第一次初始化一个容量,之后使用 clear() 方法清空数据避免内存重复分配。
|
||
|
||
3. **注册模型**
|
||
* 在 `init()` 中调用 `log.RegisterType("my-type", MyLog{})` 完成注册。
|
||
|
||
#### 示例: `DBErrorLog`
|
||
|
||
```go
|
||
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](./TEST.md)
|