From 690a2d71ca965280d4f33ec5f49ba4606daeff0a Mon Sep 17 00:00:00 2001 From: Yvan <8574526@qq,com> Date: Fri, 21 Aug 2026 17:09:50 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BC=98=E5=8C=96=E7=BB=93=E6=9E=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pkg/logging/zap.go | 125 ++++++++++++++++++++++++++++++---------- pkg/logging/zap_test.go | 23 ++++++++ 2 files changed, 118 insertions(+), 30 deletions(-) diff --git a/pkg/logging/zap.go b/pkg/logging/zap.go index 42ff57d..c499efd 100644 --- a/pkg/logging/zap.go +++ b/pkg/logging/zap.go @@ -6,6 +6,7 @@ import ( "log/slog" "os" "path/filepath" + "sort" "strings" "sync" "time" @@ -151,36 +152,111 @@ func (l *ReloadableLogger) Close() { } } -type moduleFilterCore struct { - zapcore.Core +type consoleSummaryCore struct { + level zapcore.LevelEnabler + output zapcore.WriteSyncer + prefix string fileOnly map[string]struct{} - mod string + fields []zapcore.Field } -func (c *moduleFilterCore) With(fields []zapcore.Field) zapcore.Core { - mod := c.mod - if value := moduleField(fields); value != "" { - mod = value - } - return &moduleFilterCore{Core: c.Core.With(fields), fileOnly: c.fileOnly, mod: mod} +func (c *consoleSummaryCore) Enabled(level zapcore.Level) bool { return c.level.Enabled(level) } + +func (c *consoleSummaryCore) With(fields []zapcore.Field) zapcore.Core { + inherited := append([]zapcore.Field(nil), c.fields...) + inherited = append(inherited, fields...) + return &consoleSummaryCore{level: c.level, output: c.output, prefix: c.prefix, fileOnly: c.fileOnly, fields: inherited} } -func (c *moduleFilterCore) Check(entry zapcore.Entry, checked *zapcore.CheckedEntry) *zapcore.CheckedEntry { +func (c *consoleSummaryCore) Check(entry zapcore.Entry, checked *zapcore.CheckedEntry) *zapcore.CheckedEntry { if c.Enabled(entry.Level) { return checked.AddCore(entry, c) } return checked } -func (c *moduleFilterCore) Write(entry zapcore.Entry, fields []zapcore.Field) error { - mod := c.mod - if value := moduleField(fields); value != "" { - mod = value - } - if _, excluded := c.fileOnly[mod]; excluded { +func (c *consoleSummaryCore) Write(entry zapcore.Entry, fields []zapcore.Field) error { + allFields := append([]zapcore.Field(nil), c.fields...) + allFields = append(allFields, fields...) + module := moduleField(allFields) + if _, excluded := c.fileOnly[module]; excluded { return nil } - return c.Core.Write(entry, fields) + _, err := c.output.Write([]byte(formatConsoleEntry(entry, module, allFields, c.prefix))) + return err +} + +func (c *consoleSummaryCore) Sync() error { return c.output.Sync() } + +var hiddenConsoleFields = map[string]struct{}{ + "mod": {}, "service.id": {}, "service.name": {}, "service.version": {}, + "node": {}, "app_id": {}, "env": {}, "trace_id": {}, "span_id": {}, +} + +func formatConsoleEntry(entry zapcore.Entry, module string, fields []zapcore.Field, prefix string) string { + values := zapcore.NewMapObjectEncoder() + for _, field := range fields { + field.AddTo(values) + } + keys := make([]string, 0, len(values.Fields)) + for key, value := range values.Fields { + if _, hidden := hiddenConsoleFields[key]; hidden || consoleValueEmpty(value) { + continue + } + keys = append(keys, key) + } + sort.Strings(keys) + + caller := "" + if entry.Caller.Defined { + caller = filepath.Base(entry.Caller.File) + fmt.Sprintf(":%d", entry.Caller.Line) + } + if module == "" { + module = "app" + } + parts := []string{ + prefix + entry.Time.Format("15:04:05.000"), + consoleLevel(entry.Level), + fmt.Sprintf("%-10s", module), + } + if caller != "" { + parts = append(parts, fmt.Sprintf("%-24s", caller)) + } + parts = append(parts, entry.Message) + for _, key := range keys { + parts = append(parts, key+"="+formatConsoleValue(values.Fields[key])) + } + return strings.Join(parts, " ") + "\n" +} + +func consoleLevel(level zapcore.Level) string { + label := strings.ToUpper(level.String()) + color := "\x1b[36m" + switch { + case level >= zapcore.ErrorLevel: + color = "\x1b[31m" + case level == zapcore.WarnLevel: + color = "\x1b[33m" + case level == zapcore.InfoLevel: + color = "\x1b[32m" + } + return color + fmt.Sprintf("%-5s", label) + "\x1b[0m" +} + +func consoleValueEmpty(value any) bool { + return value == nil || value == "" +} + +func formatConsoleValue(value any) string { + text := fmt.Sprint(value) + text = strings.Join(strings.Fields(text), " ") + if len(text) > 180 { + text = text[:177] + "..." + } + if strings.ContainsAny(text, " \t\r\n") { + return fmt.Sprintf("%q", text) + } + return text } func moduleField(fields []zapcore.Field) string { @@ -433,19 +509,8 @@ func newZapHandler(root, filename string, options Options, errorSink *errorSinkS for _, module := range options.FileOnlyModules { fileOnly[module] = struct{}{} } - // Files stay structured JSON for searching and the log viewer. The console - // gets a compact human-readable encoder so entries do not become one long - // JSON line that extends far beyond the terminal viewport. - consoleEncoderConfig := zap.NewProductionEncoderConfig() - consoleEncoderConfig.EncodeTime = func(value time.Time, output zapcore.PrimitiveArrayEncoder) { - output.AppendString(options.Prefix + value.Format("2006-01-02 15:04:05.000")) - } - consoleEncoderConfig.EncodeLevel = zapcore.LowercaseColorLevelEncoder - consoleEncoderConfig.EncodeCaller = zapcore.FullCallerEncoder - consoleEncoderConfig.ConsoleSeparator = " " - consoleEncoder := zapcore.NewConsoleEncoder(consoleEncoderConfig) - consoleCore := zapcore.NewCore(consoleEncoder, zapcore.AddSync(os.Stdout), levelEnabler) - core = zapcore.NewTee(fileCore, &moduleFilterCore{Core: consoleCore, fileOnly: fileOnly}) + consoleCore := &consoleSummaryCore{level: levelEnabler, output: zapcore.AddSync(os.Stdout), prefix: options.Prefix, fileOnly: fileOnly} + core = zapcore.NewTee(fileCore, consoleCore) } routed := &routedFileCore{base: core, encoder: outputEncoder.Clone(), level: levelEnabler, root: root, retentionDay: options.RetentionDay, state: &routedFileState{writers: map[string]*DailyWriter{}}, errorSink: errorSink} zapLogger := zap.New(routed) diff --git a/pkg/logging/zap_test.go b/pkg/logging/zap_test.go index 9b84c11..0f88811 100644 --- a/pkg/logging/zap_test.go +++ b/pkg/logging/zap_test.go @@ -77,6 +77,29 @@ func TestConsoleEncoderDoesNotChangeJSONFileEncoder(t *testing.T) { } } +func TestConsoleSummaryHidesInfrastructureNoise(t *testing.T) { + entry := zapcore.Entry{ + Level: zapcore.InfoLevel, + Time: time.Date(2026, 8, 21, 16, 50, 53, 145000000, time.Local), + Message: "register swagger handler", + Caller: zapcore.NewEntryCaller(0, `D:\workspace\kra\internal\server\swagger.go`, 55, true), + } + line := formatConsoleEntry(entry, "system", []zapcore.Field{ + zap.String("service.id", "DESKTOP"), zap.String("app_id", "kra"), + zap.String("trace_id", ""), zap.String("path", "/swagger/*any"), + }, "[kra] ") + for _, want := range []string{"[kra] 16:50:53.145", "INFO", "system", "swagger.go:55", "register swagger handler", "path=/swagger/*any"} { + if !strings.Contains(line, want) { + t.Fatalf("console line %q does not contain %q", line, want) + } + } + for _, unwanted := range []string{"service.id", "DESKTOP", "app_id", "trace_id", `D:\workspace`} { + if strings.Contains(line, unwanted) { + t.Fatalf("console line still contains %q: %s", unwanted, line) + } + } +} + func TestZapHandlerRecordsEveryErrorThroughSink(t *testing.T) { root := t.TempDir() var entries []ErrorEntry