From 1e085700e8b6bd8de62cef6c45f605321e5419ae Mon Sep 17 00:00:00 2001 From: Ayman Bagabas Date: Tue, 31 Oct 2023 20:33:07 -0400 Subject: [PATCH] feat: set custom styles Move styles to a per logger instance bases. --- logger.go | 14 +++++ pkg.go | 6 ++ styles.go | 161 ++++++++++++++++++++++++--------------------------- text.go | 44 +++++++++----- text_test.go | 68 +++++++++++----------- 5 files changed, 160 insertions(+), 133 deletions(-) diff --git a/logger.go b/logger.go index f044ecf..a6aacf1 100644 --- a/logger.go +++ b/logger.go @@ -46,6 +46,7 @@ type Logger struct { fields []interface{} helpers *sync.Map + styles *Styles } func (l *Logger) log(level Level, msg interface{}, keyvals ...interface{}) { @@ -290,15 +291,28 @@ func (l *Logger) SetColorProfile(profile termenv.Profile) { l.re.SetColorProfile(profile) } +// SetStyles sets the logger styles for the TextFormatter. +func (l *Logger) SetStyles(s *Styles) { + if s == nil { + s = DefaultStyles() + } + l.mu.Lock() + defer l.mu.Unlock() + l.styles = s +} + // With returns a new logger with the given keyvals added. func (l *Logger) With(keyvals ...interface{}) *Logger { + var st Styles l.mu.Lock() sl := *l + st = *l.styles l.mu.Unlock() sl.b = bytes.Buffer{} sl.mu = &sync.RWMutex{} sl.helpers = &sync.Map{} sl.fields = append(l.fields, keyvals...) + sl.styles = &st return &sl } diff --git a/pkg.go b/pkg.go index 440180c..1ba635d 100644 --- a/pkg.go +++ b/pkg.go @@ -55,6 +55,7 @@ func NewWithOptions(w io.Writer, o Options) *Logger { l.SetOutput(w) l.SetLevel(Level(l.level)) + l.SetStyles(DefaultStyles()) if l.callerFormatter == nil { l.callerFormatter = ShortCallerFormatter @@ -132,6 +133,11 @@ func SetColorProfile(profile termenv.Profile) { defaultLogger.SetColorProfile(profile) } +// SetStyles sets the logger styles for the TextFormatter. +func SetStyles(s *Styles) { + defaultLogger.SetStyles(s) +} + // GetPrefix returns the prefix for the default logger. func GetPrefix() string { return defaultLogger.GetPrefix() diff --git a/styles.go b/styles.go index b2bdf1f..14d261d 100644 --- a/styles.go +++ b/styles.go @@ -6,99 +6,92 @@ import ( "github.com/charmbracelet/lipgloss" ) -var ( - // TimestampStyle is the style for timestamps. - TimestampStyle = lipgloss.NewStyle() +// Styles defines the styles for the text logger. +type Styles struct { + // Timestamp is the style for timestamps. + Timestamp lipgloss.Style - // CallerStyle is the style for caller. - CallerStyle = lipgloss.NewStyle().Faint(true) + // Caller is the style for source caller. + Caller lipgloss.Style - // PrefixStyle is the style for prefix. - PrefixStyle = lipgloss.NewStyle().Bold(true).Faint(true) + // Prefix is the style for prefix. + Prefix lipgloss.Style - // MessageStyle is the style for messages. - MessageStyle = lipgloss.NewStyle() + // Message is the style for messages. + Message lipgloss.Style - // KeyStyle is the style for keys. - KeyStyle = lipgloss.NewStyle().Faint(true) + // Key is the style for keys. + Key lipgloss.Style - // ValueStyle is the style for values. - ValueStyle = lipgloss.NewStyle() + // Value is the style for values. + Value lipgloss.Style - // SeparatorStyle is the style for separators. - SeparatorStyle = lipgloss.NewStyle().Faint(true) + // Separator is the style for separators. + Separator lipgloss.Style - // DebugLevel is the style for debug level. - DebugLevelStyle = lipgloss.NewStyle(). - SetString(strings.ToUpper(DebugLevel.String())). - Bold(true). - MaxWidth(4). - Foreground(lipgloss.AdaptiveColor{ - Light: "63", - Dark: "63", - }) + // Levels are the styles for each level. + Levels map[Level]lipgloss.Style - // InfoLevel is the style for info level. - InfoLevelStyle = lipgloss.NewStyle(). - SetString(strings.ToUpper(InfoLevel.String())). - Bold(true). - MaxWidth(4). - Foreground(lipgloss.AdaptiveColor{ - Light: "39", - Dark: "86", - }) + // Keys overrides styles for specific keys. + Keys map[string]lipgloss.Style - // WarnLevel is the style for warn level. - WarnLevelStyle = lipgloss.NewStyle(). - SetString(strings.ToUpper(WarnLevel.String())). - Bold(true). - MaxWidth(4). - Foreground(lipgloss.AdaptiveColor{ - Light: "208", - Dark: "192", - }) - - // ErrorLevel is the style for error level. - ErrorLevelStyle = lipgloss.NewStyle(). - SetString(strings.ToUpper(ErrorLevel.String())). - Bold(true). - MaxWidth(4). - Foreground(lipgloss.AdaptiveColor{ - Light: "203", - Dark: "204", - }) - - // FatalLevel is the style for error level. - FatalLevelStyle = lipgloss.NewStyle(). - SetString(strings.ToUpper(FatalLevel.String())). - Bold(true). - MaxWidth(4). - Foreground(lipgloss.AdaptiveColor{ - Light: "133", - Dark: "134", - }) - - // KeyStyles overrides styles for specific keys. - KeyStyles = map[string]lipgloss.Style{} - - // ValueStyles overrides value styles for specific keys. - ValueStyles = map[string]lipgloss.Style{} -) + // Values overrides value styles for specific keys. + Values map[string]lipgloss.Style +} -// levelStyle is a helper function to get the style for a level. -func levelStyle(level Level) lipgloss.Style { - switch level { - case DebugLevel: - return DebugLevelStyle - case InfoLevel: - return InfoLevelStyle - case WarnLevel: - return WarnLevelStyle - case ErrorLevel: - return ErrorLevelStyle - case FatalLevel: - return FatalLevelStyle - default: - return lipgloss.NewStyle() +// DefaultStyles returns the default styles. +func DefaultStyles() *Styles { + return &Styles{ + Timestamp: lipgloss.NewStyle(), + Caller: lipgloss.NewStyle().Faint(true), + Prefix: lipgloss.NewStyle().Bold(true).Faint(true), + Message: lipgloss.NewStyle(), + Key: lipgloss.NewStyle().Faint(true), + Value: lipgloss.NewStyle(), + Separator: lipgloss.NewStyle().Faint(true), + Levels: map[Level]lipgloss.Style{ + DebugLevel: lipgloss.NewStyle(). + SetString(strings.ToUpper(DebugLevel.String())). + Bold(true). + MaxWidth(4). + Foreground(lipgloss.AdaptiveColor{ + Light: "63", + Dark: "63", + }), + InfoLevel: lipgloss.NewStyle(). + SetString(strings.ToUpper(InfoLevel.String())). + Bold(true). + MaxWidth(4). + Foreground(lipgloss.AdaptiveColor{ + Light: "39", + Dark: "86", + }), + WarnLevel: lipgloss.NewStyle(). + SetString(strings.ToUpper(WarnLevel.String())). + Bold(true). + MaxWidth(4). + Foreground(lipgloss.AdaptiveColor{ + Light: "208", + Dark: "192", + }), + ErrorLevel: lipgloss.NewStyle(). + SetString(strings.ToUpper(ErrorLevel.String())). + Bold(true). + MaxWidth(4). + Foreground(lipgloss.AdaptiveColor{ + Light: "203", + Dark: "204", + }), + FatalLevel: lipgloss.NewStyle(). + SetString(strings.ToUpper(FatalLevel.String())). + Bold(true). + MaxWidth(4). + Foreground(lipgloss.AdaptiveColor{ + Light: "133", + Dark: "134", + }), + }, + Keys: map[string]lipgloss.Style{}, + Values: map[string]lipgloss.Style{}, } } diff --git a/text.go b/text.go index c508f24..f4c34b3 100644 --- a/text.go +++ b/text.go @@ -16,6 +16,8 @@ const ( ) func (l *Logger) writeIndent(w io.Writer, str string, indent string, newline bool, key string) { + st := l.styles + // kindly borrowed from hclog for { nl := strings.IndexByte(str, '\n') @@ -23,10 +25,10 @@ func (l *Logger) writeIndent(w io.Writer, str string, indent string, newline boo if str != "" { _, _ = w.Write([]byte(indent)) val := escapeStringForOutput(str, false) - if valueStyle, ok := ValueStyles[key]; ok { + if valueStyle, ok := st.Values[key]; ok { val = valueStyle.Renderer(l.re).Render(val) } else { - val = ValueStyle.Renderer(l.re).Render(val) + val = st.Value.Renderer(l.re).Render(val) } _, _ = w.Write([]byte(val)) if newline { @@ -38,7 +40,7 @@ func (l *Logger) writeIndent(w io.Writer, str string, indent string, newline boo _, _ = w.Write([]byte(indent)) val := escapeStringForOutput(str[:nl], false) - val = ValueStyle.Renderer(l.re).Render(val) + val = st.Value.Renderer(l.re).Render(val) _, _ = w.Write([]byte(val)) _, _ = w.Write([]byte{'\n'}) str = str[nl+1:] @@ -148,6 +150,11 @@ func writeSpace(w io.Writer, first bool) { } func (l *Logger) textFormatter(keyvals ...interface{}) { + st := l.styles + if st == nil { + st = DefaultStyles() + } + lenKeyvals := len(keyvals) for i := 0; i < lenKeyvals; i += 2 { @@ -158,41 +165,46 @@ func (l *Logger) textFormatter(keyvals ...interface{}) { case TimestampKey: if t, ok := keyvals[i+1].(time.Time); ok { ts := t.Format(l.timeFormat) - ts = TimestampStyle.Renderer(l.re).Render(ts) + ts = st.Timestamp.Renderer(l.re).Render(ts) writeSpace(&l.b, firstKey) l.b.WriteString(ts) } case LevelKey: if level, ok := keyvals[i+1].(Level); ok { - lvl := levelStyle(level).Renderer(l.re).String() - writeSpace(&l.b, firstKey) - l.b.WriteString(lvl) + var lvl string + if lvlStyle, ok := st.Levels[level]; ok { + lvl = lvlStyle.Renderer(l.re).String() + } + if lvl != "" { + writeSpace(&l.b, firstKey) + l.b.WriteString(lvl) + } } case CallerKey: if caller, ok := keyvals[i+1].(string); ok { caller = fmt.Sprintf("<%s>", caller) - caller = CallerStyle.Renderer(l.re).Render(caller) + caller = st.Caller.Renderer(l.re).Render(caller) writeSpace(&l.b, firstKey) l.b.WriteString(caller) } case PrefixKey: if prefix, ok := keyvals[i+1].(string); ok { - prefix = PrefixStyle.Renderer(l.re).Render(prefix + ":") + prefix = st.Prefix.Renderer(l.re).Render(prefix + ":") writeSpace(&l.b, firstKey) l.b.WriteString(prefix) } case MessageKey: if msg := keyvals[i+1]; msg != nil { m := fmt.Sprint(msg) - m = MessageStyle.Renderer(l.re).Render(m) + m = st.Message.Renderer(l.re).Render(m) writeSpace(&l.b, firstKey) l.b.WriteString(m) } default: sep := separator indentSep := indentSeparator - sep = SeparatorStyle.Renderer(l.re).Render(sep) - indentSep = SeparatorStyle.Renderer(l.re).Render(indentSep) + sep = st.Separator.Renderer(l.re).Render(sep) + indentSep = st.Separator.Renderer(l.re).Render(indentSep) key := fmt.Sprint(keyvals[i]) val := fmt.Sprintf("%+v", keyvals[i+1]) raw := val == "" @@ -203,14 +215,14 @@ func (l *Logger) textFormatter(keyvals ...interface{}) { continue } actualKey := key - valueStyle := ValueStyle - if vs, ok := ValueStyles[actualKey]; ok { + valueStyle := st.Value + if vs, ok := st.Values[actualKey]; ok { valueStyle = vs } - if keyStyle, ok := KeyStyles[key]; ok { + if keyStyle, ok := st.Keys[key]; ok { key = keyStyle.Renderer(l.re).Render(key) } else { - key = KeyStyle.Renderer(l.re).Render(key) + key = st.Key.Renderer(l.re).Render(key) } // Values may contain multiple lines, and that format diff --git a/text_test.go b/text_test.go index 75c4bfc..8499b0f 100644 --- a/text_test.go +++ b/text_test.go @@ -237,10 +237,12 @@ func TestTextFatal(t *testing.T) { func TestTextValueStyles(t *testing.T) { var buf bytes.Buffer logger := New(&buf) - oldValueStyle := ValueStyle - defer func() { ValueStyle = oldValueStyle }() - ValueStyle = lipgloss.NewStyle().Bold(true) - ValueStyles["key3"] = ValueStyle.Copy().Underline(true) + logger.SetColorProfile(termenv.ANSI256) + lipgloss.SetColorProfile(termenv.ANSI256) + st := DefaultStyles() + st.Value = lipgloss.NewStyle().Bold(true) + st.Values["key3"] = st.Value.Copy().Underline(true) + logger.SetStyles(st) cases := []struct { name string expected string @@ -250,7 +252,7 @@ func TestTextValueStyles(t *testing.T) { }{ { name: "simple message", - expected: fmt.Sprintf("%s info\n", InfoLevelStyle), + expected: fmt.Sprintf("%s info\n", st.Levels[InfoLevel]), msg: "info", kvs: nil, f: logger.Info, @@ -266,9 +268,9 @@ func TestTextValueStyles(t *testing.T) { name: "message with keyvals", expected: fmt.Sprintf( "%s info %s%s%s %s%s%s\n", - InfoLevelStyle, - KeyStyle.Render("key1"), SeparatorStyle.Render(separator), ValueStyle.Render("val1"), - KeyStyle.Render("key2"), SeparatorStyle.Render(separator), ValueStyle.Render("val2"), + st.Levels[InfoLevel], + st.Key.Render("key1"), st.Separator.Render(separator), st.Value.Render("val1"), + st.Key.Render("key2"), st.Separator.Render(separator), st.Value.Render("val2"), ), msg: "info", kvs: []interface{}{"key1", "val1", "key2", "val2"}, @@ -278,10 +280,10 @@ func TestTextValueStyles(t *testing.T) { name: "error message with multiline", expected: fmt.Sprintf( "%s info\n %s%s\n%s%s\n%s%s\n", - ErrorLevelStyle, - KeyStyle.Render("key1"), SeparatorStyle.Render(separator), - SeparatorStyle.Render(indentSeparator), ValueStyle.Render("val1"), - SeparatorStyle.Render(indentSeparator), ValueStyle.Render("val2"), + st.Levels[ErrorLevel], + st.Key.Render("key1"), st.Separator.Render(separator), + st.Separator.Render(indentSeparator), st.Value.Render("val1"), + st.Separator.Render(indentSeparator), st.Value.Render("val2"), ), msg: "info", kvs: []interface{}{"key1", "val1\nval2"}, @@ -291,9 +293,9 @@ func TestTextValueStyles(t *testing.T) { name: "error message with keyvals", expected: fmt.Sprintf( "%s info %s%s%s %s%s%s\n", - ErrorLevelStyle, - KeyStyle.Render("key1"), SeparatorStyle.Render(separator), ValueStyle.Render("val1"), - KeyStyle.Render("key2"), SeparatorStyle.Render(separator), ValueStyle.Render("val2"), + st.Levels[ErrorLevel], + st.Key.Render("key1"), st.Separator.Render(separator), st.Value.Render("val1"), + st.Key.Render("key2"), st.Separator.Render(separator), st.Value.Render("val2"), ), msg: "info", kvs: []interface{}{"key1", "val1", "key2", "val2"}, @@ -303,10 +305,10 @@ func TestTextValueStyles(t *testing.T) { name: "odd number of keyvals", expected: fmt.Sprintf( "%s info %s%s%s %s%s%s %s%s%s\n", - ErrorLevelStyle, - KeyStyle.Render("key1"), SeparatorStyle.Render(separator), ValueStyle.Render("val1"), - KeyStyle.Render("key2"), SeparatorStyle.Render(separator), ValueStyle.Render("val2"), - KeyStyle.Render("key3"), SeparatorStyle.Render(separator), ValueStyles["key3"].Render(`"missing value"`), + st.Levels[ErrorLevel], + st.Key.Render("key1"), st.Separator.Render(separator), st.Value.Render("val1"), + st.Key.Render("key2"), st.Separator.Render(separator), st.Value.Render("val2"), + st.Key.Render("key3"), st.Separator.Render(separator), st.Values["key3"].Render(`"missing value"`), ), msg: "info", kvs: []interface{}{"key1", "val1", "key2", "val2", "key3"}, @@ -316,8 +318,8 @@ func TestTextValueStyles(t *testing.T) { name: "error field", expected: fmt.Sprintf( "%s info %s%s%s\n", - ErrorLevelStyle, - KeyStyle.Render("key1"), SeparatorStyle.Render(separator), ValueStyle.Render(`"error value"`), + st.Levels[ErrorLevel], + st.Key.Render("key1"), st.Separator.Render(separator), st.Value.Render(`"error value"`), ), msg: "info", kvs: []interface{}{"key1", errors.New("error value")}, @@ -327,8 +329,8 @@ func TestTextValueStyles(t *testing.T) { name: "struct field", expected: fmt.Sprintf( "%s info %s%s%s\n", - InfoLevelStyle, - KeyStyle.Render("key1"), SeparatorStyle.Render(separator), ValueStyle.Render("{foo:bar}"), + st.Levels[InfoLevel], + st.Key.Render("key1"), st.Separator.Render(separator), st.Value.Render("{foo:bar}"), ), msg: "info", kvs: []interface{}{"key1", struct{ foo string }{foo: "bar"}}, @@ -338,8 +340,8 @@ func TestTextValueStyles(t *testing.T) { name: "struct field quoted", expected: fmt.Sprintf( "%s info %s%s%s\n", - InfoLevelStyle, - KeyStyle.Render("key1"), SeparatorStyle.Render(separator), ValueStyle.Render(`"{foo:bar baz}"`), + st.Levels[InfoLevel], + st.Key.Render("key1"), st.Separator.Render(separator), st.Value.Render(`"{foo:bar baz}"`), ), msg: "info", kvs: []interface{}{"key1", struct{ foo string }{foo: "bar baz"}}, @@ -349,8 +351,8 @@ func TestTextValueStyles(t *testing.T) { name: "slice of strings", expected: fmt.Sprintf( "%s info %s%s%s\n", - ErrorLevelStyle, - KeyStyle.Render("key1"), SeparatorStyle.Render(separator), ValueStyle.Render(`"[foo bar]"`), + st.Levels[ErrorLevel], + st.Key.Render("key1"), st.Separator.Render(separator), st.Value.Render(`"[foo bar]"`), ), msg: "info", kvs: []interface{}{"key1", []string{"foo", "bar"}}, @@ -360,8 +362,8 @@ func TestTextValueStyles(t *testing.T) { name: "slice of structs", expected: fmt.Sprintf( "%s info %s%s%s\n", - ErrorLevelStyle, - KeyStyle.Render("key1"), SeparatorStyle.Render(separator), ValueStyle.Render(`"[{foo:bar} {foo:baz}]"`), + st.Levels[ErrorLevel], + st.Key.Render("key1"), st.Separator.Render(separator), st.Value.Render(`"[{foo:bar} {foo:baz}]"`), ), msg: "info", kvs: []interface{}{"key1", []struct{ foo string }{{foo: "bar"}, {foo: "baz"}}}, @@ -371,8 +373,8 @@ func TestTextValueStyles(t *testing.T) { name: "slice of errors", expected: fmt.Sprintf( "%s info %s%s%s\n", - ErrorLevelStyle, - KeyStyle.Render("key1"), SeparatorStyle.Render(separator), ValueStyle.Render(`"[error value1 error value2]"`), + st.Levels[ErrorLevel], + st.Key.Render("key1"), st.Separator.Render(separator), st.Value.Render(`"[error value1 error value2]"`), ), msg: "info", kvs: []interface{}{"key1", []error{errors.New("error value1"), errors.New("error value2")}}, @@ -382,8 +384,8 @@ func TestTextValueStyles(t *testing.T) { name: "map of strings", expected: fmt.Sprintf( "%s info %s%s%s\n", - ErrorLevelStyle, - KeyStyle.Render("key1"), SeparatorStyle.Render(separator), ValueStyle.Render(`"map[baz:qux foo:bar]"`), + st.Levels[ErrorLevel], + st.Key.Render("key1"), st.Separator.Render(separator), st.Value.Render(`"map[baz:qux foo:bar]"`), ), msg: "info", kvs: []interface{}{"key1", map[string]string{"foo": "bar", "baz": "qux"}},