简化截断逻辑

This commit is contained in:
ideaa
2025-09-04 14:20:51 +08:00
parent a56d4ad5b7
commit 97d3ca76d0
2 changed files with 232 additions and 51 deletions

View File

@@ -7,7 +7,6 @@ import (
"regexp" "regexp"
"strconv" "strconv"
"strings" "strings"
"unicode/utf8"
"github.com/fatih/color" "github.com/fatih/color"
"github.com/wonli/aqi/internal/config" "github.com/wonli/aqi/internal/config"
@@ -141,15 +140,11 @@ func getFileStyleEncoder(c config.Logger) zapcore.Encoder {
patternStr := fmt.Sprintf(`"action"\s*:\s*"%s"`, regexp.QuoteMeta(actionPattern)) patternStr := fmt.Sprintf(`"action"\s*:\s*"%s"`, regexp.QuoteMeta(actionPattern))
pattern := regexp.MustCompile(patternStr) pattern := regexp.MustCompile(patternStr)
fieldPattern := fmt.Sprintf(`("%s":\s*)(.*)`, regexp.QuoteMeta(fieldPath))
fieldRegex := regexp.MustCompile(fieldPattern)
filters = append(filters, FilterRule{ filters = append(filters, FilterRule{
Action: actionPattern, Action: actionPattern,
Field: fieldPath, Field: fieldPath,
MaxLen: maxLen, MaxLen: maxLen,
Pattern: pattern, Pattern: pattern,
FieldRegex: fieldRegex,
}) })
} }
} }
@@ -162,11 +157,10 @@ func getFileStyleEncoder(c config.Logger) zapcore.Encoder {
} }
type FilterRule struct { type FilterRule struct {
Action string Action string
Field string Field string
MaxLen int MaxLen int
Pattern *regexp.Regexp // 匹配action的正则 Pattern *regexp.Regexp // 匹配action的正则
FieldRegex *regexp.Regexp // 匹配字段的正则
} }
// fileStyleEncoder 自定义编码风格输出 // fileStyleEncoder 自定义编码风格输出
@@ -257,23 +251,19 @@ func (e *fileStyleEncoder) EncodeEntry(entry zapcore.Entry, fields []zapcore.Fie
func (e *fileStyleEncoder) processMessage(msg string, filters []FilterRule) string { func (e *fileStyleEncoder) processMessage(msg string, filters []FilterRule) string {
for _, rule := range filters { for _, rule := range filters {
if strings.Contains(msg, rule.Action) && rule.Pattern.MatchString(msg) { if strings.Contains(msg, rule.Action) && rule.Pattern.MatchString(msg) {
msg = rule.FieldRegex.ReplaceAllStringFunc(msg, func(match string) string { // 查找字段位置
parts := rule.FieldRegex.FindStringSubmatch(match) fieldKey := fmt.Sprintf(`"%s":`, rule.Field)
if len(parts) >= 3 { pos := strings.Index(msg, fieldKey)
value := parts[2] if pos != -1 {
if len(value) > rule.MaxLen { startPos := pos + len(fieldKey)
prefix := parts[1] if len(msg[startPos:]) > rule.MaxLen {
// 确保不会从中文字符中间截断 r := []rune(msg[startPos:])
safeIndex := rule.MaxLen if rule.MaxLen < len(r) {
for safeIndex > 0 && !utf8.RuneStart(value[safeIndex]) { truncated := string(r[:rule.MaxLen]) + "..."
safeIndex-- msg = msg[:startPos] + truncated
}
return prefix + value[:safeIndex] + "..."
} }
return match
} }
return match }
})
} }
} }
return msg return msg

View File

@@ -10,15 +10,215 @@ import (
) )
func TestZap(t *testing.T) { func TestZap(t *testing.T) {
testCases := []struct {
name string
logFilters map[string]string
messages []string
description string
}{
{
name: "基础截断测试",
logFilters: map[string]string{
"article.list": "data:116",
"res.uploadImg": "params:64",
},
messages: []string{
`{"action":"res.uploadImg","params":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAYoAAAGICAYAAABftLn"}`,
`{"code":0,"action":"article.list","id":"92","data":[{"id":83,"status":1,"parentId":0,"position":"ai-diagnosis","icon":"https://cheguanjia-test.oss-cn-chengdu.aliyuncs.com/files/2011/cce5e12eb9e403b.png?rid=4384\u0026mime_type=image/png","searchIcon":"","tagType":2,"orderSort":5,"name":"AI诊断","questions":{"data":[],"rule":"random","random":5},"cards":{"steps":[]},"notes":"","createdTime":"2025-07-11T11:49:24+08:00","updatedTime":"2025-07-11T13:50:04+08:00"}]}`,
},
description: "测试基本的字符串和数组截断功能",
},
{
name: "中文字符截断测试",
logFilters: map[string]string{
"user.info": "message:20",
},
messages: []string{
`{"action":"user.info","message":"这是一个包含中文字符的很长的消息内容,用来测试中文字符的正确截断功能,确保不会从中文字符中间截断"}`,
`{"action":"user.info","message":"Hello World! 这里混合了英文和中文字符,测试混合字符的截断效果"}`,
},
description: "测试中文字符安全截断,确保不会从中文字符中间截断",
},
{
name: "边界条件测试",
logFilters: map[string]string{
"test.boundary": "content:0", // 长度为0
"test.small": "content:5", // 很小的长度
"test.exact": "content:10", // 刚好等于内容长度
},
messages: []string{
`{"action":"test.boundary","content":"任何内容都会被截断"}`,
`{"action":"test.small","content":"短内容测试"}`,
`{"action":"test.exact","content":"1234567890"}`, // 刚好10个字符
`{"action":"test.exact","content":"12345678901"}`, // 11个字符应该被截断
},
description: "测试边界条件长度为0、很小长度、刚好等于内容长度等情况",
},
{
name: "特殊字符测试",
logFilters: map[string]string{
"special.chars": "data:30",
},
messages: []string{
`{"action":"special.chars","data":"包含特殊字符: \"引号\" \n换行 \t制表符 \\反斜杠 和其他特殊符号 @#$%^&*()"}`,
`{"action":"special.chars","data":"JSON转义字符测试: \u0026 \u003c \u003e 以及 emoji 😀🎉🚀"}`,
`{"action":"special.chars","data":"URL编码字符: %20 %3A %2F %3F 和HTML实体: &amp; &lt; &gt;"}`,
},
description: "测试包含特殊字符、转义字符、emoji等的内容截断",
},
{
name: "复杂JSON结构测试",
logFilters: map[string]string{
"complex.nested": "payload:50",
"complex.array": "items:40",
},
messages: []string{
`{"action":"complex.nested","payload":{"user":{"id":123,"name":"张三","profile":{"age":25,"city":"北京","hobbies":["读书","游泳","编程"]}},"timestamp":"2024-01-01T00:00:00Z"}}`,
`{"action":"complex.array","items":[{"id":1,"name":"项目A","tags":["重要","紧急"]},{"id":2,"name":"项目B","tags":["普通"]},{"id":3,"name":"项目C","tags":["低优先级"]}]}`,
},
description: "测试复杂嵌套JSON结构的截断处理",
},
{
name: "无匹配规则测试",
logFilters: map[string]string{
"match.rule": "content:20",
},
messages: []string{
`{"action":"no.match","content":"这条消息不应该被截断因为action不匹配任何规则"}`,
`{"action":"match.rule","other_field":"这个字段不会被截断,因为字段名不匹配"}`,
`{"different_action":"match.rule","content":"action字段名不同不会匹配规则"}`,
},
description: "测试不匹配过滤规则的消息应该保持原样",
},
{
name: "空值和null测试",
logFilters: map[string]string{
"empty.test": "value:10",
},
messages: []string{
`{"action":"empty.test","value":""}`,
`{"action":"empty.test","value":null}`,
`{"action":"empty.test","value":" "}`, // 只有空格
`{"action":"empty.test"}`, // 缺少字段
},
description: "测试空字符串、null值、纯空格和缺失字段的处理",
},
{
name: "多字段规则测试",
logFilters: map[string]string{
"multi.field1": "title:15",
"multi.field2": "description:25",
},
messages: []string{
`{"action":"multi.field1","title":"这是一个很长的标题内容需要被截断","description":"这个描述不会被截断因为规则不匹配"}`,
`{"action":"multi.field2","title":"这个标题不会被截断","description":"这是一个更长的描述内容,包含更多的详细信息和说明文字,应该被截断"}`,
},
description: "测试不同action对应不同字段的截断规则",
},
{
name: "性能压力测试",
logFilters: map[string]string{
"perf.test": "large_data:100",
},
messages: []string{
`{"action":"perf.test","large_data":"` + strings.Repeat("这是重复的内容用于测试大数据量的截断性能。", 100) + `"}`,
},
description: "测试大数据量的截断性能",
},
{
name: "格式异常测试",
logFilters: map[string]string{
"format.test": "data:20",
},
messages: []string{
`{"action":"format.test","data":"正常数据"}`, // 正常JSON
`{"action":"format.test","data":"包含\"引号\"的数据"}`, // 包含转义引号
`{"action":"format.test","data":"包含,逗号的数据"}`, // 包含逗号
`{"action":"format.test","data":"包含}花括号的数据"}`, // 包含花括号
`{"action":"format.test","data":"包含]方括号的数据"}`, // 包含方括号
},
description: "测试包含JSON特殊字符的数据处理",
},
{
name: "负数和零长度测试",
logFilters: map[string]string{
"negative.test": "data:-5", // 负数长度
"zero.test": "data:0", // 零长度
},
messages: []string{
`{"action":"negative.test","data":"负数长度测试数据"}`,
`{"action":"zero.test","data":"零长度测试数据"}`,
},
description: "测试负数和零长度的边界情况处理",
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
log.Printf("\n=== %s ===\n%s\n", tc.name, tc.description)
// 构建过滤规则
filters := make([]FilterRule, 0, len(tc.logFilters))
for actionPattern, fieldRule := range tc.logFilters {
// 解析字段路径和长度格式field.path:maxLen
ruleParts := strings.SplitN(fieldRule, ":", 2)
if len(ruleParts) != 2 {
continue
}
fieldPath := strings.TrimSpace(ruleParts[0])
maxLen, _ := strconv.Atoi(strings.TrimSpace(ruleParts[1]))
if maxLen < 0 {
maxLen = 0
}
patternStr := fmt.Sprintf(`"action":"%s"`, regexp.QuoteMeta(actionPattern))
pattern := regexp.MustCompile(patternStr)
filters = append(filters, FilterRule{
Action: actionPattern,
Field: fieldPath,
MaxLen: maxLen,
Pattern: pattern,
})
}
fs := fileStyleEncoder{}
// 处理每条消息
for i, msg := range tc.messages {
originalLen := len(msg)
processedMsg := fs.processMessage(msg, filters)
processedLen := len(processedMsg)
log.Printf("消息 %d (原长度: %d, 处理后长度: %d):\n", i+1, originalLen, processedLen)
log.Printf("处理后: %s\n", processedMsg)
// 验证截断是否正确
if processedLen < originalLen {
log.Printf("✓ 消息被成功截断\n")
} else if processedLen == originalLen {
log.Printf("- 消息长度未变化(可能未匹配规则或无需截断)\n")
}
log.Println()
}
})
}
}
// 辅助函数:生成大量测试数据
func generateLargeTestData(size int) string {
return strings.Repeat("测试数据", size)
}
// 基准测试:测试截断性能
func BenchmarkProcessMessage(b *testing.B) {
logFilters := map[string]string{ logFilters := map[string]string{
"article.list": "data:16", "perf.test": "data:100",
"res.uploadImg": "params:64",
} }
filters := make([]FilterRule, 0, len(logFilters)) filters := make([]FilterRule, 0, len(logFilters))
for actionPattern, fieldRule := range logFilters { for actionPattern, fieldRule := range logFilters {
// 解析字段路径和长度格式field.path:maxLen
ruleParts := strings.SplitN(fieldRule, ":", 2) ruleParts := strings.SplitN(fieldRule, ":", 2)
if len(ruleParts) != 2 { if len(ruleParts) != 2 {
continue continue
@@ -26,35 +226,26 @@ func TestZap(t *testing.T) {
fieldPath := strings.TrimSpace(ruleParts[0]) fieldPath := strings.TrimSpace(ruleParts[0])
maxLen, _ := strconv.Atoi(strings.TrimSpace(ruleParts[1])) maxLen, _ := strconv.Atoi(strings.TrimSpace(ruleParts[1]))
if maxLen <= 0 { if maxLen < 0 {
maxLen = 0 maxLen = 0
} }
patternStr := fmt.Sprintf(`"action":"%s"`, regexp.QuoteMeta(actionPattern)) patternStr := fmt.Sprintf(`"action":"%s"`, regexp.QuoteMeta(actionPattern))
pattern := regexp.MustCompile(patternStr) pattern := regexp.MustCompile(patternStr)
fieldPattern := fmt.Sprintf(`("%s":\s*)(.*)`, regexp.QuoteMeta(fieldPath))
fieldRegex := regexp.MustCompile(fieldPattern)
filters = append(filters, FilterRule{ filters = append(filters, FilterRule{
Action: actionPattern, Action: actionPattern,
Field: fieldPath, Field: fieldPath,
MaxLen: maxLen, MaxLen: maxLen,
Pattern: pattern, Pattern: pattern,
FieldRegex: fieldRegex,
}) })
} }
// 测试消息
msg1 := `{"action":"res.uploadImg","params":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAYoAAAGICAYAAABftLn"}`
msg2 := `{"code":0,"action":"article.list","data":{"page":{"current":1,"pageSize":10,"total":5}}}`
fs := fileStyleEncoder{} fs := fileStyleEncoder{}
testMsg := `{"action":"perf.test","data":"` + generateLargeTestData(1000) + `"}`
// 处理消息 b.ResetTimer()
processedMsg1 := fs.processMessage(msg1, filters) for i := 0; i < b.N; i++ {
log.Println("处理后的消息1:", processedMsg1) _ = fs.processMessage(testMsg, filters)
}
processedMsg2 := fs.processMessage(msg2, filters)
log.Println("处理后的消息2:", processedMsg2)
} }