diff --git a/context_test.go b/context_test.go index 6ab628b..a448cf9 100644 --- a/context_test.go +++ b/context_test.go @@ -27,5 +27,5 @@ func TestLogContext_fields(t *testing.T) { l = FromContext(ctx) require.NotNil(t, l) l.Debug("test") - require.Equal(t, "DEBUG test foo=bar\n", buf.String()) + require.Equal(t, "DEBU test foo=bar\n", buf.String()) } diff --git a/go.mod b/go.mod index 7ead51f..c034a57 100644 --- a/go.mod +++ b/go.mod @@ -1,10 +1,23 @@ module github.com/charmbracelet/log -go 1.15 +go 1.17 require ( - github.com/charmbracelet/lipgloss v0.6.0 + github.com/charmbracelet/lipgloss v0.7.0 github.com/go-logfmt/logfmt v0.6.0 github.com/mattn/go-isatty v0.0.17 github.com/stretchr/testify v1.8.2 ) + +require ( + github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect + github.com/davecgh/go-spew v1.1.1 // indirect + github.com/lucasb-eyer/go-colorful v1.2.0 // indirect + github.com/mattn/go-runewidth v0.0.14 // indirect + github.com/muesli/reflow v0.3.0 // indirect + github.com/muesli/termenv v0.15.0 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect + github.com/rivo/uniseg v0.2.0 // indirect + golang.org/x/sys v0.6.0 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect +) diff --git a/go.sum b/go.sum index 2d50695..3827b78 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,7 @@ -github.com/charmbracelet/lipgloss v0.6.0 h1:1StyZB9vBSOyuZxQUcUwGr17JmojPNm87inij9N3wJY= -github.com/charmbracelet/lipgloss v0.6.0/go.mod h1:tHh2wr34xcHjC2HCXIlGSG1jaDF0S0atAUvBMP6Ppuk= +github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k= +github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8= +github.com/charmbracelet/lipgloss v0.7.0 h1:cezqy7Ca4XaO4xWQ+uRmsFKyitFnC88GFwce+yCNWos= +github.com/charmbracelet/lipgloss v0.7.0/go.mod h1:uLUJKOkkcdPmrrE60+ZVpe3Fiz0aekJ02eqL2NrpOTs= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -7,16 +9,15 @@ github.com/go-logfmt/logfmt v0.6.0 h1:wGYYu3uicYdqXVgoYbvnkrPVXkuLM1p1ifugDMEdRi github.com/go-logfmt/logfmt v0.6.0/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs= github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY= github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= -github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= github.com/mattn/go-isatty v0.0.17 h1:BTarxUcIeDqL27Mc+vyvdWYSL28zpIhv3RoTdsLMPng= github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= -github.com/mattn/go-runewidth v0.0.10/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk= -github.com/mattn/go-runewidth v0.0.13 h1:lTGmDsbAYt5DmK6OnoV7EuIF1wEIFAcxld6ypU4OSgU= -github.com/mattn/go-runewidth v0.0.13/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= -github.com/muesli/reflow v0.2.1-0.20210115123740-9e1d0d53df68 h1:y1p/ycavWjGT9FnmSjdbWUlLGvcxrY0Rw3ATltrxOhk= -github.com/muesli/reflow v0.2.1-0.20210115123740-9e1d0d53df68/go.mod h1:Xk+z4oIWdQqJzsxyjgl3P22oYZnHdZ8FFTHAQQt5BMQ= -github.com/muesli/termenv v0.11.1-0.20220204035834-5ac8409525e0 h1:STjmj0uFfRryL9fzRA/OupNppeAID6QJYPMavTL7jtY= -github.com/muesli/termenv v0.11.1-0.20220204035834-5ac8409525e0/go.mod h1:Bd5NYQ7pd+SrtBSrSNoBBmXlcY8+Xj4BMJgh8qcZrvs= +github.com/mattn/go-runewidth v0.0.12/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk= +github.com/mattn/go-runewidth v0.0.14 h1:+xnbZSEeDbOIg5/mE6JF0w6n9duR1l3/WmbinWVwUuU= +github.com/mattn/go-runewidth v0.0.14/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= +github.com/muesli/reflow v0.3.0 h1:IFsN6K9NfGtjeggFP+68I4chLZV2yIKsXJFNZ+eWh6s= +github.com/muesli/reflow v0.3.0/go.mod h1:pbwTDkVPibjO2kyvBQRBxTWEEGDGq0FlB1BIKtnHY/8= +github.com/muesli/termenv v0.15.0 h1:ZYfCF4CZGhAA4meilZ5pd7tfUX4QLH4zB7OBie4RMS8= +github.com/muesli/termenv v0.15.0/go.mod h1:HeAQPTzpfs016yGtA4g00CsdYnVLJvxsS4ANqrZs2sQ= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= @@ -29,9 +30,9 @@ github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/ github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8= github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= -golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab h1:2QkjZIsXupsJbJIdSjjUOgWK3aEtzyuh2mPt3l/CkeU= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.6.0 h1:MVltZSvRTcU2ljQOhs94SXPftV6DCNnZViHeQps87pQ= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/logger.go b/logger.go index cefa882..fff985e 100644 --- a/logger.go +++ b/logger.go @@ -10,6 +10,8 @@ import ( "strings" "sync" "sync/atomic" + + "github.com/charmbracelet/lipgloss" ) var ( @@ -25,6 +27,7 @@ type Logger struct { w io.Writer b bytes.Buffer mu *sync.RWMutex + re *lipgloss.Renderer isDiscard uint32 @@ -35,7 +38,6 @@ type Logger struct { callerOffset int formatter Formatter - noStyles bool reportCaller bool reportTimestamp bool @@ -242,10 +244,7 @@ func (l *Logger) SetOutput(w io.Writer) { isDiscard = 1 } atomic.StoreUint32(&l.isDiscard, isDiscard) - if !isTerminal(w) { - // This only affects the TextFormatter - l.noStyles = true - } + l.re = lipgloss.NewRenderer(w) } // SetFormatter sets the formatter. diff --git a/pkg_test.go b/pkg_test.go index 238afef..0b49867 100644 --- a/pkg_test.go +++ b/pkg_test.go @@ -36,7 +36,7 @@ func TestGlobal(t *testing.T) { }, { name: "default logger error with timestamp", - expected: "0001/01/01 00:00:00 ERROR info\n", + expected: "0001/01/01 00:00:00 ERRO info\n", msg: "info", kvs: nil, f: Error, diff --git a/stdlog_test.go b/stdlog_test.go index b6b8d5c..6cf73a7 100644 --- a/stdlog_test.go +++ b/stdlog_test.go @@ -32,7 +32,7 @@ func TestStdLog(t *testing.T) { }, { name: "error level", - expected: "ERROR coffee\n", + expected: "ERRO coffee\n", f: func(l *log.Logger) { l.Print("ERROR coffee") }, }, } @@ -67,7 +67,7 @@ func TestStdLog_forceLevel(t *testing.T) { }, { name: "error", - expected: "ERROR coffee\n", + expected: "ERRO coffee\n", level: ErrorLevel, }, } @@ -104,7 +104,7 @@ func TestStdLog_writer(t *testing.T) { }, { name: "error", - expected: fmt.Sprintf("ERROR coffee\n", filepath.Base(file), line+27), + expected: fmt.Sprintf("ERRO coffee\n", filepath.Base(file), line+27), level: ErrorLevel, }, } diff --git a/styles.go b/styles.go index 8499def..b2bdf1f 100644 --- a/styles.go +++ b/styles.go @@ -1,6 +1,10 @@ package log -import "github.com/charmbracelet/lipgloss" +import ( + "strings" + + "github.com/charmbracelet/lipgloss" +) var ( // TimestampStyle is the style for timestamps. @@ -26,7 +30,7 @@ var ( // DebugLevel is the style for debug level. DebugLevelStyle = lipgloss.NewStyle(). - SetString("DEBUG"). + SetString(strings.ToUpper(DebugLevel.String())). Bold(true). MaxWidth(4). Foreground(lipgloss.AdaptiveColor{ @@ -36,7 +40,7 @@ var ( // InfoLevel is the style for info level. InfoLevelStyle = lipgloss.NewStyle(). - SetString("INFO"). + SetString(strings.ToUpper(InfoLevel.String())). Bold(true). MaxWidth(4). Foreground(lipgloss.AdaptiveColor{ @@ -46,7 +50,7 @@ var ( // WarnLevel is the style for warn level. WarnLevelStyle = lipgloss.NewStyle(). - SetString("WARN"). + SetString(strings.ToUpper(WarnLevel.String())). Bold(true). MaxWidth(4). Foreground(lipgloss.AdaptiveColor{ @@ -56,7 +60,7 @@ var ( // ErrorLevel is the style for error level. ErrorLevelStyle = lipgloss.NewStyle(). - SetString("ERROR"). + SetString(strings.ToUpper(ErrorLevel.String())). Bold(true). MaxWidth(4). Foreground(lipgloss.AdaptiveColor{ @@ -66,7 +70,7 @@ var ( // FatalLevel is the style for error level. FatalLevelStyle = lipgloss.NewStyle(). - SetString("FATAL"). + SetString(strings.ToUpper(FatalLevel.String())). Bold(true). MaxWidth(4). Foreground(lipgloss.AdaptiveColor{ diff --git a/text.go b/text.go index 943d0f7..4c0eb48 100644 --- a/text.go +++ b/text.go @@ -23,12 +23,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 !l.noStyles { - if valueStyle, ok := ValueStyles[key]; ok { - val = valueStyle.Render(val) - } else { - val = ValueStyle.Render(val) - } + if valueStyle, ok := ValueStyles[key]; ok { + val = valueStyle.Renderer(l.re).Render(val) + } else { + val = ValueStyle.Renderer(l.re).Render(val) } _, _ = w.Write([]byte(val)) if newline { @@ -40,9 +38,7 @@ func (l *Logger) writeIndent(w io.Writer, str string, indent string, newline boo _, _ = w.Write([]byte(indent)) val := escapeStringForOutput(str[:nl], false) - if !l.noStyles { - val = ValueStyle.Render(val) - } + val = ValueStyle.Renderer(l.re).Render(val) _, _ = w.Write([]byte(val)) _, _ = w.Write([]byte{'\n'}) str = str[nl+1:] @@ -152,53 +148,40 @@ func (l *Logger) textFormatter(keyvals ...interface{}) { case TimestampKey: if t, ok := keyvals[i+1].(time.Time); ok { ts := t.Format(l.timeFormat) - if !l.noStyles { - ts = TimestampStyle.Render(ts) - } + ts = TimestampStyle.Renderer(l.re).Render(ts) l.b.WriteString(ts) l.b.WriteByte(' ') } case LevelKey: if level, ok := keyvals[i+1].(Level); ok { - lvl := strings.ToUpper(level.String()) - if !l.noStyles { - lvl = levelStyle(level).String() - } + lvl := levelStyle(level).Renderer(l.re).String() l.b.WriteString(lvl) l.b.WriteByte(' ') } case CallerKey: if caller, ok := keyvals[i+1].(string); ok { caller = fmt.Sprintf("<%s>", caller) - if !l.noStyles { - caller = CallerStyle.Render(caller) - } + caller = CallerStyle.Renderer(l.re).Render(caller) l.b.WriteString(caller) l.b.WriteByte(' ') } case PrefixKey: if prefix, ok := keyvals[i+1].(string); ok { - if !l.noStyles { - prefix = PrefixStyle.Render(prefix) - } + prefix = PrefixStyle.Renderer(l.re).Render(prefix) l.b.WriteString(prefix) l.b.WriteByte(' ') } case MessageKey: if msg := keyvals[i+1]; msg != nil { m := fmt.Sprint(msg) - if !l.noStyles { - m = MessageStyle.Render(m) - } + m = MessageStyle.Renderer(l.re).Render(m) l.b.WriteString(m) } default: sep := separator indentSep := indentSeparator - if !l.noStyles { - sep = SeparatorStyle.Render(sep) - indentSep = SeparatorStyle.Render(indentSep) - } + sep = SeparatorStyle.Renderer(l.re).Render(sep) + indentSep = SeparatorStyle.Renderer(l.re).Render(indentSep) moreKeys := i < len(keyvals)-2 key := fmt.Sprint(keyvals[i]) val := fmt.Sprintf("%+v", keyvals[i+1]) @@ -214,12 +197,10 @@ func (l *Logger) textFormatter(keyvals ...interface{}) { if vs, ok := ValueStyles[actualKey]; ok { valueStyle = vs } - if !l.noStyles { - if keyStyle, ok := KeyStyles[key]; ok { - key = keyStyle.Render(key) - } else { - key = KeyStyle.Render(key) - } + if keyStyle, ok := KeyStyles[key]; ok { + key = keyStyle.Renderer(l.re).Render(key) + } else { + key = KeyStyle.Renderer(l.re).Render(key) } // Values may contain multiple lines, and that format @@ -242,17 +223,10 @@ func (l *Logger) textFormatter(keyvals ...interface{}) { l.b.WriteByte(' ') l.b.WriteString(key) l.b.WriteString(sep) - if !l.noStyles { - l.b.WriteString(valueStyle.Render(fmt.Sprintf(`"%s"`, - escapeStringForOutput(val, true)))) - } else { - l.b.WriteString(fmt.Sprintf(`"%s"`, - escapeStringForOutput(val, true))) - } + l.b.WriteString(valueStyle.Renderer(l.re).Render(fmt.Sprintf(`"%s"`, + escapeStringForOutput(val, true)))) } else { - if !l.noStyles { - val = valueStyle.Render(val) - } + val = valueStyle.Renderer(l.re).Render(val) l.b.WriteByte(' ') l.b.WriteString(key) l.b.WriteString(sep) diff --git a/text_test.go b/text_test.go index f51e14a..d1590e5 100644 --- a/text_test.go +++ b/text_test.go @@ -122,70 +122,70 @@ func TestTextLogger(t *testing.T) { }, { name: "error message with keyvals", - expected: "ERROR info key1=val1 key2=val2\n", + expected: "ERRO info key1=val1 key2=val2\n", msg: "info", kvs: []interface{}{"key1", "val1", "key2", "val2"}, f: logger.Error, }, { name: "error message with multiline", - expected: "ERROR info\n key1=\n │ val1\n │ val2\n", + expected: "ERRO info\n key1=\n │ val1\n │ val2\n", msg: "info", kvs: []interface{}{"key1", "val1\nval2"}, f: logger.Error, }, { name: "odd number of keyvals", - expected: "ERROR info key1=val1 key2=val2 key3=\"missing value\"\n", + expected: "ERRO info key1=val1 key2=val2 key3=\"missing value\"\n", msg: "info", kvs: []interface{}{"key1", "val1", "key2", "val2", "key3"}, f: logger.Error, }, { name: "error field", - expected: "ERROR info key1=\"error value\"\n", + expected: "ERRO info key1=\"error value\"\n", msg: "info", kvs: []interface{}{"key1", errors.New("error value")}, f: logger.Error, }, { name: "struct field", - expected: "ERROR info key1={foo:bar}\n", + expected: "ERRO info key1={foo:bar}\n", msg: "info", kvs: []interface{}{"key1", struct{ foo string }{foo: "bar"}}, f: logger.Error, }, { name: "struct field quoted", - expected: "ERROR info key1=\"{foo:bar baz}\"\n", + expected: "ERRO info key1=\"{foo:bar baz}\"\n", msg: "info", kvs: []interface{}{"key1", struct{ foo string }{foo: "bar baz"}}, f: logger.Error, }, { name: "slice of strings", - expected: "ERROR info key1=\"[foo bar]\"\n", + expected: "ERRO info key1=\"[foo bar]\"\n", msg: "info", kvs: []interface{}{"key1", []string{"foo", "bar"}}, f: logger.Error, }, { name: "slice of structs", - expected: "ERROR info key1=\"[{foo:bar} {foo:baz}]\"\n", + expected: "ERRO info key1=\"[{foo:bar} {foo:baz}]\"\n", msg: "info", kvs: []interface{}{"key1", []struct{ foo string }{{foo: "bar"}, {foo: "baz"}}}, f: logger.Error, }, { name: "slice of errors", - expected: "ERROR info key1=\"[error value1 error value2]\"\n", + expected: "ERRO info key1=\"[error value1 error value2]\"\n", msg: "info", kvs: []interface{}{"key1", []error{errors.New("error value1"), errors.New("error value2")}}, f: logger.Error, }, { name: "map of strings", - expected: "ERROR info key1=\"map[baz:qux foo:bar]\"\n", + expected: "ERRO info key1=\"map[baz:qux foo:bar]\"\n", msg: "info", kvs: []interface{}{"key1", map[string]string{"foo": "bar", "baz": "qux"}}, f: logger.Error, @@ -235,7 +235,6 @@ func TestTextFatal(t *testing.T) { func TestTextValueStyles(t *testing.T) { var buf bytes.Buffer logger := New(&buf) - logger.noStyles = false oldValueStyle := ValueStyle defer func() { ValueStyle = oldValueStyle }() ValueStyle = lipgloss.NewStyle().Bold(true)