From a01af36af4fb387a8bff6f528f5c5fb9cb89bb83 Mon Sep 17 00:00:00 2001 From: afjcjsbx Date: Thu, 12 Mar 2026 18:58:24 +0100 Subject: [PATCH] feat(logger): add custom console formatter for JSON and multiline strings --- pkg/logger/logger.go | 65 +++++++++++++++++++++++++++++++++++++++----- 1 file changed, 58 insertions(+), 7 deletions(-) diff --git a/pkg/logger/logger.go b/pkg/logger/logger.go index 80adcf86c..54dc61588 100644 --- a/pkg/logger/logger.go +++ b/pkg/logger/logger.go @@ -5,6 +5,7 @@ import ( "os" "path/filepath" "runtime" + "strconv" "strings" "sync" @@ -45,6 +46,9 @@ func init() { consoleWriter := zerolog.ConsoleWriter{ Out: os.Stdout, TimeFormat: "15:04:05", // TODO: make it configurable??? + + // Custom formatter to handle multiline strings and JSON objects + FormatFieldValue: formatFieldValue, } logger = zerolog.New(consoleWriter).With().Timestamp().Logger() @@ -52,6 +56,37 @@ func init() { }) } +func formatFieldValue(i any) string { + var s string + + switch val := i.(type) { + case string: + s = val + case []byte: + s = string(val) + default: + return fmt.Sprintf("%v", i) + } + + if unquoted, err := strconv.Unquote(s); err == nil { + s = unquoted + } + + if strings.Contains(s, "\n") { + return fmt.Sprintf("\n%s", s) + } + + if strings.Contains(s, " ") { + if (strings.HasPrefix(s, "{") && strings.HasSuffix(s, "}")) || + (strings.HasPrefix(s, "[") && strings.HasSuffix(s, "]")) { + return s + } + return fmt.Sprintf("%q", s) + } + + return s +} + func SetLevel(level LogLevel) { mu.Lock() defer mu.Unlock() @@ -162,10 +197,7 @@ func logMessage(level LogLevel, component string, message string, fields map[str event.Str("caller", fmt.Sprintf(" %s:%d (%s)", callerFile, callerLine, callerFunc)) } - for k, v := range fields { - event.Interface(k, v) - } - + appendFields(event, fields) event.Msg(message) // Also log to file if enabled @@ -175,9 +207,8 @@ func logMessage(level LogLevel, component string, message string, fields map[str if component != "" { fileEvent.Str("component", component) } - for k, v := range fields { - fileEvent.Interface(k, v) - } + + appendFields(event, fields) fileEvent.Msg(message) } @@ -186,6 +217,26 @@ func logMessage(level LogLevel, component string, message string, fields map[str } } +func appendFields(event *zerolog.Event, fields map[string]any) { + for k, v := range fields { + // Type switch to avoid double JSON serialization of strings + switch val := v.(type) { + case string: + event.Str(k, val) + case int: + event.Int(k, val) + case int64: + event.Int64(k, val) + case float64: + event.Float64(k, val) + case bool: + event.Bool(k, val) + default: + event.Interface(k, v) // Fallback for struct, slice and maps + } + } +} + func Debug(message string) { logMessage(DEBUG, "", message, nil) }