日志过滤

This commit is contained in:
ideaa
2025-05-16 16:50:25 +08:00
parent 4cca2a6d6d
commit 8d75fd8901
4 changed files with 155 additions and 19 deletions

2
app.go
View File

@@ -181,7 +181,7 @@ func Init(options ...Option) *AppConfig {
var c config.Logger
err = viper.UnmarshalKey(acf.LogPathKey, &c)
if err != nil {
color.Red("failed to init app log")
color.Red("failed to init app log: " + err.Error())
os.Exit(1)
}

View File

@@ -10,12 +10,13 @@ type Logger struct {
LogFile string
RuntimeLogFile string
LogPath string `yaml:"logPath"` // Path of the log file
MaxSize int `yaml:"maxSize"` // Maximum log file size in MB
MaxBackups int `yaml:"maxBackups"` // Maximum number of log file backups
MaxAge int `yaml:"maxAge"` // Maximum number of days to retain log files
Compress bool `yaml:"compress"` // Whether to enable gzip compression
UseCaller bool `yaml:"useCaller"` // Whether to enable Zap Caller
LogPath string `yaml:"logPath"` // Path of the log file
LogFilter map[string]string `yaml:"logFilter"` // Filter
MaxSize int `yaml:"maxSize"` // Maximum log file size in MB
MaxBackups int `yaml:"maxBackups"` // Maximum number of log file backups
MaxAge int `yaml:"maxAge"` // Maximum number of days to retain log files
Compress bool `yaml:"compress"` // Whether to enable gzip compression
UseCaller bool `yaml:"useCaller"` // Whether to enable Zap Caller
}
// GetEncoder 根据模式获取编码器

View File

@@ -2,16 +2,19 @@ package logger
import (
"fmt"
"os"
"path/filepath"
"regexp"
"strconv"
"strings"
"unicode/utf8"
"github.com/fatih/color"
"github.com/wonli/aqi/internal/config"
"go.uber.org/zap"
"go.uber.org/zap/buffer"
"go.uber.org/zap/zapcore"
"gopkg.in/natefinch/lumberjack.v2"
"os"
"path/filepath"
"regexp"
"strings"
)
var ZapLog *zap.Logger
@@ -68,7 +71,7 @@ func Init(c config.Logger) {
stdEncoder := newLimitLengthEncoder(c.GetEncoder(""), 300)
stdLog := zapcore.NewCore(stdEncoder, zapcore.AddSync(os.Stdout), zap.InfoLevel)
fileEncoder := getFileStyleEncoder()
fileEncoder := getFileStyleEncoder(c)
fileLog := zapcore.NewCore(fileEncoder, zapcore.AddSync(&hook), zap.InfoLevel)
rFileLog := zapcore.NewCore(fileEncoder, zapcore.AddSync(&runtimeHook), zap.InfoLevel)
@@ -106,7 +109,7 @@ func newLimitLengthEncoder(encoder zapcore.Encoder, limit int) zapcore.Encoder {
}
// getFileStyleEncoder 获取文件风格的日志编码器
func getFileStyleEncoder() zapcore.Encoder {
func getFileStyleEncoder(c config.Logger) zapcore.Encoder {
encoderConfig := zapcore.EncoderConfig{
TimeKey: "", // 这些字段会在自定义格式中处理
LevelKey: "",
@@ -120,22 +123,69 @@ func getFileStyleEncoder() zapcore.Encoder {
EncodeName: zapcore.FullNameEncoder,
}
return &fileStyleEncoder{
Encoder: zapcore.NewConsoleEncoder(encoderConfig),
filters := make([]FilterRule, 0, len(c.LogFilter))
if c.LogFilter != nil {
for actionPattern, fieldRule := range c.LogFilter {
// 解析字段路径和长度格式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 {
continue
}
patternStr := fmt.Sprintf(`"action"\s*:\s*"%s"`, regexp.QuoteMeta(actionPattern))
pattern := regexp.MustCompile(patternStr)
fieldPattern := fmt.Sprintf(`("%s":\s*)(.*)`, regexp.QuoteMeta(fieldPath))
fieldRegex := regexp.MustCompile(fieldPattern)
filters = append(filters, FilterRule{
Action: actionPattern,
Field: fieldPath,
MaxLen: maxLen,
Pattern: pattern,
FieldRegex: fieldRegex,
})
}
}
return &fileStyleEncoder{
Encoder: zapcore.NewConsoleEncoder(encoderConfig),
base64Filter: regexp.MustCompile(`("data:[^"]*;base64,)([^"]*)`),
logFilters: filters,
}
}
type FilterRule struct {
Action string
Field string
MaxLen int
Pattern *regexp.Regexp // 匹配action的正则
FieldRegex *regexp.Regexp // 匹配字段的正则
}
// fileStyleEncoder 自定义编码风格输出
type fileStyleEncoder struct {
zapcore.Encoder
base64Filter *regexp.Regexp
logFilters []FilterRule
}
func (e *fileStyleEncoder) EncodeEntry(entry zapcore.Entry, fields []zapcore.Field) (*buffer.Buffer, error) {
// 处理自定义过滤规则
if e.logFilters != nil {
entry.Message = e.processMessage(entry.Message, e.logFilters)
}
// 正则表达式过滤base64的主体
filterRegex := regexp.MustCompile(`("data:[^"]*;base64,)([^"]*)`)
if filterRegex.MatchString(entry.Message) {
// 替换中间部分,保留前后部分
entry.Message = filterRegex.ReplaceAllString(entry.Message, `$1..(replace)..`)
if e.base64Filter.MatchString(entry.Message) {
entry.Message = e.base64Filter.ReplaceAllString(entry.Message, `$1..(replace)..`)
}
// 创建输出缓冲区
@@ -203,3 +253,28 @@ func (e *fileStyleEncoder) EncodeEntry(entry zapcore.Entry, fields []zapcore.Fie
return buf, nil
}
func (e *fileStyleEncoder) processMessage(msg string, filters []FilterRule) string {
for _, rule := range filters {
if strings.Contains(msg, rule.Action) && rule.Pattern.MatchString(msg) {
msg = rule.FieldRegex.ReplaceAllStringFunc(msg, func(match string) string {
parts := rule.FieldRegex.FindStringSubmatch(match)
if len(parts) >= 3 {
value := parts[2]
if len(value) > rule.MaxLen {
prefix := parts[1]
// 确保不会从中文字符中间截断
safeIndex := rule.MaxLen
for safeIndex > 0 && !utf8.RuneStart(value[safeIndex]) {
safeIndex--
}
return prefix + value[:safeIndex] + "..."
}
return match
}
return match
})
}
}
return msg
}

60
logger/zap_test.go Normal file
View File

@@ -0,0 +1,60 @@
package logger
import (
"fmt"
"log"
"regexp"
"strconv"
"strings"
"testing"
)
func TestZap(t *testing.T) {
logFilters := map[string]string{
"article.list": "data:16",
"res.uploadImg": "params:64",
}
filters := make([]FilterRule, 0, len(logFilters))
for actionPattern, fieldRule := range 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)
fieldPattern := fmt.Sprintf(`("%s":\s*)(.*)`, regexp.QuoteMeta(fieldPath))
fieldRegex := regexp.MustCompile(fieldPattern)
filters = append(filters, FilterRule{
Action: actionPattern,
Field: fieldPath,
MaxLen: maxLen,
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{}
// 处理消息
processedMsg1 := fs.processMessage(msg1, filters)
log.Println("处理后的消息1:", processedMsg1)
processedMsg2 := fs.processMessage(msg2, filters)
log.Println("处理后的消息2:", processedMsg2)
}