Skip to content

Commit

Permalink
Add ColorHeaderAndFields logger option (#118)
Browse files Browse the repository at this point in the history
* Add `ColorHeaderAndFields` logger option
* Add color globals for re-used colored field elements
  • Loading branch information
picatz authored Aug 29, 2022
1 parent 1791bf6 commit 9846b38
Show file tree
Hide file tree
Showing 2 changed files with 70 additions and 14 deletions.
80 changes: 66 additions & 14 deletions intlogger.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,12 @@ var (
Warn: color.New(color.FgHiYellow),
Error: color.New(color.FgHiRed),
}

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
Expand All @@ -70,6 +76,7 @@ type intLogger struct {
level *int32

headerColor ColorOption
fieldColor ColorOption

implied []interface{}

Expand Down Expand Up @@ -115,14 +122,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{
Expand All @@ -137,6 +149,7 @@ func newLogger(opts *LoggerOptions) *intLogger {
exclude: opts.Exclude,
independentLevels: opts.IndependentLevels,
headerColor: headerColor,
fieldColor: fieldColor,
}
if opts.IncludeLocation {
l.callerOffset = offsetIntLogger + opts.AdditionalLocationOffset
Expand Down Expand Up @@ -235,7 +248,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 {
Expand Down Expand Up @@ -281,7 +305,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 {
Expand All @@ -295,13 +319,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
Expand Down Expand Up @@ -353,30 +380,55 @@ 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
default:
key = fmt.Sprintf("%s", st)
}

// Optionally apply the ANSI "faint" and "bold"
// SGR values to the key.
if l.fieldColor != ColorOff {
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 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") {
l.writer.WriteString("\n ")
l.writer.WriteString(key)
l.writer.WriteString("=\n")
writeIndent(l.writer, val, " | ")
if l.fieldColor != ColorOff {
l.writer.WriteString(faintFieldSeparatorWithNewLine)
writeIndent(l.writer, val, faintMultiLinePrefix)
} else {
l.writer.WriteString("=\n")
writeIndent(l.writer, val, " | ")
}
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(faintFieldSeparator)
} 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(faintFieldSeparator)
} else {
l.writer.WriteByte('=')
}
l.writer.WriteString(val)
}
}
Expand Down
4 changes: 4 additions & 0 deletions logger.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down

0 comments on commit 9846b38

Please sign in to comment.