diff --git a/CHANGELOG.md b/CHANGELOG.md index 27996d6..84dfbc0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ # Changelog +## [1.1.10] - 2026-05-05 +- **稳定性增强**: + - 修复 `TestSplitTag` 在秒级进位边界时的偶发性失败。优化后的测试逻辑同时校验日志产生前后的两个潜在时间槽,闭环消除了环境抖动导致的 race condition。 + ## [1.1.9] - 2026-05-05 - **稳定性增强**: - 改进 `SplitTag` 测试用例,通过秒级切分步进,闭环验证了 `FileWriter` 的文件自动轮转逻辑,确保在高频或定时切分场景下的可靠性。 diff --git a/TEST.md b/TEST.md index 8eaee69..e613632 100644 --- a/TEST.md +++ b/TEST.md @@ -5,13 +5,13 @@ - 架构: amd64 - CPU: Intel(R) Core(TM) i9-9980HK CPU @ 2.40GHz -## 基准测试结果 (v1.1.8) +## 基准测试结果 (v1.1.10) | 测试用例 | 迭代次数 | 耗时 (ns/op) | 内存分配 (B/op) | 分配次数 (allocs/op) | | :--- | :--- | :--- | :--- | :--- | -| `BenchmarkLogger_RequestLog_Realistic` | 540,817 | 2,256 | 561 | 17 | -| `BenchmarkLoggerInfo` | 341,980 | 3,164 | - | - | -| `BenchmarkLoggerAsyncConcurrent` | 1,289,307 | 914 | - | - | +| `BenchmarkLogger_RequestLog_Realistic` | 544,791 | 2,230 | 561 | 17 | +| `BenchmarkLoggerInfo` | 368,821 | 3,042 | - | - | +| `BenchmarkLoggerAsyncConcurrent` | 1,216,018 | 919 | - | - | ## 版本对比评估 @@ -21,10 +21,11 @@ | **v1.1.4** | Meta-Driven Array | JSON Array | 独立工具/Meta | ~7,080 ns/op | | **v1.1.6** | BaseLog Pointer Opt | JSON Array | 独立工具/Meta | ~7,445 ns/op | | **v1.1.7** | Dead Code Removal | JSON Array | 独立工具/Meta | ~1,059 ns/op | -| **v1.1.8** | **Hybrid Deep Masking** | **JSON Array** | 独立工具/Meta | **~914 ns/op** | +| **v1.1.8** | Hybrid Deep Masking | JSON Array | 独立工具/Meta | ~914 ns/op | +| **v1.1.10** | **Stability & Infrastructure** | **JSON Array** | 独立工具/Meta | **~919 ns/op** | ## 总结 -- **性能质变**: v1.1.8 通过 **混合序列化路径**,在实现深度脱敏的同时,将异步并发性能提升到了巅峰(**914ns**),真正做到了安全与性能兼得。 -- **深度安全**: 借助 `cast.ToJSONDesensitizeBytes`,现在可以自动穿透并屏蔽嵌套在 Map、Struct 或 Slice 内部的任何敏感信息。 -- **摩擦消除**: 统一了基础类型与复杂类型的处理逻辑,代码更易维护。 +- **稳定性增强**: v1.1.10 修复了 `SplitTag` 轮转测试在秒级边界时的 race condition,测试用例更加健壮。 +- **基础设施对齐**: 核心元数据解析与测试用例全面对齐 `go/cast` 与 `go/file` 基础设施,消除了原生 `strconv` 与 `os` 操作的摩擦。 +- **性能维持**: 在增强稳定性的同时,异步并发性能稳定维持在 **~919ns**,保持了极高的吞吐能力。 diff --git a/functional_test.go b/functional_test.go index 2eff62e..9a1b8a5 100644 --- a/functional_test.go +++ b/functional_test.go @@ -1,11 +1,11 @@ package log_test import ( - "os" "strings" "testing" "time" + "apigo.cc/go/file" "apigo.cc/go/log" ) @@ -23,33 +23,42 @@ func TestSplitTag(t *testing.T) { logger := log.NewLogger(conf) // 1. 记录第一条日志 + t1 := time.Now() logger.Info("first message") time.Sleep(300 * time.Millisecond) // 等待异步写入 - expectedFile1 := logFile + "." + time.Now().Format(splitTag) - if _, err := os.Stat(expectedFile1); os.IsNotExist(err) { - t.Fatalf("First log file %s does not exist", expectedFile1) + expectedFile1 := logFile + "." + t1.Format(splitTag) + if !file.Exists(expectedFile1) { + // 可能在写入时秒数刚好进位 + expectedFile1 = logFile + "." + time.Now().Format(splitTag) + if !file.Exists(expectedFile1) { + t.Fatalf("First log file does not exist (checked both possible time slots)") + } } - // 2. 等待跨秒 - time.Sleep(1100 * time.Millisecond) + // 2. 等待跨秒,确保下次写入肯定会触发轮转 + time.Sleep(1200 * time.Millisecond) // 3. 记录第二条日志,触发轮转 + t2 := time.Now() logger.Info("second message") time.Sleep(300 * time.Millisecond) // 等待异步写入 - expectedFile2 := logFile + "." + time.Now().Format(splitTag) + + expectedFile2 := logFile + "." + t2.Format(splitTag) + if !file.Exists(expectedFile2) { + expectedFile2 = logFile + "." + time.Now().Format(splitTag) + if !file.Exists(expectedFile2) { + t.Fatalf("Second log file does not exist after rotation") + } + } if expectedFile1 == expectedFile2 { t.Errorf("Files should be different for rotation, but both are %s", expectedFile1) } - if _, err := os.Stat(expectedFile2); os.IsNotExist(err) { - t.Errorf("Second log file %s does not exist after rotation", expectedFile2) - } - // 清理 - os.Remove(expectedFile1) - os.Remove(expectedFile2) + file.Remove(expectedFile1) + file.Remove(expectedFile2) } func TestSensitiveDetailed(t *testing.T) { diff --git a/meta.go b/meta.go index dd949ff..a4a13d3 100644 --- a/meta.go +++ b/meta.go @@ -3,10 +3,10 @@ package log import ( "reflect" "sort" - "strconv" "strings" "sync" + "apigo.cc/go/cast" "apigo.cc/go/file" ) @@ -163,7 +163,7 @@ func flattenStructFields(t reflect.Type, result *[]reflect.StructField, parentIn for _, part := range parts { kv := strings.SplitN(part, ":", 2) if len(kv) == 2 && strings.TrimSpace(kv[0]) == "pos" { - if p, err := strconv.Atoi(strings.TrimSpace(kv[1])); err == nil { + if p := cast.To[int](strings.TrimSpace(kv[1])); p > 0 { pos = p } }