diff --git a/CHANGELOG.md b/CHANGELOG.md index 19657d7..2ff274e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## [1.3.2] - 2026-05-13 +- **功能增强: 引入摩擦消除工具 `As`**: + - **泛型支持**: 新增全局泛型函数 `log.As[T](v T, err error) T`,仿照 `cast.As` 设计,自动记录错误并返回零值,极大简化了带 error 返回值的函数链式调用。 + - **Logger 扩展**: `Logger` 结构体新增 `As(v any, err error) any` 方法,支持实例级别的错误捕获与自动记录。 + - **调用栈优化**: 优化了 `GetCallStacks` 逻辑,自动跳过 `default_logger.go` 中的内部帧,确保 `log.As` 记录的错误位置精准指向业务代码。 + ## [1.3.1] - 2026-05-12 - **架构升级: 引入 LoggerService**: - **解耦重构**: 重构全局变量管理,引入 `loggerService` 结构体,集中化管理异步写入协程、Writers 对象池、文件句柄与丢弃计数。 diff --git a/README.md b/README.md index 4745915..f4b4db2 100644 --- a/README.md +++ b/README.md @@ -89,10 +89,14 @@ export LOG_FILE=console 1. **分级记录** * `Debug`, `Info`, `Warning`, `Error` —— 标准日志方法,支持 `message` + 变长 `extra` 参数。 -2. **通用记录 (`Log`)** +2. **摩擦消除 (`As`)** + * `As(v, err)` —— 仿照 `cast.As`,忽略错误并返回零值,但会自动将错误记录到日志中。支持全局调用 (`log.As`) 或实例调用 (`logger.As`)。 + * **优势**: 在类型转换或快速赋值场景下,无需繁琐的 `if err != nil` 判断,同时确保异常被记录。 + +3. **通用记录 (`Log`)** * `Log(LogEntry)` —— 记录自定义结构的日志。 -3. **独立可视化工具 (`logv`)** +4. **独立可视化工具 (`logv`)** * **安装**: `go install apigo.cc/go/log/logv@latest` * **使用**: `tail -f app.log | logv` 或 `tail -f app.log | logv -json`。 diff --git a/default_logger.go b/default_logger.go index 50ba33a..5232e8a 100644 --- a/default_logger.go +++ b/default_logger.go @@ -30,3 +30,15 @@ func SetDefaultName(name string) { DefaultLogger.SetName(name) } } + +// As 仿照 cast.As,使用 DefaultLogger 记录错误并返回零值 (消除摩擦) +func As[T any](v T, err error) T { + if err != nil { + if DefaultLogger != nil { + DefaultLogger.Error(err.Error()) + } + var zero T + return zero + } + return v +} diff --git a/log_test.go b/log_test.go index 0cd62f6..899aecc 100644 --- a/log_test.go +++ b/log_test.go @@ -1,6 +1,7 @@ package log_test import ( + "strconv" "testing" "apigo.cc/go/log" @@ -86,3 +87,28 @@ func TestExtraLogs(t *testing.T) { logger.Info("Extra log test", "key", "value") } + +func TestAs(t *testing.T) { + // 1. 测试 log.As (使用 DefaultLogger) + val1 := log.As(strconv.Atoi("123")) + if val1 != 123 { + t.Errorf("log.As expected 123, got %v", val1) + } + + val2 := log.As(strconv.Atoi("abc")) + if val2 != 0 { + t.Errorf("log.As expected 0, got %v", val2) + } + + // 2. 测试 logger.As (方法) + logger := log.NewLogger(log.Config{Level: "debug"}) + val3 := logger.As(strconv.Atoi("456")).(int) + if val3 != 456 { + t.Errorf("logger.As expected 456, got %v", val3) + } + + val4 := logger.As(strconv.Atoi("def")).(int) + if val4 != 0 { + t.Errorf("logger.As expected 0, got %v", val4) + } +} diff --git a/logger.go b/logger.go index 0bf7645..0cc75b9 100644 --- a/logger.go +++ b/logger.go @@ -254,6 +254,14 @@ func (logger *Logger) GetTraceId() string { return logger.traceId } +// As 仿照 cast.As,忽略错误并返回零值,但会将错误记录到日志中 (消除摩擦) +func (logger *Logger) As(v any, err error) any { + if err != nil { + logger.Error(err.Error()) + } + return v +} + func (logger *Logger) CheckLevel(logLevel LevelType) bool { settedLevel := logger.level if settedLevel == 0 { diff --git a/utility.go b/utility.go index 5797732..4aaee9e 100644 --- a/utility.go +++ b/utility.go @@ -69,6 +69,7 @@ func getCallStacks(truncations []string) []string { isLogInternal := (strings.Contains(file, "/log/logger.go") || strings.Contains(file, "/log/utility.go") || strings.Contains(file, "/log/standard.go") || + strings.Contains(file, "/log/default_logger.go") || strings.Contains(file, "/log/extra.go")) if isLogInternal { diff --git a/viewer_test.go b/viewer_test.go index 3613af3..6c18bbd 100644 --- a/viewer_test.go +++ b/viewer_test.go @@ -181,12 +181,12 @@ func TestCallStacksViewable(t *testing.T) { wd, _ := os.Getwd() entry := &CallStackLog{ BaseLog: log.BaseLog{ - LogType: "error", + LogType: "test_error", LogTime: 1714896000000000000, }, CallStacks: []string{wd + "/main.go:10", "/usr/local/go/src/runtime/panic.go:100"}, } - log.RegisterType("error", entry) + log.RegisterType("test_error", entry) line := string(log.Marshal(entry, nil)) out := log.Viewable(line)