feat: add log.As and logger.As for frictionless error handling

This commit is contained in:
AI Engineer 2026-05-13 01:07:42 +08:00
parent a2b1055f5d
commit 1f816dc8b3
7 changed files with 61 additions and 4 deletions

View File

@ -1,5 +1,11 @@
# Changelog # 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 ## [1.3.1] - 2026-05-12
- **架构升级: 引入 LoggerService**: - **架构升级: 引入 LoggerService**:
- **解耦重构**: 重构全局变量管理,引入 `loggerService` 结构体集中化管理异步写入协程、Writers 对象池、文件句柄与丢弃计数。 - **解耦重构**: 重构全局变量管理,引入 `loggerService` 结构体集中化管理异步写入协程、Writers 对象池、文件句柄与丢弃计数。

View File

@ -89,10 +89,14 @@ export LOG_FILE=console
1. **分级记录** 1. **分级记录**
* `Debug`, `Info`, `Warning`, `Error` —— 标准日志方法,支持 `message` + 变长 `extra` 参数。 * `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)` —— 记录自定义结构的日志。 * `Log(LogEntry)` —— 记录自定义结构的日志。
3. **独立可视化工具 (`logv`)** 4. **独立可视化工具 (`logv`)**
* **安装**: `go install apigo.cc/go/log/logv@latest` * **安装**: `go install apigo.cc/go/log/logv@latest`
* **使用**: `tail -f app.log | logv``tail -f app.log | logv -json` * **使用**: `tail -f app.log | logv``tail -f app.log | logv -json`

View File

@ -30,3 +30,15 @@ func SetDefaultName(name string) {
DefaultLogger.SetName(name) 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
}

View File

@ -1,6 +1,7 @@
package log_test package log_test
import ( import (
"strconv"
"testing" "testing"
"apigo.cc/go/log" "apigo.cc/go/log"
@ -86,3 +87,28 @@ func TestExtraLogs(t *testing.T) {
logger.Info("Extra log test", "key", "value") 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)
}
}

View File

@ -254,6 +254,14 @@ func (logger *Logger) GetTraceId() string {
return logger.traceId 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 { func (logger *Logger) CheckLevel(logLevel LevelType) bool {
settedLevel := logger.level settedLevel := logger.level
if settedLevel == 0 { if settedLevel == 0 {

View File

@ -69,6 +69,7 @@ func getCallStacks(truncations []string) []string {
isLogInternal := (strings.Contains(file, "/log/logger.go") || isLogInternal := (strings.Contains(file, "/log/logger.go") ||
strings.Contains(file, "/log/utility.go") || strings.Contains(file, "/log/utility.go") ||
strings.Contains(file, "/log/standard.go") || strings.Contains(file, "/log/standard.go") ||
strings.Contains(file, "/log/default_logger.go") ||
strings.Contains(file, "/log/extra.go")) strings.Contains(file, "/log/extra.go"))
if isLogInternal { if isLogInternal {

View File

@ -181,12 +181,12 @@ func TestCallStacksViewable(t *testing.T) {
wd, _ := os.Getwd() wd, _ := os.Getwd()
entry := &CallStackLog{ entry := &CallStackLog{
BaseLog: log.BaseLog{ BaseLog: log.BaseLog{
LogType: "error", LogType: "test_error",
LogTime: 1714896000000000000, LogTime: 1714896000000000000,
}, },
CallStacks: []string{wd + "/main.go:10", "/usr/local/go/src/runtime/panic.go:100"}, 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)) line := string(log.Marshal(entry, nil))
out := log.Viewable(line) out := log.Viewable(line)