From 8c37f289569f64b785c37d55a85d284d39fcbb71 Mon Sep 17 00:00:00 2001 From: Kent 'picat' Gruber Date: Mon, 29 Aug 2022 12:41:34 -0400 Subject: [PATCH 1/7] Add `ColorHeaderAndFields` logger option --- intlogger.go | 109 +++++++++++++++++++++++++++++++++++++++------------ logger.go | 4 ++ 2 files changed, 88 insertions(+), 25 deletions(-) diff --git a/intlogger.go b/intlogger.go index e2ebdb0..a33e131 100644 --- a/intlogger.go +++ b/intlogger.go @@ -70,6 +70,7 @@ type intLogger struct { level *int32 headerColor ColorOption + fieldColor ColorOption implied []interface{} @@ -115,14 +116,19 @@ func newLogger(opts *LoggerOptions) *intLogger { mutex = new(sync.Mutex) } - var primaryColor, headerColor ColorOption - - if opts.ColorHeaderOnly { - primaryColor = ColorOff + var ( + primaryColor ColorOption = ColorOff + headerColor ColorOption = ColorOff + fieldColor ColorOption = ColorOff + ) + switch { + case opts.ColorHeaderOnly: headerColor = opts.Color - } else { + case opts.ColorHeaderAndFields: + fieldColor = opts.Color + headerColor = opts.Color + default: primaryColor = opts.Color - headerColor = ColorOff } l := &intLogger{ @@ -137,6 +143,7 @@ func newLogger(opts *LoggerOptions) *intLogger { exclude: opts.Exclude, independentLevels: opts.IndependentLevels, headerColor: headerColor, + fieldColor: fieldColor, } if opts.IncludeLocation { l.callerOffset = offsetIntLogger + opts.AdditionalLocationOffset @@ -235,7 +242,18 @@ func needsQuoting(str string) bool { return false } -// Non-JSON logging format function +// logPlain is the non-JSON logging format function which writes directly +// to the underlying writer the logger was initialized with. +// +// If the logger was initialized with a color function, it also handles +// applying the color to the log message. +// +// Color Options +// 1. No color. +// 2. Color the whole log line, based on the level. +// 3. Color only the header (level) part of the log line. +// 4. Color both the header and fields of the log line. +// func (l *intLogger) logPlain(t time.Time, name string, level Level, msg string, args ...interface{}) { if !l.disableTime { @@ -245,10 +263,11 @@ func (l *intLogger) logPlain(t time.Time, name string, level Level, msg string, s, ok := _levelToBracket[level] if ok { - if l.headerColor != ColorOff { + switch { + case l.headerColor != ColorOff: color := _levelToColor[level] color.Fprint(l.writer, s) - } else { + default: l.writer.WriteString(s) } } else { @@ -281,7 +300,7 @@ func (l *intLogger) logPlain(t time.Time, name string, level Level, msg string, var stacktrace CapturedStacktrace - if args != nil && len(args) > 0 { + if len(args) > 0 { if len(args)%2 != 0 { cs, ok := args[len(args)-1].(CapturedStacktrace) if ok { @@ -295,13 +314,16 @@ func (l *intLogger) logPlain(t time.Time, name string, level Level, msg string, l.writer.WriteByte(':') + // Handle the field arguments, which come in pairs (key=val). FOR: for i := 0; i < len(args); i = i + 2 { var ( + key string val string raw bool ) + // Convert the field value to a string. switch st := args[i+1].(type) { case string: val = st @@ -353,8 +375,7 @@ func (l *intLogger) logPlain(t time.Time, name string, level Level, msg string, } } - var key string - + // Convert the field key to a string. switch st := args[i].(type) { case string: key = st @@ -362,23 +383,61 @@ func (l *intLogger) logPlain(t time.Time, name string, level Level, msg string, key = fmt.Sprintf("%s", st) } - if strings.Contains(val, "\n") { - l.writer.WriteString("\n ") - l.writer.WriteString(key) - l.writer.WriteString("=\n") - writeIndent(l.writer, val, " | ") - l.writer.WriteString(" ") - } else if !raw && needsQuoting(val) { + // Optionally apply the ANSI "dim" (faint) and "bold" + // SGR values to the key. + if l.fieldColor != ColorOff { + color := color.New(color.Faint, color.Bold) + key = color.Sprint(key) + } + + // Values may contain multiple lines, and that format + // is preserved, with each line prefixed with a " | " + // to show it's part of a collection of lines. + // + // Values may also neeq quoting, if not all the runes + // in the value string are "normal", like if they + // contain ANSI escape sequences. + switch { + case strings.Contains(val, "\n"): + key = "\n " + key + + valStrBuilder := &strings.Builder{} + valStrWriter := newWriter(valStrBuilder, ColorOff) + + valStrBuilder.WriteString("\n") + + if l.fieldColor != ColorOff { + color := color.New(color.Faint) + writeIndent(valStrWriter, val, color.Sprint(" | ")) + } else { + writeIndent(valStrWriter, val, " | ") + } + valStrWriter.Flush(level) + + valStrBuilder.WriteString(" ") + + val = valStrBuilder.String() + case !raw && needsQuoting(val): + val = strconv.Quote(val) + } + + // If the key contains a newline, because the value itself + // contains newlines, then we pad with an extra space. + if !strings.Contains(key, "\n") { l.writer.WriteByte(' ') - l.writer.WriteString(key) - l.writer.WriteByte('=') - l.writer.WriteString(strconv.Quote(val)) + } + l.writer.WriteString(key) + + if l.fieldColor != ColorOff { + faintColor := color.New(color.Faint) + faintColor.Fprint(l.writer, "=") + resetColor := color.New(color.Reset) + resetColor.Fprint(l.writer, "") } else { - l.writer.WriteByte(' ') - l.writer.WriteString(key) l.writer.WriteByte('=') - l.writer.WriteString(val) } + + l.writer.WriteString(val) } } diff --git a/logger.go b/logger.go index f371874..50dee82 100644 --- a/logger.go +++ b/logger.go @@ -274,6 +274,10 @@ type LoggerOptions struct { // Only color the header, not the body. This can help with readability of long messages. ColorHeaderOnly bool + // Color the header and message body fields. This can help with readability + // of long messages with multiple fields. + ColorHeaderAndFields bool + // A function which is called with the log information and if it returns true the value // should not be logged. // This is useful when interacting with a system that you wish to suppress the log From 900f370d3d0321b512611fbc237d3240c2e79529 Mon Sep 17 00:00:00 2001 From: Kent 'picat' Gruber Date: Mon, 29 Aug 2022 13:50:24 -0400 Subject: [PATCH 2/7] Use `if/else` instead of `switch` Addresses https://github.com/hashicorp/go-hclog/pull/118#discussion_r957600295 and https://github.com/hashicorp/go-hclog/pull/118#discussion_r957603396 --- intlogger.go | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/intlogger.go b/intlogger.go index a33e131..39b2ef2 100644 --- a/intlogger.go +++ b/intlogger.go @@ -263,11 +263,10 @@ func (l *intLogger) logPlain(t time.Time, name string, level Level, msg string, s, ok := _levelToBracket[level] if ok { - switch { - case l.headerColor != ColorOff: + if l.headerColor != ColorOff { color := _levelToColor[level] color.Fprint(l.writer, s) - default: + } else { l.writer.WriteString(s) } } else { @@ -397,8 +396,7 @@ func (l *intLogger) logPlain(t time.Time, name string, level Level, msg string, // Values may also neeq quoting, if not all the runes // in the value string are "normal", like if they // contain ANSI escape sequences. - switch { - case strings.Contains(val, "\n"): + if strings.Contains(val, "\n") { key = "\n " + key valStrBuilder := &strings.Builder{} @@ -417,7 +415,7 @@ func (l *intLogger) logPlain(t time.Time, name string, level Level, msg string, valStrBuilder.WriteString(" ") val = valStrBuilder.String() - case !raw && needsQuoting(val): + } else if !raw && needsQuoting(val) { val = strconv.Quote(val) } From 7c2f5c4ed6b2d2235b579b92b8a4a43acf364208 Mon Sep 17 00:00:00 2001 From: Kent 'picat' Gruber Date: Mon, 29 Aug 2022 15:05:19 -0400 Subject: [PATCH 3/7] Add field color globals Addresses https://github.com/hashicorp/go-hclog/pull/118#discussion_r957642487 --- intlogger.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/intlogger.go b/intlogger.go index 39b2ef2..499ff50 100644 --- a/intlogger.go +++ b/intlogger.go @@ -48,6 +48,9 @@ var ( Warn: color.New(color.FgHiYellow), Error: color.New(color.FgHiRed), } + + faintBoldColor = color.New(color.Faint, color.Bold) + faintColor = color.New(color.Faint) ) // Make sure that intLogger is a Logger From bcbe74d4b449b702e59de51a83aa67822e7396e2 Mon Sep 17 00:00:00 2001 From: Kent 'picat' Gruber Date: Mon, 29 Aug 2022 15:05:51 -0400 Subject: [PATCH 4/7] Use previous field writing logic with color check Addresses: * https://github.com/hashicorp/go-hclog/pull/118#discussion_r957647170 * https://github.com/hashicorp/go-hclog/pull/118#discussion_r957638858 --- intlogger.go | 50 +++++++++++++++----------------------------------- 1 file changed, 15 insertions(+), 35 deletions(-) diff --git a/intlogger.go b/intlogger.go index 499ff50..30b9f5b 100644 --- a/intlogger.go +++ b/intlogger.go @@ -385,60 +385,40 @@ func (l *intLogger) logPlain(t time.Time, name string, level Level, msg string, key = fmt.Sprintf("%s", st) } - // Optionally apply the ANSI "dim" (faint) and "bold" + // Optionally apply the ANSI "faint" and "bold" // SGR values to the key. if l.fieldColor != ColorOff { - color := color.New(color.Faint, color.Bold) - key = color.Sprint(key) + key = faintBoldColor.Sprint(key) } // Values may contain multiple lines, and that format // is preserved, with each line prefixed with a " | " // to show it's part of a collection of lines. // - // Values may also neeq quoting, if not all the runes + // Values may also need quoting, if not all the runes // in the value string are "normal", like if they // contain ANSI escape sequences. if strings.Contains(val, "\n") { - key = "\n " + key - - valStrBuilder := &strings.Builder{} - valStrWriter := newWriter(valStrBuilder, ColorOff) - - valStrBuilder.WriteString("\n") - + l.writer.WriteString("\n ") + l.writer.WriteString(key) + l.writer.WriteString("=\n") if l.fieldColor != ColorOff { - color := color.New(color.Faint) - writeIndent(valStrWriter, val, color.Sprint(" | ")) + writeIndent(l.writer, val, faintColor.Sprint(" | ")) } else { - writeIndent(valStrWriter, val, " | ") + writeIndent(l.writer, val, " | ") } - valStrWriter.Flush(level) - - valStrBuilder.WriteString(" ") - - val = valStrBuilder.String() + l.writer.WriteString(" ") } else if !raw && needsQuoting(val) { - val = strconv.Quote(val) - } - - // If the key contains a newline, because the value itself - // contains newlines, then we pad with an extra space. - if !strings.Contains(key, "\n") { l.writer.WriteByte(' ') - } - l.writer.WriteString(key) - - if l.fieldColor != ColorOff { - faintColor := color.New(color.Faint) - faintColor.Fprint(l.writer, "=") - resetColor := color.New(color.Reset) - resetColor.Fprint(l.writer, "") + l.writer.WriteString(key) + l.writer.WriteByte('=') + l.writer.WriteString(strconv.Quote(val)) } else { + l.writer.WriteByte(' ') + l.writer.WriteString(key) l.writer.WriteByte('=') + l.writer.WriteString(val) } - - l.writer.WriteString(val) } } From 60e2fe97896872bdc3414c3ab3f360e5efef4d23 Mon Sep 17 00:00:00 2001 From: Kent 'picat' Gruber Date: Mon, 29 Aug 2022 15:28:00 -0400 Subject: [PATCH 5/7] Add `multiLinePrefix` and add more `fieldColor` checks --- intlogger.go | 23 ++++++++++++++++++----- 1 file changed, 18 insertions(+), 5 deletions(-) diff --git a/intlogger.go b/intlogger.go index 30b9f5b..dbea94d 100644 --- a/intlogger.go +++ b/intlogger.go @@ -32,6 +32,10 @@ const TimeFormatJSON = "2006-01-02T15:04:05.000000Z07:00" // errJsonUnsupportedTypeMsg is included in log json entries, if an arg cannot be serialized to json const errJsonUnsupportedTypeMsg = "logging contained values that don't serialize to json" +// multiLinePrefix is used for fields that contain multi-line values +// when using non-JSON logging. +const multiLinePrefix = " | " + var ( _levelToBracket = map[Level]string{ Debug: "[DEBUG]", @@ -401,22 +405,31 @@ func (l *intLogger) logPlain(t time.Time, name string, level Level, msg string, if strings.Contains(val, "\n") { l.writer.WriteString("\n ") l.writer.WriteString(key) - l.writer.WriteString("=\n") if l.fieldColor != ColorOff { - writeIndent(l.writer, val, faintColor.Sprint(" | ")) + l.writer.WriteString(faintColor.Sprint("=\n")) + writeIndent(l.writer, val, faintColor.Sprint(multiLinePrefix)) } else { - writeIndent(l.writer, val, " | ") + l.writer.WriteString("=\n") + writeIndent(l.writer, val, multiLinePrefix) } l.writer.WriteString(" ") } else if !raw && needsQuoting(val) { l.writer.WriteByte(' ') l.writer.WriteString(key) - l.writer.WriteByte('=') + if l.fieldColor != ColorOff { + l.writer.WriteString(faintColor.Sprint("=")) + } else { + l.writer.WriteByte('=') + } l.writer.WriteString(strconv.Quote(val)) } else { l.writer.WriteByte(' ') l.writer.WriteString(key) - l.writer.WriteByte('=') + if l.fieldColor != ColorOff { + l.writer.WriteString(faintColor.Sprint("=")) + } else { + l.writer.WriteByte('=') + } l.writer.WriteString(val) } } From b05901417e8147177f8f23d9155f4e5cccfbeb5b Mon Sep 17 00:00:00 2001 From: Kent 'picat' Gruber Date: Mon, 29 Aug 2022 15:29:54 -0400 Subject: [PATCH 6/7] Add `faintMultiLinePrefix` --- intlogger.go | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/intlogger.go b/intlogger.go index dbea94d..e270997 100644 --- a/intlogger.go +++ b/intlogger.go @@ -53,8 +53,9 @@ var ( Error: color.New(color.FgHiRed), } - faintBoldColor = color.New(color.Faint, color.Bold) - faintColor = color.New(color.Faint) + faintBoldColor = color.New(color.Faint, color.Bold) + faintColor = color.New(color.Faint) + faintMultiLinePrefix = faintColor.Sprint(multiLinePrefix) ) // Make sure that intLogger is a Logger @@ -407,7 +408,7 @@ func (l *intLogger) logPlain(t time.Time, name string, level Level, msg string, l.writer.WriteString(key) if l.fieldColor != ColorOff { l.writer.WriteString(faintColor.Sprint("=\n")) - writeIndent(l.writer, val, faintColor.Sprint(multiLinePrefix)) + writeIndent(l.writer, val, faintMultiLinePrefix) } else { l.writer.WriteString("=\n") writeIndent(l.writer, val, multiLinePrefix) From fd264152857468feff6a784c047e6585bebdd664 Mon Sep 17 00:00:00 2001 From: Kent 'picat' Gruber Date: Mon, 29 Aug 2022 15:38:57 -0400 Subject: [PATCH 7/7] Use color globals for re-used field elements --- intlogger.go | 20 +++++++++----------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/intlogger.go b/intlogger.go index e270997..9312f3c 100644 --- a/intlogger.go +++ b/intlogger.go @@ -32,10 +32,6 @@ const TimeFormatJSON = "2006-01-02T15:04:05.000000Z07:00" // errJsonUnsupportedTypeMsg is included in log json entries, if an arg cannot be serialized to json const errJsonUnsupportedTypeMsg = "logging contained values that don't serialize to json" -// multiLinePrefix is used for fields that contain multi-line values -// when using non-JSON logging. -const multiLinePrefix = " | " - var ( _levelToBracket = map[Level]string{ Debug: "[DEBUG]", @@ -53,9 +49,11 @@ var ( Error: color.New(color.FgHiRed), } - faintBoldColor = color.New(color.Faint, color.Bold) - faintColor = color.New(color.Faint) - faintMultiLinePrefix = faintColor.Sprint(multiLinePrefix) + faintBoldColor = color.New(color.Faint, color.Bold) + faintColor = color.New(color.Faint) + faintMultiLinePrefix = faintColor.Sprint(" | ") + faintFieldSeparator = faintColor.Sprint("=") + faintFieldSeparatorWithNewLine = faintColor.Sprint("=\n") ) // Make sure that intLogger is a Logger @@ -407,18 +405,18 @@ func (l *intLogger) logPlain(t time.Time, name string, level Level, msg string, l.writer.WriteString("\n ") l.writer.WriteString(key) if l.fieldColor != ColorOff { - l.writer.WriteString(faintColor.Sprint("=\n")) + l.writer.WriteString(faintFieldSeparatorWithNewLine) writeIndent(l.writer, val, faintMultiLinePrefix) } else { l.writer.WriteString("=\n") - writeIndent(l.writer, val, multiLinePrefix) + writeIndent(l.writer, val, " | ") } l.writer.WriteString(" ") } else if !raw && needsQuoting(val) { l.writer.WriteByte(' ') l.writer.WriteString(key) if l.fieldColor != ColorOff { - l.writer.WriteString(faintColor.Sprint("=")) + l.writer.WriteString(faintFieldSeparator) } else { l.writer.WriteByte('=') } @@ -427,7 +425,7 @@ func (l *intLogger) logPlain(t time.Time, name string, level Level, msg string, l.writer.WriteByte(' ') l.writer.WriteString(key) if l.fieldColor != ColorOff { - l.writer.WriteString(faintColor.Sprint("=")) + l.writer.WriteString(faintFieldSeparator) } else { l.writer.WriteByte('=') }