稳定性增强:修复 TestSplitTag 秒级边界竞态,并完成 go/cast 与 go/file 基础设施对齐(by AI)

This commit is contained in:
AI Engineer 2026-05-05 23:47:07 +08:00
parent 9c6112d253
commit 78d6addf4c
4 changed files with 37 additions and 23 deletions

View File

@ -1,5 +1,9 @@
# Changelog
## [1.1.10] - 2026-05-05
- **稳定性增强**:
- 修复 `TestSplitTag` 在秒级进位边界时的偶发性失败。优化后的测试逻辑同时校验日志产生前后的两个潜在时间槽,闭环消除了环境抖动导致的 race condition。
## [1.1.9] - 2026-05-05
- **稳定性增强**:
- 改进 `SplitTag` 测试用例,通过秒级切分步进,闭环验证了 `FileWriter` 的文件自动轮转逻辑,确保在高频或定时切分场景下的可靠性。

17
TEST.md
View File

@ -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**,保持了极高的吞吐能力

View File

@ -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) {

View File

@ -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
}
}