From 70ed71254b25b390cc7226dadfe858c941ec1514 Mon Sep 17 00:00:00 2001 From: Pieter Noordhuis Date: Thu, 30 Nov 2023 11:37:40 +0100 Subject: [PATCH 1/6] Rewrite the friendly log handler It wasn't working because it deferred to the regular `slog.TextHandler` for the `WithAttr` and `WithGroup` functions. Both of these functions don't mutate the handler but return a new one. When the top level logger called one of these, log records in that context used the standard handler instead of ours. To implement tracking of attrs and groups, I followed the guide at https://github.com/golang/example/blob/master/slog-handler-guide/README.md for writing custom handlers. --- cmd/root/logger.go | 60 +------- libs/log/handler/colors.go | 54 +++++++ libs/log/handler/colors_test.go | 31 ++++ libs/log/handler/friendly.go | 240 ++++++++++++++++++++++++++++++ libs/log/handler/friendly_test.go | 116 +++++++++++++++ libs/log/handler/options.go | 15 ++ 6 files changed, 462 insertions(+), 54 deletions(-) create mode 100644 libs/log/handler/colors.go create mode 100644 libs/log/handler/colors_test.go create mode 100644 libs/log/handler/friendly.go create mode 100644 libs/log/handler/friendly_test.go create mode 100644 libs/log/handler/options.go diff --git a/cmd/root/logger.go b/cmd/root/logger.go index be342a7ab9..494b28fc3d 100644 --- a/cmd/root/logger.go +++ b/cmd/root/logger.go @@ -3,7 +3,6 @@ package root import ( "context" "fmt" - "io" "log/slog" "os" @@ -11,7 +10,7 @@ import ( "github.com/databricks/cli/libs/env" "github.com/databricks/cli/libs/flags" "github.com/databricks/cli/libs/log" - "github.com/fatih/color" + "github.com/databricks/cli/libs/log/handler" "github.com/spf13/cobra" ) @@ -21,54 +20,6 @@ const ( envLogFormat = "DATABRICKS_LOG_FORMAT" ) -type friendlyHandler struct { - slog.Handler - w io.Writer -} - -var ( - levelTrace = color.New(color.FgYellow).Sprint("TRACE") - levelDebug = color.New(color.FgYellow).Sprint("DEBUG") - levelInfo = color.New(color.FgGreen).Sprintf("%5s", "INFO") - levelWarn = color.New(color.FgMagenta).Sprintf("%5s", "WARN") - levelError = color.New(color.FgRed).Sprint("ERROR") -) - -func (l *friendlyHandler) coloredLevel(rec slog.Record) string { - switch rec.Level { - case log.LevelTrace: - return levelTrace - case slog.LevelDebug: - return levelDebug - case slog.LevelInfo: - return levelInfo - case slog.LevelWarn: - return levelWarn - case log.LevelError: - return levelError - } - return "" -} - -func (l *friendlyHandler) Handle(ctx context.Context, rec slog.Record) error { - t := fmt.Sprintf("%02d:%02d", rec.Time.Hour(), rec.Time.Minute()) - attrs := "" - rec.Attrs(func(a slog.Attr) bool { - attrs += fmt.Sprintf(" %s%s%s", - color.CyanString(a.Key), - color.CyanString("="), - color.YellowString(a.Value.String())) - return true - }) - msg := fmt.Sprintf("%s %s %s%s\n", - color.MagentaString(t), - l.coloredLevel(rec), - rec.Message, - attrs) - _, err := l.w.Write([]byte(msg)) - return err -} - type logFlags struct { file flags.LogFileFlag level flags.LogLevelFlag @@ -83,10 +34,11 @@ func (f *logFlags) makeLogHandler(opts slog.HandlerOptions) (slog.Handler, error case flags.OutputText: w := f.file.Writer() if cmdio.IsTTY(w) { - return &friendlyHandler{ - Handler: slog.NewTextHandler(w, &opts), - w: w, - }, nil + return handler.NewFriendlyHandler(w, &handler.Options{ + Color: true, + Level: opts.Level, + ReplaceAttr: opts.ReplaceAttr, + }), nil } return slog.NewTextHandler(w, &opts), nil diff --git a/libs/log/handler/colors.go b/libs/log/handler/colors.go new file mode 100644 index 0000000000..7a8b48bc13 --- /dev/null +++ b/libs/log/handler/colors.go @@ -0,0 +1,54 @@ +package handler + +import "github.com/fatih/color" + +// ttyColors is a slice of colors that can be enabled or disabled. +// This adds one level of indirection to the colors such that they +// can be easily be enabled or disabled together, regardless of +// global settings in the color package. +type ttyColors []*color.Color + +// ttyColor is an enum for the colors in ttyColors. +type ttyColor int + +const ( + ttyColorInvalid ttyColor = iota + ttyColorTime + ttyColorAttrKey + ttyColorAttrSeparator + ttyColorAttrValue + ttyColorLevelTrace + ttyColorLevelDebug + ttyColorLevelInfo + ttyColorLevelWarn + ttyColorLevelError + + // Marker for the last value to know how many colors there are. + ttyColorLevelLast +) + +func newColors(enable bool) ttyColors { + ttyColors := make(ttyColors, ttyColorLevelLast) + ttyColors[ttyColorInvalid] = color.New(color.FgWhite) + ttyColors[ttyColorTime] = color.New(color.FgWhite, color.Bold) + ttyColors[ttyColorAttrKey] = color.New(color.FgCyan) + ttyColors[ttyColorAttrSeparator] = color.New(color.FgWhite) + ttyColors[ttyColorAttrValue] = color.New(color.FgWhite) + ttyColors[ttyColorLevelTrace] = color.New(color.FgMagenta) + ttyColors[ttyColorLevelDebug] = color.New(color.FgCyan) + ttyColors[ttyColorLevelInfo] = color.New(color.FgBlue) + ttyColors[ttyColorLevelWarn] = color.New(color.FgYellow) + ttyColors[ttyColorLevelError] = color.New(color.FgRed) + + if enable { + for _, color := range ttyColors { + color.EnableColor() + } + } else { + for _, color := range ttyColors { + color.DisableColor() + } + } + + return ttyColors +} diff --git a/libs/log/handler/colors_test.go b/libs/log/handler/colors_test.go new file mode 100644 index 0000000000..aa042fb0bb --- /dev/null +++ b/libs/log/handler/colors_test.go @@ -0,0 +1,31 @@ +package handler + +import ( + "fmt" + "testing" +) + +func showColors(t *testing.T, colors ttyColors) { + t.Log(colors[ttyColorInvalid].Sprint("invalid")) + t.Log(colors[ttyColorTime].Sprint("time")) + t.Log( + fmt.Sprint( + colors[ttyColorAttrKey].Sprint("key"), + colors[ttyColorAttrSeparator].Sprint("="), + colors[ttyColorAttrValue].Sprint("value"), + ), + ) + t.Log(colors[ttyColorLevelTrace].Sprint("trace")) + t.Log(colors[ttyColorLevelDebug].Sprint("debug")) + t.Log(colors[ttyColorLevelInfo].Sprint("info")) + t.Log(colors[ttyColorLevelWarn].Sprint("warn")) + t.Log(colors[ttyColorLevelError].Sprint("error")) +} + +func TestTTYColorsEnabled(t *testing.T) { + showColors(t, newColors(true)) +} + +func TestTTYColorsDisabled(t *testing.T) { + showColors(t, newColors(false)) +} diff --git a/libs/log/handler/friendly.go b/libs/log/handler/friendly.go new file mode 100644 index 0000000000..b4f652c811 --- /dev/null +++ b/libs/log/handler/friendly.go @@ -0,0 +1,240 @@ +package handler + +import ( + "context" + "fmt" + "io" + "log/slog" + "sync" + "time" + + "github.com/databricks/cli/libs/log" +) + +// friendlyHandler implements a custom [slog.Handler] that writes +// human readable (and colorized) log lines to a terminal. +// +// The implementation is based on the guide at: +// https://github.com/golang/example/blob/master/slog-handler-guide/README.md +type friendlyHandler struct { + opts Options + goas []groupOrAttrs + mu *sync.Mutex + out io.Writer + + // List of colors to use for formatting. + ttyColors + + // Cache (colorized) level strings. + levelTrace string + levelDebug string + levelInfo string + levelWarn string + levelError string +} + +// groupOrAttrs holds either a group name or a list of slog.Attrs. +type groupOrAttrs struct { + group string // group name if non-empty + attrs []slog.Attr // attrs if non-empty +} + +func NewFriendlyHandler(out io.Writer, opts *Options) slog.Handler { + h := &friendlyHandler{out: out, mu: &sync.Mutex{}} + if opts != nil { + h.opts = *opts + } + if h.opts.Level == nil { + h.opts.Level = slog.LevelInfo + } + + h.ttyColors = newColors(opts.Color) + + // Cache (colorized) level strings. + // The colors to use for each level are configured in `colors.go`. + h.levelTrace = h.sprintf(ttyColorLevelTrace, "%5s", "TRACE") + h.levelDebug = h.sprintf(ttyColorLevelDebug, "%5s", "DEBUG") + h.levelInfo = h.sprintf(ttyColorLevelInfo, "%5s", "INFO") + h.levelWarn = h.sprintf(ttyColorLevelWarn, "%5s", "WARN") + h.levelError = h.sprintf(ttyColorLevelError, "%5s", "ERROR") + return h +} + +func (h *friendlyHandler) sprint(color ttyColor, args ...any) string { + return h.ttyColors[color].Sprint(args...) +} + +func (h *friendlyHandler) sprintf(color ttyColor, format string, args ...any) string { + return h.ttyColors[color].Sprintf(format, args...) +} + +func (h *friendlyHandler) coloredLevel(r slog.Record) string { + switch r.Level { + case log.LevelTrace: + return h.levelTrace + case log.LevelDebug: + return h.levelDebug + case log.LevelInfo: + return h.levelInfo + case log.LevelWarn: + return h.levelWarn + case log.LevelError: + return h.levelError + } + return "" +} + +// Enabled implements slog.Handler. +func (h *friendlyHandler) Enabled(ctx context.Context, level slog.Level) bool { + return level >= h.opts.Level.Level() +} + +type handleState struct { + h *friendlyHandler + + buf []byte + prefix string + + // Keep stack of groups to pass to [slog.ReplaceAttr] function. + groups []string +} + +func (h *friendlyHandler) handleState() *handleState { + return &handleState{ + h: h, + + buf: make([]byte, 0, 1024), + prefix: "", + } +} + +func (s *handleState) openGroup(name string) { + s.groups = append(s.groups, name) + s.prefix += name + "." +} + +func (s *handleState) closeGroup(name string) { + s.prefix = s.prefix[:len(s.prefix)-len(name)-1] + s.groups = s.groups[:len(s.groups)-1] +} + +func (s *handleState) append(args ...any) { + s.buf = fmt.Append(s.buf, args...) +} + +func (s *handleState) appendf(format string, args ...any) { + s.buf = fmt.Appendf(s.buf, format, args...) +} + +func (s *handleState) appendAttr(a slog.Attr) { + if rep := s.h.opts.ReplaceAttr; rep != nil && a.Value.Kind() != slog.KindGroup { + // Resolve before calling ReplaceAttr, so the user doesn't have to. + a.Value = a.Value.Resolve() + a = rep(s.groups, a) + } + + // Resolve the Attr's value before doing anything else. + a.Value = a.Value.Resolve() + + // Ignore empty Attrs. + if a.Equal(slog.Attr{}) { + return + } + + switch a.Value.Kind() { + case slog.KindGroup: + attrs := a.Value.Group() + // Output only non-empty groups. + if len(attrs) > 0 { + if a.Key != "" { + s.openGroup(a.Key) + } + for _, aa := range attrs { + s.appendAttr(aa) + } + if a.Key != "" { + s.closeGroup(a.Key) + } + } + case slog.KindTime: + s.append( + " ", + s.h.sprint(ttyColorAttrKey, s.prefix, a.Key), + s.h.sprint(ttyColorAttrSeparator, "="), + s.h.sprint(ttyColorAttrValue, a.Value.Time().Format(time.RFC3339Nano)), + ) + default: + // Quote string values, to make them easy to parse. + s.append( + " ", + s.h.sprint(ttyColorAttrKey, s.prefix, a.Key), + s.h.sprint(ttyColorAttrSeparator, "="), + s.h.sprint(ttyColorAttrValue, fmt.Sprintf("%q", a.Value.String())), + ) + } +} + +// Handle implements slog.Handler. +func (h *friendlyHandler) Handle(ctx context.Context, r slog.Record) error { + state := h.handleState() + state.append(h.sprintf(ttyColorTime, "%02d:%02d:%02d ", r.Time.Hour(), r.Time.Minute(), r.Time.Second())) + state.appendf("%s ", h.coloredLevel(r)) + state.append(r.Message) + + // Handle state from WithGroup and WithAttrs. + goas := h.goas + if r.NumAttrs() == 0 { + // If the record has no Attrs, remove groups at the end of the list; they are empty. + for len(goas) > 0 && goas[len(goas)-1].group != "" { + goas = goas[:len(goas)-1] + } + } + for _, goa := range goas { + if goa.group != "" { + state.openGroup(goa.group) + } else { + for _, a := range goa.attrs { + state.appendAttr(a) + } + } + } + + // Add attributes from the record. + r.Attrs(func(a slog.Attr) bool { + state.appendAttr(a) + return true + }) + + // Add newline. + state.append("\n") + + // Write the log line. + h.mu.Lock() + defer h.mu.Unlock() + _, err := h.out.Write(state.buf) + return err +} + +func (h *friendlyHandler) withGroupOrAttrs(goa groupOrAttrs) *friendlyHandler { + h2 := *h + h2.goas = make([]groupOrAttrs, len(h.goas)+1) + copy(h2.goas, h.goas) + h2.goas[len(h2.goas)-1] = goa + return &h2 +} + +// WithGroup implements slog.Handler. +func (h *friendlyHandler) WithGroup(name string) slog.Handler { + if name == "" { + return h + } + return h.withGroupOrAttrs(groupOrAttrs{group: name}) +} + +// WithAttrs implements slog.Handler. +func (h *friendlyHandler) WithAttrs(attrs []slog.Attr) slog.Handler { + if len(attrs) == 0 { + return h + } + return h.withGroupOrAttrs(groupOrAttrs{attrs: attrs}) +} diff --git a/libs/log/handler/friendly_test.go b/libs/log/handler/friendly_test.go new file mode 100644 index 0000000000..7d2c2a658d --- /dev/null +++ b/libs/log/handler/friendly_test.go @@ -0,0 +1,116 @@ +package handler + +import ( + "bytes" + "context" + "log/slog" + "testing" + "time" + + "github.com/databricks/cli/libs/log" + "github.com/stretchr/testify/assert" +) + +func TestFriendlyHandler(t *testing.T) { + var out bytes.Buffer + + handler := NewFriendlyHandler(&out, &Options{ + Color: true, + Level: log.LevelTrace, + }) + + logger := slog.New(handler) + + { + // One line per level. + for _, level := range []slog.Level{ + log.LevelTrace, + log.LevelDebug, + log.LevelInfo, + log.LevelWarn, + log.LevelError, + } { + out.Reset() + logger.Log(context.Background(), level, "simple message") + t.Log(out.String()) + } + } + + { + // Single key/value pair. + out.Reset() + logger.Info("simple message", "key", "value") + t.Log(out.String()) + } + + { + // Multiple key/value pairs. + out.Reset() + logger.Info("simple message", "key1", "value", "key2", "value") + t.Log(out.String()) + } + + { + // Multiple key/value pairs with duplicate keys. + out.Reset() + logger.Info("simple message", "key", "value", "key", "value") + t.Log(out.String()) + } + + { + // Log message with time. + out.Reset() + logger.Info("simple message", "time", time.Now()) + t.Log(out.String()) + } + + { + // Log message with grouped key/value pairs. + out.Reset() + logger.Info("simple message", slog.Group("group", slog.String("key", "value"))) + t.Log(out.String()) + } + + { + // Add key/value pairs to logger. + out.Reset() + logger.With("logger_key", "value").Info("simple message") + t.Log(out.String()) + } + + { + // Add group to logger. + out.Reset() + logger.WithGroup("logger_group").Info("simple message", "key", "value") + t.Log(out.String()) + } + + { + // Add group and key/value pairs to logger. + out.Reset() + logger.WithGroup("logger_group").With("logger_key", "value").Info("simple message") + t.Log(out.String()) + } +} + +func TestFriendlyHandlerReplaceAttr(t *testing.T) { + var out bytes.Buffer + + handler := NewFriendlyHandler(&out, &Options{ + ReplaceAttr: func(groups []string, a slog.Attr) slog.Attr { + if a.Key == "key" { + a.Key = "replaced" + } + return a + }, + }) + + logger := slog.New(handler) + + { + // ReplaceAttr replaces attributes. + out.Reset() + logger.Info("simple message", "key", "value") + assert.Contains(t, out.String(), `replaced="value"`) + } +} diff --git a/libs/log/handler/options.go b/libs/log/handler/options.go new file mode 100644 index 0000000000..0b8cfbe204 --- /dev/null +++ b/libs/log/handler/options.go @@ -0,0 +1,15 @@ +package handler + +import "log/slog" + +type Options struct { + // Color enables colorized output. + Color bool + + // Level is the minimum enabled logging level. + Level slog.Leveler + + // ReplaceAttr is a function that can be used to replace attributes. + // Semantics are identical to [slog.ReplaceAttr]. + ReplaceAttr func(groups []string, a slog.Attr) slog.Attr +} From e12d982563448e08eb29f5eb2f73dc73ebe03c77 Mon Sep 17 00:00:00 2001 From: Pieter Noordhuis Date: Thu, 30 Nov 2023 23:41:21 +0100 Subject: [PATCH 2/6] Trim test logs --- libs/log/handler/friendly_test.go | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/libs/log/handler/friendly_test.go b/libs/log/handler/friendly_test.go index 7d2c2a658d..3559884d12 100644 --- a/libs/log/handler/friendly_test.go +++ b/libs/log/handler/friendly_test.go @@ -4,6 +4,7 @@ import ( "bytes" "context" "log/slog" + "strings" "testing" "time" @@ -32,7 +33,7 @@ func TestFriendlyHandler(t *testing.T) { } { out.Reset() logger.Log(context.Background(), level, "simple message") - t.Log(out.String()) + t.Log(strings.TrimSpace(out.String())) } } @@ -40,56 +41,56 @@ func TestFriendlyHandler(t *testing.T) { // Single key/value pair. out.Reset() logger.Info("simple message", "key", "value") - t.Log(out.String()) + t.Log(strings.TrimSpace(out.String())) } { // Multiple key/value pairs. out.Reset() logger.Info("simple message", "key1", "value", "key2", "value") - t.Log(out.String()) + t.Log(strings.TrimSpace(out.String())) } { // Multiple key/value pairs with duplicate keys. out.Reset() logger.Info("simple message", "key", "value", "key", "value") - t.Log(out.String()) + t.Log(strings.TrimSpace(out.String())) } { // Log message with time. out.Reset() logger.Info("simple message", "time", time.Now()) - t.Log(out.String()) + t.Log(strings.TrimSpace(out.String())) } { // Log message with grouped key/value pairs. out.Reset() logger.Info("simple message", slog.Group("group", slog.String("key", "value"))) - t.Log(out.String()) + t.Log(strings.TrimSpace(out.String())) } { // Add key/value pairs to logger. out.Reset() logger.With("logger_key", "value").Info("simple message") - t.Log(out.String()) + t.Log(strings.TrimSpace(out.String())) } { // Add group to logger. out.Reset() logger.WithGroup("logger_group").Info("simple message", "key", "value") - t.Log(out.String()) + t.Log(strings.TrimSpace(out.String())) } { // Add group and key/value pairs to logger. out.Reset() logger.WithGroup("logger_group").With("logger_key", "value").Info("simple message") - t.Log(out.String()) + t.Log(strings.TrimSpace(out.String())) } } From 1622a99e89e7dabad05a4bdc13852d1af5a0f295 Mon Sep 17 00:00:00 2001 From: Pieter Noordhuis Date: Fri, 1 Dec 2023 12:13:17 +0100 Subject: [PATCH 3/6] Tweak colors --- libs/log/handler/colors.go | 8 +++++--- libs/log/handler/friendly.go | 14 +++++++++++--- 2 files changed, 16 insertions(+), 6 deletions(-) diff --git a/libs/log/handler/colors.go b/libs/log/handler/colors.go index 7a8b48bc13..7d1ae52d85 100644 --- a/libs/log/handler/colors.go +++ b/libs/log/handler/colors.go @@ -14,6 +14,7 @@ type ttyColor int const ( ttyColorInvalid ttyColor = iota ttyColorTime + ttyColorMessage ttyColorAttrKey ttyColorAttrSeparator ttyColorAttrValue @@ -30,10 +31,11 @@ const ( func newColors(enable bool) ttyColors { ttyColors := make(ttyColors, ttyColorLevelLast) ttyColors[ttyColorInvalid] = color.New(color.FgWhite) - ttyColors[ttyColorTime] = color.New(color.FgWhite, color.Bold) + ttyColors[ttyColorTime] = color.New(color.FgBlack, color.Bold) + ttyColors[ttyColorMessage] = color.New(color.FgWhite, color.Bold) ttyColors[ttyColorAttrKey] = color.New(color.FgCyan) - ttyColors[ttyColorAttrSeparator] = color.New(color.FgWhite) - ttyColors[ttyColorAttrValue] = color.New(color.FgWhite) + ttyColors[ttyColorAttrSeparator] = color.New(color.Reset) + ttyColors[ttyColorAttrValue] = color.New(color.Reset) ttyColors[ttyColorLevelTrace] = color.New(color.FgMagenta) ttyColors[ttyColorLevelDebug] = color.New(color.FgCyan) ttyColors[ttyColorLevelInfo] = color.New(color.FgBlue) diff --git a/libs/log/handler/friendly.go b/libs/log/handler/friendly.go index b4f652c811..33b88a9e25 100644 --- a/libs/log/handler/friendly.go +++ b/libs/log/handler/friendly.go @@ -5,6 +5,7 @@ import ( "fmt" "io" "log/slog" + "strings" "sync" "time" @@ -164,12 +165,19 @@ func (s *handleState) appendAttr(a slog.Attr) { s.h.sprint(ttyColorAttrValue, a.Value.Time().Format(time.RFC3339Nano)), ) default: - // Quote string values, to make them easy to parse. + str := a.Value.String() + format := "%s" + + // Quote values wih spaces, to make them easy to parse. + if strings.ContainsAny(str, " \t\n") { + format = "%q" + } + s.append( " ", s.h.sprint(ttyColorAttrKey, s.prefix, a.Key), s.h.sprint(ttyColorAttrSeparator, "="), - s.h.sprint(ttyColorAttrValue, fmt.Sprintf("%q", a.Value.String())), + s.h.sprint(ttyColorAttrValue, fmt.Sprintf(format, str)), ) } } @@ -179,7 +187,7 @@ func (h *friendlyHandler) Handle(ctx context.Context, r slog.Record) error { state := h.handleState() state.append(h.sprintf(ttyColorTime, "%02d:%02d:%02d ", r.Time.Hour(), r.Time.Minute(), r.Time.Second())) state.appendf("%s ", h.coloredLevel(r)) - state.append(r.Message) + state.append(h.sprint(ttyColorMessage, r.Message)) // Handle state from WithGroup and WithAttrs. goas := h.goas From f40930980ce3d35152aa5f86a031a26fa40bb6fb Mon Sep 17 00:00:00 2001 From: Pieter Noordhuis Date: Fri, 1 Dec 2023 12:19:15 +0100 Subject: [PATCH 4/6] Fix test --- libs/log/handler/friendly_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libs/log/handler/friendly_test.go b/libs/log/handler/friendly_test.go index 3559884d12..93cfcaf81f 100644 --- a/libs/log/handler/friendly_test.go +++ b/libs/log/handler/friendly_test.go @@ -112,6 +112,6 @@ func TestFriendlyHandlerReplaceAttr(t *testing.T) { // ReplaceAttr replaces attributes. out.Reset() logger.Info("simple message", "key", "value") - assert.Contains(t, out.String(), `replaced="value"`) + assert.Contains(t, out.String(), `replaced=value`) } } From 374b3b45d4c72aa57579ae8c00a1f26081ebbf78 Mon Sep 17 00:00:00 2001 From: Pieter Noordhuis Date: Fri, 1 Dec 2023 12:24:45 +0100 Subject: [PATCH 5/6] Work on tests --- libs/log/handler/friendly_test.go | 107 ++++++++++++++---------------- 1 file changed, 50 insertions(+), 57 deletions(-) diff --git a/libs/log/handler/friendly_test.go b/libs/log/handler/friendly_test.go index 93cfcaf81f..ca6e823e1e 100644 --- a/libs/log/handler/friendly_test.go +++ b/libs/log/handler/friendly_test.go @@ -9,7 +9,6 @@ import ( "time" "github.com/databricks/cli/libs/log" - "github.com/stretchr/testify/assert" ) func TestFriendlyHandler(t *testing.T) { @@ -22,76 +21,65 @@ func TestFriendlyHandler(t *testing.T) { logger := slog.New(handler) - { - // One line per level. - for _, level := range []slog.Level{ - log.LevelTrace, - log.LevelDebug, - log.LevelInfo, - log.LevelWarn, - log.LevelError, - } { - out.Reset() + // Helper function to run a test case and print the output. + run := func(fn func()) { + out.Reset() + fn() + t.Log(strings.TrimSpace(out.String())) + } + + // One line per level. + for _, level := range []slog.Level{ + log.LevelTrace, + log.LevelDebug, + log.LevelInfo, + log.LevelWarn, + log.LevelError, + } { + run(func() { logger.Log(context.Background(), level, "simple message") - t.Log(strings.TrimSpace(out.String())) - } + }) } - { - // Single key/value pair. - out.Reset() + // Single key/value pair. + run(func() { logger.Info("simple message", "key", "value") - t.Log(strings.TrimSpace(out.String())) - } + }) - { - // Multiple key/value pairs. - out.Reset() + // Multiple key/value pairs. + run(func() { logger.Info("simple message", "key1", "value", "key2", "value") - t.Log(strings.TrimSpace(out.String())) - } + }) - { - // Multiple key/value pairs with duplicate keys. - out.Reset() + // Multiple key/value pairs with duplicate keys. + run(func() { logger.Info("simple message", "key", "value", "key", "value") - t.Log(strings.TrimSpace(out.String())) - } + }) - { - // Log message with time. - out.Reset() + // Log message with time. + run(func() { logger.Info("simple message", "time", time.Now()) - t.Log(strings.TrimSpace(out.String())) - } + }) - { - // Log message with grouped key/value pairs. - out.Reset() + // Log message with grouped key/value pairs. + run(func() { logger.Info("simple message", slog.Group("group", slog.String("key", "value"))) - t.Log(strings.TrimSpace(out.String())) - } + }) - { - // Add key/value pairs to logger. - out.Reset() + // Add key/value pairs to logger. + run(func() { logger.With("logger_key", "value").Info("simple message") - t.Log(strings.TrimSpace(out.String())) - } + }) - { - // Add group to logger. - out.Reset() + // Add group to logger. + run(func() { logger.WithGroup("logger_group").Info("simple message", "key", "value") - t.Log(strings.TrimSpace(out.String())) - } + }) - { - // Add group and key/value pairs to logger. - out.Reset() + // Add group and key/value pairs to logger. + run(func() { logger.WithGroup("logger_group").With("logger_key", "value").Info("simple message") - t.Log(strings.TrimSpace(out.String())) - } + }) } func TestFriendlyHandlerReplaceAttr(t *testing.T) { @@ -108,10 +96,15 @@ func TestFriendlyHandlerReplaceAttr(t *testing.T) { logger := slog.New(handler) - { - // ReplaceAttr replaces attributes. + // Helper function to run a test case and print the output. + run := func(fn func()) { out.Reset() - logger.Info("simple message", "key", "value") - assert.Contains(t, out.String(), `replaced=value`) + fn() + t.Log(strings.TrimSpace(out.String())) } + + // ReplaceAttr replaces attributes. + run(func() { + logger.Info("simple message", "key", "value") + }) } From f900d64474fcd132796ec9d635ce5f80b0d5700f Mon Sep 17 00:00:00 2001 From: Pieter Noordhuis Date: Fri, 1 Dec 2023 13:02:56 +0100 Subject: [PATCH 6/6] Tweak colors --- libs/log/handler/colors.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/libs/log/handler/colors.go b/libs/log/handler/colors.go index 7d1ae52d85..a1b8e84917 100644 --- a/libs/log/handler/colors.go +++ b/libs/log/handler/colors.go @@ -32,9 +32,9 @@ func newColors(enable bool) ttyColors { ttyColors := make(ttyColors, ttyColorLevelLast) ttyColors[ttyColorInvalid] = color.New(color.FgWhite) ttyColors[ttyColorTime] = color.New(color.FgBlack, color.Bold) - ttyColors[ttyColorMessage] = color.New(color.FgWhite, color.Bold) - ttyColors[ttyColorAttrKey] = color.New(color.FgCyan) - ttyColors[ttyColorAttrSeparator] = color.New(color.Reset) + ttyColors[ttyColorMessage] = color.New(color.Reset) + ttyColors[ttyColorAttrKey] = color.New(color.Faint) + ttyColors[ttyColorAttrSeparator] = color.New(color.Faint) ttyColors[ttyColorAttrValue] = color.New(color.Reset) ttyColors[ttyColorLevelTrace] = color.New(color.FgMagenta) ttyColors[ttyColorLevelDebug] = color.New(color.FgCyan)