Skip to content

Commit e92a06b

Browse files
committed
feat: add color to json output
1 parent fb820d2 commit e92a06b

2 files changed

Lines changed: 105 additions & 41 deletions

File tree

‎json.go‎

Lines changed: 91 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,12 @@ import (
55
"encoding/json"
66
"fmt"
77
"time"
8+
9+
"github.com/charmbracelet/lipgloss"
810
)
911

1012
func (l *Logger) jsonFormatter(keyvals ...interface{}) {
11-
jw := &jsonWriter{w: &l.b}
13+
jw := &jsonWriter{w: &l.b, r: l.re, s: l.styles.Separator}
1214
jw.start()
1315

1416
i := 0
@@ -33,87 +35,114 @@ func (l *Logger) jsonFormatterRoot(jw *jsonWriter, key, value any) {
3335
switch key {
3436
case TimestampKey:
3537
if t, ok := value.(time.Time); ok {
36-
jw.objectItem(TimestampKey, t.Format(l.timeFormat))
38+
jw.objectItem(l.styles.Key, TimestampKey, l.styles.Timestamp, t.Format(l.timeFormat))
3739
}
3840
case LevelKey:
3941
if level, ok := value.(Level); ok {
40-
jw.objectItem(LevelKey, level.String())
42+
ls, ok := l.styles.Levels[level]
43+
if ok {
44+
jw.objectItem(l.styles.Key, LevelKey, ls, level.String())
45+
}
4146
}
4247
case CallerKey:
4348
if caller, ok := value.(string); ok {
44-
jw.objectItem(CallerKey, caller)
49+
jw.objectItem(l.styles.Key, CallerKey, l.styles.Caller, caller)
4550
}
4651
case PrefixKey:
4752
if prefix, ok := value.(string); ok {
48-
jw.objectItem(PrefixKey, prefix)
53+
jw.objectItem(l.styles.Key, PrefixKey, l.styles.Prefix, prefix)
4954
}
5055
case MessageKey:
5156
if msg := value; msg != nil {
52-
jw.objectItem(MessageKey, fmt.Sprint(msg))
57+
jw.objectItem(l.styles.Key, MessageKey, l.styles.Message, fmt.Sprint(msg))
5358
}
5459
default:
55-
l.jsonFormatterItem(jw, key, value)
60+
l.jsonFormatterItem(jw, 0, l.styles.Key, key, l.styles.Value, value)
5661
}
5762
}
5863

59-
func (l *Logger) jsonFormatterItem(jw *jsonWriter, key, value any) {
60-
switch k := key.(type) {
64+
func (l *Logger) jsonFormatterItem(
65+
jw *jsonWriter, d int, ks lipgloss.Style, anyKey any, vs lipgloss.Style, value any,
66+
) {
67+
var key string
68+
switch k := anyKey.(type) {
6169
case fmt.Stringer:
62-
jw.objectKey(k.String())
70+
key = k.String()
6371
case error:
64-
jw.objectKey(k.Error())
72+
key = k.Error()
6573
default:
66-
jw.objectKey(fmt.Sprint(k))
74+
key = fmt.Sprint(k)
6775
}
76+
77+
// override styles based on root key
78+
if d == 0 {
79+
if s, ok := l.styles.Keys[key]; ok {
80+
ks = s
81+
}
82+
if s, ok := l.styles.Values[key]; ok {
83+
vs = s
84+
}
85+
}
86+
87+
jw.objectKey(ks, key)
88+
6889
switch v := value.(type) {
6990
case error:
70-
jw.objectValue(v.Error())
91+
jw.objectValue(vs, v.Error())
7192
case slogLogValuer:
72-
l.writeSlogValue(jw, v.LogValue())
93+
l.writeSlogValue(jw, d, ks, vs, v.LogValue())
7394
case slogValue:
74-
l.writeSlogValue(jw, v.Resolve())
95+
l.writeSlogValue(jw, d, ks, vs, v.Resolve())
7596
case fmt.Stringer:
76-
jw.objectValue(v.String())
97+
jw.objectValue(vs, v.String())
7798
default:
78-
jw.objectValue(v)
99+
jw.objectValue(vs, v)
79100
}
80101
}
81102

82-
func (l *Logger) writeSlogValue(jw *jsonWriter, v slogValue) {
103+
func (l *Logger) writeSlogValue(jw *jsonWriter, depth int, ks, vs lipgloss.Style, v slogValue) {
83104
switch v.Kind() {
84105
case slogKindGroup:
85106
jw.start()
86107
for _, attr := range v.Group() {
87-
l.jsonFormatterItem(jw, attr.Key, attr.Value)
108+
l.jsonFormatterItem(jw, depth+1, ks, attr.Key, vs, attr.Value)
88109
}
89110
jw.end()
90111
default:
91-
jw.objectValue(v.Any())
112+
jw.objectValue(vs, v.Any())
92113
}
93114
}
94115

95116
type jsonWriter struct {
96117
w *bytes.Buffer
118+
r *lipgloss.Renderer
119+
s lipgloss.Style
97120
d int
98121
}
99122

100123
func (w *jsonWriter) start() {
101-
w.w.WriteRune('{')
124+
objectStart := w.s.Renderer(w.r).Render("{")
125+
w.w.WriteString(objectStart)
102126
w.d = 0
103127
}
104128

105129
func (w *jsonWriter) end() {
106-
w.w.WriteRune('}')
130+
objectEnd := w.s.Renderer(w.r).Render("}")
131+
w.w.WriteString(objectEnd)
107132
}
108133

109-
func (w *jsonWriter) objectItem(key string, value any) {
110-
w.objectKey(key)
111-
w.objectValue(value)
134+
func (w *jsonWriter) objectItem(
135+
ks lipgloss.Style, key string,
136+
vs lipgloss.Style, value any,
137+
) {
138+
w.objectKey(ks, key)
139+
w.objectValue(vs, value)
112140
}
113141

114-
func (w *jsonWriter) objectKey(key string) {
142+
func (w *jsonWriter) objectKey(s lipgloss.Style, key string) {
115143
if w.d > 0 {
116-
w.w.WriteRune(',')
144+
itemSep := w.s.Renderer(w.r).Render(",")
145+
w.w.WriteString(itemSep)
117146
}
118147
w.d++
119148

@@ -123,16 +152,49 @@ func (w *jsonWriter) objectKey(key string) {
123152
w.w.Truncate(pos)
124153
w.w.WriteString(`"invalid key"`)
125154
}
126-
w.w.WriteRune(':')
155+
156+
// re-apply value with style
157+
w.renderStyle(s, pos)
158+
159+
valSep := w.s.Renderer(w.r).Render(`:`)
160+
w.w.WriteString(valSep)
127161
}
128162

129-
func (w *jsonWriter) objectValue(value any) {
163+
func (w *jsonWriter) objectValue(s lipgloss.Style, value any) {
130164
pos := w.w.Len()
131165
err := w.writeEncoded(value)
132166
if err != nil {
133167
w.w.Truncate(pos)
134168
w.w.WriteString(`"invalid value"`)
135169
}
170+
171+
// re-apply value with style
172+
w.renderStyle(s, pos)
173+
}
174+
175+
// renderStyle applies the given style to the string at the given position.
176+
func (w *jsonWriter) renderStyle(st lipgloss.Style, pos int) {
177+
s := w.w.String()[pos:]
178+
179+
// manually apply quotes
180+
sep := ""
181+
if len(s) > 2 && s[0] == '"' && s[len(s)-1] == '"' {
182+
s = s[1 : len(s)-1] // apply style within quotes
183+
sep = w.s.Renderer(w.r).Render(`"`)
184+
} else if st.String() != "" {
185+
sep = w.s.Renderer(w.r).Render(`"`)
186+
}
187+
188+
// render with style
189+
s = st.Renderer(w.r).Render(s)
190+
191+
// rewind
192+
w.w.Truncate(pos)
193+
194+
// re-apply with colors
195+
w.w.WriteString(sep)
196+
w.w.WriteString(s)
197+
w.w.WriteString(sep)
136198
}
137199

138200
func (w *jsonWriter) writeEncoded(v any) error {

‎json_test.go‎

Lines changed: 14 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import (
88
"runtime"
99
"testing"
1010

11+
"github.com/charmbracelet/lipgloss"
1112
"github.com/stretchr/testify/require"
1213
)
1314

@@ -200,6 +201,7 @@ func TestJsonCustomKey(t *testing.T) {
200201
}
201202

202203
func TestJsonWriter(t *testing.T) {
204+
noStyle := lipgloss.NewStyle()
203205
testCases := []struct {
204206
name string
205207
fn func(w *jsonWriter)
@@ -209,7 +211,7 @@ func TestJsonWriter(t *testing.T) {
209211
"string",
210212
func(w *jsonWriter) {
211213
w.start()
212-
w.objectItem("a", "value")
214+
w.objectItem(noStyle, "a", noStyle, "value")
213215
w.end()
214216
},
215217
`{"a":"value"}`,
@@ -218,7 +220,7 @@ func TestJsonWriter(t *testing.T) {
218220
"int",
219221
func(w *jsonWriter) {
220222
w.start()
221-
w.objectItem("a", 123)
223+
w.objectItem(noStyle, "a", noStyle, 123)
222224
w.end()
223225
},
224226
`{"a":123}`,
@@ -227,7 +229,7 @@ func TestJsonWriter(t *testing.T) {
227229
"bytes",
228230
func(w *jsonWriter) {
229231
w.start()
230-
w.objectItem("b", []byte{0x0, 0x1})
232+
w.objectItem(noStyle, "b", noStyle, []byte{0x0, 0x1})
231233
w.end()
232234
},
233235
`{"b":"AAE="}`,
@@ -244,8 +246,8 @@ func TestJsonWriter(t *testing.T) {
244246
"multiple in asc order",
245247
func(w *jsonWriter) {
246248
w.start()
247-
w.objectItem("a", "value")
248-
w.objectItem("b", "some-other")
249+
w.objectItem(noStyle, "a", noStyle, "value")
250+
w.objectItem(noStyle, "b", noStyle, "some-other")
249251
w.end()
250252
},
251253
`{"a":"value","b":"some-other"}`,
@@ -254,8 +256,8 @@ func TestJsonWriter(t *testing.T) {
254256
"multiple in desc order",
255257
func(w *jsonWriter) {
256258
w.start()
257-
w.objectItem("b", "some-other")
258-
w.objectItem("a", "value")
259+
w.objectItem(noStyle, "b", noStyle, "some-other")
260+
w.objectItem(noStyle, "a", noStyle, "value")
259261
w.end()
260262
},
261263
`{"b":"some-other","a":"value"}`,
@@ -264,7 +266,7 @@ func TestJsonWriter(t *testing.T) {
264266
"depth",
265267
func(w *jsonWriter) {
266268
w.start()
267-
w.objectItem("a", map[string]int{"b": 123})
269+
w.objectItem(noStyle, "a", noStyle, map[string]int{"b": 123})
268270
w.end()
269271
},
270272
`{"a":{"b":123}}`,
@@ -273,7 +275,7 @@ func TestJsonWriter(t *testing.T) {
273275
"key contains reserved",
274276
func(w *jsonWriter) {
275277
w.start()
276-
w.objectItem("a:\"b", "value")
278+
w.objectItem(noStyle, "a:\"b", noStyle, "value")
277279
w.end()
278280
},
279281
`{"a:\"b":"value"}`,
@@ -282,7 +284,7 @@ func TestJsonWriter(t *testing.T) {
282284
"pointer",
283285
func(w *jsonWriter) {
284286
w.start()
285-
w.objectItem("a", ptr("pointer"))
287+
w.objectItem(noStyle, "a", noStyle, ptr("pointer"))
286288
w.end()
287289
},
288290
`{"a":"pointer"}`,
@@ -291,7 +293,7 @@ func TestJsonWriter(t *testing.T) {
291293
"double-pointer",
292294
func(w *jsonWriter) {
293295
w.start()
294-
w.objectItem("a", ptr(ptr("pointer")))
296+
w.objectItem(noStyle, "a", noStyle, ptr(ptr("pointer")))
295297
w.end()
296298
},
297299
`{"a":"pointer"}`,
@@ -300,7 +302,7 @@ func TestJsonWriter(t *testing.T) {
300302
"invalid",
301303
func(w *jsonWriter) {
302304
w.start()
303-
w.objectItem("a", invalidJSON{})
305+
w.objectItem(noStyle, "a", noStyle, invalidJSON{})
304306
w.end()
305307
},
306308
`{"a":"invalid value"}`,

0 commit comments

Comments
 (0)