Compare commits
No commits in common. "main" and "v1.5.9" have entirely different histories.
16
CHANGELOG.md
16
CHANGELOG.md
@ -1,21 +1,5 @@
|
||||
# Changelog
|
||||
|
||||
## [1.5.11] - 2026-08-24
|
||||
- **Log Viewer 元数据修复**:
|
||||
- Meta 持久化改为合并磁盘已有类型,避免独立 `logv` 启动时用基础日志类型覆盖应用的 `api_call`、`request` 等业务 Meta。
|
||||
- 修复业务日志在 `logv` 中退化为无颜色、无字段映射的数组输出。
|
||||
- **依赖更新**: 对齐 `shell v1.5.6`。
|
||||
|
||||
## [1.5.10] - 2026-08-24
|
||||
- **默认配置扩展**:
|
||||
- 新增 `SetDefaultConfig`,允许应用提供内置默认配置,并继续由配置文件和环境变量覆盖。
|
||||
- 最终有效配置未变化时复用现有 `DefaultLogger`,避免重复初始化写入资源。
|
||||
- **切分文件可靠性**:
|
||||
- 修复默认 logger 初始化早于 writer service 时写入 nil 文件映射导致的 panic。
|
||||
- 文件 logger 初始化时自动创建父目录。
|
||||
- split 文件日志在异步 writer 启动前同步落盘,避免启动日志回退到终端。
|
||||
- **依赖更新**: 对齐 `cast v1.5.5`、`config v1.5.4`、`file v1.5.6`、`id v1.5.7`、`shell v1.5.5`、`encoding v1.5.6`、`rand v1.5.4`、`safe v1.5.3`。
|
||||
|
||||
## [1.5.9] - 2026-07-18
|
||||
- **依赖校验修复**: 重新整理模块 checksum,并对齐 `x/crypto v0.52.0`、`x/sys v0.45.0`。
|
||||
|
||||
|
||||
20
README.md
20
README.md
@ -25,12 +25,6 @@ import "apigo.cc/go/log"
|
||||
|
||||
// 默认 logger (通过 log.json 或环境变量配置)
|
||||
func main() {
|
||||
// 应用内置预设;log.json/log.yml、env 配置和环境变量仍可覆盖
|
||||
log.SetDefaultConfig(log.Config{
|
||||
File: "logs/app.log",
|
||||
SplitTag: ".2006-01-02",
|
||||
})
|
||||
|
||||
// 在微服务场景下动态设置应用名称
|
||||
log.SetDefaultName("my-microservice")
|
||||
|
||||
@ -88,29 +82,23 @@ export LOG_FILE=console
|
||||
* `truncations`: 堆栈信息截断前缀(多个以逗号分隔,默认截断 `github.com/`, `golang.org/`, `/apigo.cc/`)。
|
||||
* `sensitive`: 需要自动脱敏的字段名(多个以逗号分隔,不区分大小写),默认处理 `phone,password,secret,token,accessToken,authorization`。
|
||||
|
||||
文件输出会在 logger 初始化时自动创建父目录。使用 `splitTag` 时,异步 writer service 启动前产生的日志会同步写入目标文件,启动后自动切换为异步写入。
|
||||
|
||||
## 🛠 API 指南
|
||||
|
||||
### 核心功能
|
||||
|
||||
1. **应用默认配置**
|
||||
* `SetDefaultConfig(Config)` —— 设置应用内置预设,然后加载配置文件与环境变量覆盖;最终有效配置未变化时复用当前默认 logger。
|
||||
|
||||
2. **分级记录**
|
||||
1. **分级记录**
|
||||
* `Debug`, `Info`, `Warning`, `Error` —— 标准日志方法,支持 `message` + 变长 `extra` 参数。
|
||||
|
||||
3. **摩擦消除 (`As`)**
|
||||
2. **摩擦消除 (`As`)**
|
||||
* `As(v, err)` —— 仿照 `cast.As`,忽略错误并返回零值,但会自动将错误记录到日志中。支持全局调用 (`log.As`) 或实例调用 (`logger.As`)。
|
||||
* **优势**: 在类型转换或快速赋值场景下,无需繁琐的 `if err != nil` 判断,同时确保异常被记录。
|
||||
|
||||
4. **通用记录 (`Log`)**
|
||||
3. **通用记录 (`Log`)**
|
||||
* `Log(LogEntry)` —— 记录自定义结构的日志。
|
||||
|
||||
5. **独立可视化工具 (`logv`)**
|
||||
4. **独立可视化工具 (`logv`)**
|
||||
* **安装**: `go install apigo.cc/go/log/logv@latest`
|
||||
* **使用**: `tail -f app.log | logv` 或 `tail -f app.log | logv -json`。
|
||||
* `logv` 从当前目录加载 `.log.meta.json`;基础 Meta 会与应用已有业务 Meta 合并,不会覆盖业务日志类型。
|
||||
|
||||
### 自定义日志扩展 (规范)
|
||||
|
||||
|
||||
24
TEST.md
24
TEST.md
@ -1,27 +1,5 @@
|
||||
# Test Results
|
||||
|
||||
## v1.5.11 验证
|
||||
- 验证 Meta 同步保留磁盘已有业务类型,并更新当前进程注册的同名类型。
|
||||
- 验证本地 `logv` 格式化 `request` 日志时恢复时间、字段映射、隐藏规则和 ANSI 颜色。
|
||||
- 验证 `logv` 运行后 `.log.meta.json` 仍保留 `api_call` 与 `request`。
|
||||
- 全量单元测试、竞态测试和基准测试均通过。
|
||||
|
||||
## v1.5.10 验证
|
||||
- 验证 split 文件 logger 在包初始化阶段安全创建,不依赖文件级 `init` 顺序。
|
||||
- 验证文件 logger 自动创建父目录。
|
||||
- 验证异步 writer 启动前的 split 日志同步写入目标文件。
|
||||
- 验证代码默认配置保留未被配置文件覆盖的字段,并由配置文件覆盖已配置字段。
|
||||
- 验证最终有效配置不变时 `SetDefaultConfig` 复用现有 logger。
|
||||
- 全量单元测试、竞态测试和基准测试均通过。
|
||||
|
||||
### Benchmark(Apple M3 Max / arm64)
|
||||
|
||||
```text
|
||||
BenchmarkLoggerInfo-16 889536 1361 ns/op
|
||||
BenchmarkLoggerAsyncConcurrent-16 4128828 286.5 ns/op
|
||||
BenchmarkLogger_RequestLog_Realistic-16 1000000 1111 ns/op 975 B/op 27 allocs/op
|
||||
```
|
||||
|
||||
## v1.5.9 验证
|
||||
- 独立模块解析、全量测试与基准测试均通过。
|
||||
|
||||
@ -85,5 +63,3 @@ ok apigo.cc/go/log 3.301s
|
||||
- **深度脱敏能力**: `TestDeepDesensitization` 闭环验证了对复杂嵌套结构的脱敏逻辑。
|
||||
- **可靠性边界**: `TestLoggerReliability` 模拟了极高压力下的日志丢弃与缓冲策略。
|
||||
- **文件切分**: `TestSplitTag` 实测了基于时间滚动的文件切分能力。
|
||||
- **启动期文件写入**: `TestLoggerCore_SplitFileWritesBeforeWriterServiceStarts` 验证异步服务启动前日志直接写入 split 文件。
|
||||
- **默认配置覆盖**: `TestLoadDefaultConfig` 与 `TestSetDefaultConfigKeepsLoggerWhenEffectiveConfigIsUnchanged` 验证代码预设、文件覆盖和无变化复用行为。
|
||||
|
||||
117
core_test.go
117
core_test.go
@ -1,12 +1,8 @@
|
||||
package log
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestLoggerCore_Initialization(t *testing.T) {
|
||||
@ -22,119 +18,6 @@ func TestLoggerCore_Initialization(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestLoggerCore_SplitFileInitialization(t *testing.T) {
|
||||
logFile := filepath.Join(t.TempDir(), "logs", "server.log")
|
||||
logger := NewLogger(Config{
|
||||
File: logFile,
|
||||
SplitTag: "20060102",
|
||||
})
|
||||
|
||||
if logger.file == nil {
|
||||
t.Fatal("expected split file writer to be initialized")
|
||||
}
|
||||
if _, err := os.Stat(filepath.Dir(logFile)); err != nil {
|
||||
t.Fatalf("expected parent directory to be created: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestLoggerCore_SplitFileWritesBeforeWriterServiceStarts(t *testing.T) {
|
||||
if WriterService.Running.Load() {
|
||||
t.Skip("writer service already running")
|
||||
}
|
||||
|
||||
logFile := filepath.Join(t.TempDir(), "logs", "server.log")
|
||||
logger := NewLogger(Config{
|
||||
File: logFile,
|
||||
SplitTag: "20060102",
|
||||
})
|
||||
logger.Info("before writer service starts")
|
||||
logger.file.Run()
|
||||
|
||||
data, err := os.ReadFile(logFile + "." + time.Now().Format("20060102"))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !strings.Contains(string(data), "before writer service starts") {
|
||||
t.Fatalf("expected pre-start log to be written to split file, got %q", data)
|
||||
}
|
||||
}
|
||||
|
||||
func TestLoadDefaultConfig(t *testing.T) {
|
||||
oldConfig := defaultConfig
|
||||
oldWorkingDir, err := os.Getwd()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
t.Cleanup(func() {
|
||||
defaultConfig = oldConfig
|
||||
_ = os.Chdir(oldWorkingDir)
|
||||
})
|
||||
|
||||
tempDir := t.TempDir()
|
||||
if err := os.WriteFile(filepath.Join(tempDir, "log.yml"), []byte("level: warning\n"), 0644); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := os.Chdir(tempDir); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
defaultConfig = Config{
|
||||
Name: "built-in",
|
||||
Level: "debug",
|
||||
File: "logs/server.log",
|
||||
}
|
||||
conf := loadDefaultConfig()
|
||||
|
||||
if conf.Name != "built-in" {
|
||||
t.Fatalf("expected default name to be retained, got %q", conf.Name)
|
||||
}
|
||||
if conf.File != "logs/server.log" {
|
||||
t.Fatalf("expected default file to be retained, got %q", conf.File)
|
||||
}
|
||||
if conf.Level != "warning" {
|
||||
t.Fatalf("expected file config to override level, got %q", conf.Level)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSetDefaultConfig(t *testing.T) {
|
||||
oldConfig := defaultConfig
|
||||
oldLogger := DefaultLogger
|
||||
t.Cleanup(func() {
|
||||
defaultConfig = oldConfig
|
||||
DefaultLogger = oldLogger
|
||||
DefaultLogger.RedirectStdLog()
|
||||
})
|
||||
|
||||
SetDefaultConfig(Config{Name: "application-default", Level: "debug"})
|
||||
|
||||
if DefaultLogger.config.Name != "application-default" {
|
||||
t.Fatalf("expected rebuilt default logger name, got %q", DefaultLogger.config.Name)
|
||||
}
|
||||
if DefaultLogger.config.Level != "debug" {
|
||||
t.Fatalf("expected rebuilt default logger level, got %q", DefaultLogger.config.Level)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSetDefaultConfigKeepsLoggerWhenEffectiveConfigIsUnchanged(t *testing.T) {
|
||||
oldConfig := defaultConfig
|
||||
oldLogger := DefaultLogger
|
||||
t.Cleanup(func() {
|
||||
defaultConfig = oldConfig
|
||||
DefaultLogger = oldLogger
|
||||
DefaultLogger.RedirectStdLog()
|
||||
})
|
||||
|
||||
defaultConfig = Config{}
|
||||
DefaultLogger = NewLogger(Config{Name: "configured", Level: "info"})
|
||||
currentLogger := DefaultLogger
|
||||
|
||||
SetDefaultConfig(Config{Name: "configured"})
|
||||
|
||||
if DefaultLogger != currentLogger {
|
||||
t.Fatal("expected unchanged effective config to keep the existing logger")
|
||||
}
|
||||
}
|
||||
|
||||
func TestLoggerCore_Concurrency(t *testing.T) {
|
||||
conf := Config{
|
||||
Name: "concurrent-test",
|
||||
|
||||
@ -4,40 +4,18 @@ import (
|
||||
"apigo.cc/go/config"
|
||||
)
|
||||
|
||||
var (
|
||||
defaultConfig Config
|
||||
DefaultLogger *Logger
|
||||
)
|
||||
|
||||
func loadDefaultConfig() Config {
|
||||
conf := defaultConfig
|
||||
_ = config.Load(&conf, "log")
|
||||
return conf
|
||||
}
|
||||
var DefaultLogger *Logger
|
||||
|
||||
func init() {
|
||||
RegisterWriterMaker("es", newESWriter)
|
||||
RegisterWriterMaker("ess", newESWriter)
|
||||
|
||||
setDefaultLogger(loadDefaultConfig())
|
||||
}
|
||||
|
||||
func setDefaultLogger(conf Config) {
|
||||
var conf Config
|
||||
_ = config.Load(&conf, "log")
|
||||
DefaultLogger = NewLogger(conf)
|
||||
DefaultLogger.RedirectStdLog()
|
||||
}
|
||||
|
||||
// SetDefaultConfig sets application defaults, then applies file and environment
|
||||
// overrides before rebuilding DefaultLogger.
|
||||
func SetDefaultConfig(conf Config) {
|
||||
defaultConfig = conf
|
||||
finalConfig := normalizeConfig(loadDefaultConfig())
|
||||
if DefaultLogger != nil && DefaultLogger.config == finalConfig {
|
||||
return
|
||||
}
|
||||
setDefaultLogger(finalConfig)
|
||||
}
|
||||
|
||||
// New 创建带有 traceId 的 Logger 副本
|
||||
func New(traceId string) *Logger {
|
||||
return DefaultLogger.New(traceId)
|
||||
|
||||
16
go.mod
16
go.mod
@ -3,19 +3,19 @@ module apigo.cc/go/log
|
||||
go 1.25.0
|
||||
|
||||
require (
|
||||
apigo.cc/go/cast v1.5.5
|
||||
apigo.cc/go/config v1.5.4
|
||||
apigo.cc/go/file v1.5.6
|
||||
apigo.cc/go/id v1.5.7
|
||||
apigo.cc/go/shell v1.5.6
|
||||
apigo.cc/go/cast v1.5.3
|
||||
apigo.cc/go/config v1.5.3
|
||||
apigo.cc/go/file v1.5.5
|
||||
apigo.cc/go/id v1.5.4
|
||||
apigo.cc/go/shell v1.5.3
|
||||
)
|
||||
|
||||
require apigo.cc/go/jsmod v1.5.3 // indirect
|
||||
|
||||
require (
|
||||
apigo.cc/go/encoding v1.5.6 // indirect
|
||||
apigo.cc/go/rand v1.5.4 // indirect
|
||||
apigo.cc/go/safe v1.5.3 // indirect
|
||||
apigo.cc/go/encoding v1.5.4 // indirect
|
||||
apigo.cc/go/rand v1.5.3 // indirect
|
||||
apigo.cc/go/safe v1.5.2 // indirect
|
||||
golang.org/x/crypto v0.52.0 // indirect
|
||||
golang.org/x/sys v0.45.0 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
|
||||
32
go.sum
32
go.sum
@ -1,21 +1,21 @@
|
||||
apigo.cc/go/cast v1.5.5 h1:DMbfK3uPhPjRaXutj3StIZIkfjFIATSXfuAOeNOd4Fw=
|
||||
apigo.cc/go/cast v1.5.5/go.mod h1:GMjjrYn93tWat1U409G7h1jR3ejfLLI7r0efBo9Sbd4=
|
||||
apigo.cc/go/config v1.5.4 h1:1c/OarGwbz3+6ikurE+a6LJLjtzXvGvbxw3HV/Nk54M=
|
||||
apigo.cc/go/config v1.5.4/go.mod h1:oN+D2F8ETIyqKp+Yu8R4PRQlUoqR44o35jAHwLrrAq0=
|
||||
apigo.cc/go/encoding v1.5.6 h1:v02swVfbFGidD4QcX2ktuHHbCjdSbOB85fhzAXay+7M=
|
||||
apigo.cc/go/encoding v1.5.6/go.mod h1:Big9q1Zwy4071dXtnrQ3SJDzfa/G7/A60KE/5+M//P8=
|
||||
apigo.cc/go/file v1.5.6 h1:Y7w3Tyu4e16VuED7rF2pzba+dzGE+hjDnBlIPVHIfzA=
|
||||
apigo.cc/go/file v1.5.6/go.mod h1:9sdW4ylSOA0HWc8Yt8qdnmMf6nn5SUEmjoPKyXpYXIQ=
|
||||
apigo.cc/go/id v1.5.7 h1:Y5Sx6sQBCAdYMCQPTjODZyGMMd1+WRWCy2dWHVq11XQ=
|
||||
apigo.cc/go/id v1.5.7/go.mod h1:fugudFBqfVNakfm91zZzuzU0P4PULzo6sylB8hRMqxA=
|
||||
apigo.cc/go/cast v1.5.3 h1:jk6VX0rGFhjKtfPhsaV6IKYpiGmORRk9qPTtuNS53tw=
|
||||
apigo.cc/go/cast v1.5.3/go.mod h1:GMjjrYn93tWat1U409G7h1jR3ejfLLI7r0efBo9Sbd4=
|
||||
apigo.cc/go/config v1.5.3 h1:peq1FM2xO+vzPHJf8Dwg3DXm8PtFQMfTFKQj6fpoG7A=
|
||||
apigo.cc/go/config v1.5.3/go.mod h1:ZiOAjWa1mQIzszaJZN+kO6YU4GXreng+NxkcK/TAkqQ=
|
||||
apigo.cc/go/encoding v1.5.4 h1:Fk8TrveZATyy8SHukC4ZiqdTSp+QIfsRHtt55xmMK7w=
|
||||
apigo.cc/go/encoding v1.5.4/go.mod h1:dShEsZ3gKqBINz7TSOYf4e7/fBCqCY9VzlenoGUQUFM=
|
||||
apigo.cc/go/file v1.5.5 h1:/+HmDumLu6Qk2KuQL63M9lpgzHTDL+QJ8dStOl7e9gs=
|
||||
apigo.cc/go/file v1.5.5/go.mod h1:xRVNhctvqOKeBemmcRW/BQfgkc3B+vT/UZVdSc7duUo=
|
||||
apigo.cc/go/id v1.5.4 h1:D1Zx9gEZhOgdTgZ4SdmPImhpc9xGiOA33Y+j2MkstzQ=
|
||||
apigo.cc/go/id v1.5.4/go.mod h1:hCTQq+KC1ALWe1FpPERf+W4B6FSulg9FAgOUJDDySiY=
|
||||
apigo.cc/go/jsmod v1.5.3 h1:S3W317bH0QV2NMeRO1E0v6ySIBOfMWYv/NuQJbvqKWU=
|
||||
apigo.cc/go/jsmod v1.5.3/go.mod h1:bmyeZtOAP/j5am+YRnaiM89smysK24K7ebk0koFtsSw=
|
||||
apigo.cc/go/rand v1.5.4 h1:eessFBsKQuoOYdzrStldOGw9f4HqbeO87X95bI4jIBQ=
|
||||
apigo.cc/go/rand v1.5.4/go.mod h1:q1BTFkY/cXE229dDD5Q22lF7T0DoKPV6xAu+6bCrDH4=
|
||||
apigo.cc/go/safe v1.5.3 h1:9p/BmdlVWLbekpKByZIFC09Qn8Wdhik2eINiwunBxPs=
|
||||
apigo.cc/go/safe v1.5.3/go.mod h1:Ay8kEPL76DeXH4ifsVTc/3/sfGHlWLQjAp4vi7GA9AI=
|
||||
apigo.cc/go/shell v1.5.6 h1:2i93lNJ0oy/D5kOmlmOjwVjLPsW7vQBo0trLOF9AAAI=
|
||||
apigo.cc/go/shell v1.5.6/go.mod h1:Bp73DGKESOISWSIGUtL1dsFo5g4SI10GhgsnLJCTpsw=
|
||||
apigo.cc/go/rand v1.5.3 h1:O4bPIwyaOWEBCr0nL9A4G4qG48AqiGTCzfPeckm3Ius=
|
||||
apigo.cc/go/rand v1.5.3/go.mod h1:q1BTFkY/cXE229dDD5Q22lF7T0DoKPV6xAu+6bCrDH4=
|
||||
apigo.cc/go/safe v1.5.2 h1:EnuEOW/SGwf/5A0nw9LnqfKJE071+TIc6ez8HI9R9Lg=
|
||||
apigo.cc/go/safe v1.5.2/go.mod h1:2GqCCLLGex4OAhdET3iBWm1R+LIYtmTrvHP8W0iESSw=
|
||||
apigo.cc/go/shell v1.5.3 h1:pI+u12sy6upoygq+1XXqUlvUboBfH4Q52jRpoJFv56A=
|
||||
apigo.cc/go/shell v1.5.3/go.mod h1:FdZWUrcXHGJXo725oSyHqAeFoX0E9yY3PDhrz9hujgY=
|
||||
github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=
|
||||
github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk=
|
||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||
|
||||
18
logger.go
18
logger.go
@ -10,7 +10,6 @@ import (
|
||||
"time"
|
||||
|
||||
"apigo.cc/go/cast"
|
||||
"apigo.cc/go/file"
|
||||
"apigo.cc/go/id"
|
||||
)
|
||||
|
||||
@ -84,7 +83,7 @@ func RegisterWriterMaker(name string, f func(*Config) Writer) {
|
||||
writerMakers[name] = f
|
||||
}
|
||||
|
||||
func normalizeConfig(conf Config) Config {
|
||||
func NewLogger(conf Config) *Logger {
|
||||
if conf.Level == "" {
|
||||
conf.Level = "info"
|
||||
}
|
||||
@ -98,11 +97,6 @@ func normalizeConfig(conf Config) Config {
|
||||
if conf.Name == "" {
|
||||
conf.Name = getDefaultName()
|
||||
}
|
||||
return conf
|
||||
}
|
||||
|
||||
func NewLogger(conf Config) *Logger {
|
||||
conf = normalizeConfig(conf)
|
||||
|
||||
logger := Logger{
|
||||
truncations: cast.Split(conf.Truncations, ","),
|
||||
@ -144,7 +138,6 @@ func NewLogger(conf Config) *Logger {
|
||||
}
|
||||
}
|
||||
} else {
|
||||
file.EnsureParentDir(conf.File)
|
||||
if conf.SplitTag != "" {
|
||||
WriterService.FilesLock.RLock()
|
||||
logger.file = WriterService.Files[conf.File+conf.SplitTag]
|
||||
@ -194,14 +187,7 @@ func (logger *Logger) writeBuf(entry LogEntry, buf []byte) {
|
||||
if logger.writer != nil {
|
||||
logger.writer.Log(entry, buf)
|
||||
} else if logger.file != nil {
|
||||
WriterService.FilesLock.RLock()
|
||||
if WriterService.Running.Load() {
|
||||
WriterService.FilesLock.RUnlock()
|
||||
writeAsync(logPayload{entry: entry, buf: buf, file: logger.file})
|
||||
return
|
||||
}
|
||||
logger.file.Write(time.Now(), buf)
|
||||
WriterService.FilesLock.RUnlock()
|
||||
fmt.Println(Viewable(string(buf)))
|
||||
} else if logger.goLogger == nil {
|
||||
fmt.Println(Viewable(string(buf)))
|
||||
} else {
|
||||
|
||||
2
meta.go
2
meta.go
@ -228,7 +228,7 @@ func flattenStructFields(t reflect.Type, result *[]fieldInfo, parentIndex []int)
|
||||
func syncMetaFile() {
|
||||
metaLock.RLock()
|
||||
defer metaLock.RUnlock()
|
||||
_ = file.PatchFile(metaFilePath, metaRegistry)
|
||||
_ = file.MarshalFilePretty(metaFilePath, metaRegistry)
|
||||
}
|
||||
|
||||
// SetMetaFilePath allows changing the path for testing or configuration purposes
|
||||
|
||||
38
meta_test.go
38
meta_test.go
@ -47,12 +47,8 @@ func (l *MockErrorLog) Reset() {
|
||||
|
||||
func TestMetaExtraction(t *testing.T) {
|
||||
// Setup custom meta file path for testing
|
||||
oldMetaFilePath := metaFilePath
|
||||
SetMetaFilePath(".test.meta.json")
|
||||
t.Cleanup(func() {
|
||||
SetMetaFilePath(oldMetaFilePath)
|
||||
_ = os.Remove(".test.meta.json")
|
||||
})
|
||||
defer os.Remove(".test.meta.json")
|
||||
|
||||
RegisterType("mock_info", MockInfoLog{})
|
||||
RegisterType("mock_error", MockErrorLog{})
|
||||
@ -107,35 +103,3 @@ func TestMetaExtraction(t *testing.T) {
|
||||
t.Errorf("expected at least 2 types in registry, got %d", len(registry))
|
||||
}
|
||||
}
|
||||
|
||||
func TestSyncMetaFilePreservesExistingTypes(t *testing.T) {
|
||||
oldMetaFilePath := metaFilePath
|
||||
metaPath := ".test.merge.meta.json"
|
||||
SetMetaFilePath(metaPath)
|
||||
t.Cleanup(func() {
|
||||
SetMetaFilePath(oldMetaFilePath)
|
||||
_ = os.Remove(metaPath)
|
||||
})
|
||||
|
||||
existing := `{"api_call":[{"Index":0,"Name":"Action","Color":"cyan"}]}`
|
||||
if err := os.WriteFile(metaPath, []byte(existing), 0644); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
RegisterType("mock_merge", MockInfoLog{})
|
||||
|
||||
data, err := os.ReadFile(metaPath)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
var registry map[string][]MetaField
|
||||
if err := json.Unmarshal(data, ®istry); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(registry["api_call"]) != 1 || registry["api_call"][0].Name != "Action" {
|
||||
t.Fatalf("existing business metadata was not preserved: %+v", registry["api_call"])
|
||||
}
|
||||
if len(registry["mock_merge"]) == 0 {
|
||||
t.Fatal("newly registered metadata was not written")
|
||||
}
|
||||
}
|
||||
|
||||
15
writer.go
15
writer.go
@ -37,18 +37,9 @@ type writerService struct {
|
||||
|
||||
var (
|
||||
// WriterService is the global instance of defaultService.
|
||||
WriterService = newWriterService()
|
||||
WriterService = &writerService{}
|
||||
)
|
||||
|
||||
func newWriterService() *writerService {
|
||||
service := &writerService{
|
||||
LogChannel: make(chan logPayload, 10000),
|
||||
Files: make(map[string]*FileWriter),
|
||||
}
|
||||
service.Writers.Store([]Writer{})
|
||||
return service
|
||||
}
|
||||
|
||||
// ConsoleWriter 控制台写入器
|
||||
type ConsoleWriter struct {
|
||||
}
|
||||
@ -61,6 +52,10 @@ func (w *ConsoleWriter) Run() {
|
||||
}
|
||||
|
||||
func init() {
|
||||
WriterService.LogChannel = make(chan logPayload, 10000)
|
||||
WriterService.Writers.Store([]Writer{})
|
||||
WriterService.Files = make(map[string]*FileWriter)
|
||||
|
||||
RegisterWriterMaker("console", func(conf *Config) Writer {
|
||||
return &ConsoleWriter{}
|
||||
})
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user