• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

hamba / logger / 24274249029

11 Apr 2026 04:04AM UTC coverage: 79.483% (-0.6%) from 80.06%
24274249029

Pull #127

github

web-flow
Merge 4616282bf into 8f10bc7c8
Pull Request #127: feat: minor improvements

14 of 15 new or added lines in 1 file covered. (93.33%)

1 existing line in 1 file now uncovered.

523 of 658 relevant lines covered (79.48%)

24.9 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

92.53
/format.go
1
package logger
2

3
import (
4
        "fmt"
5
        "strings"
6
        "time"
7
        "unicode/utf8"
8

9
        "github.com/hamba/logger/v2/internal/bytes"
10
)
11

12
const (
13
        // TimestampKey is the key used for timestamps.
14
        TimestampKey = "ts"
15
        // LevelKey is the key used for message levels.
16
        LevelKey = "lvl"
17
        // MessageKey is the key used for message descriptions.
18
        MessageKey = "msg"
19
)
20

21
// Formatter represents a log message formatter.
22
type Formatter interface {
23
        WriteMessage(buf *bytes.Buffer, ts time.Time, lvl Level, msg string)
24
        AppendBeginMarker(buf *bytes.Buffer)
25
        AppendEndMarker(buf *bytes.Buffer)
26
        AppendLineBreak(buf *bytes.Buffer)
27
        AppendArrayStart(buf *bytes.Buffer)
28
        AppendArraySep(buf *bytes.Buffer)
29
        AppendArrayEnd(buf *bytes.Buffer)
30
        AppendKey(buf *bytes.Buffer, key string)
31
        AppendString(buf *bytes.Buffer, s string)
32
        AppendBool(buf *bytes.Buffer, b bool)
33
        AppendInt(buf *bytes.Buffer, i int64)
34
        AppendUint(buf *bytes.Buffer, i uint64)
35
        AppendFloat(buf *bytes.Buffer, f float64)
36
        AppendTime(buf *bytes.Buffer, t time.Time)
37
        AppendDuration(buf *bytes.Buffer, d time.Duration)
38
        AppendInterface(buf *bytes.Buffer, v any)
39
}
40

41
type json struct{}
42

43
// JSONFormat formats a log line in json format.
44
func JSONFormat() Formatter {
34✔
45
        return &json{}
34✔
46
}
34✔
47

48
func (j *json) WriteMessage(buf *bytes.Buffer, ts time.Time, lvl Level, msg string) {
2✔
49
        if !ts.IsZero() {
4✔
50
                buf.WriteString("\"" + TimestampKey + "\":")
2✔
51
                j.AppendTime(buf, ts)
2✔
52
                buf.WriteString(",\"" + LevelKey + "\":\"")
2✔
53
        } else {
2✔
NEW
54
                buf.WriteString("\"" + LevelKey + "\":\"")
×
UNCOV
55
        }
×
56
        buf.WriteString(lvl.String())
2✔
57
        buf.WriteString("\",\"" + MessageKey + "\":")
2✔
58
        appendString(buf, msg, true)
2✔
59
}
60

61
func (j *json) AppendBeginMarker(buf *bytes.Buffer) {
2✔
62
        buf.WriteString("{")
2✔
63
}
2✔
64

65
func (j *json) AppendEndMarker(buf *bytes.Buffer) {
2✔
66
        buf.WriteString("}")
2✔
67
}
2✔
68

69
func (j *json) AppendLineBreak(buf *bytes.Buffer) {
2✔
70
        buf.WriteString("\n")
2✔
71
}
2✔
72

73
func (j *json) AppendArrayStart(buf *bytes.Buffer) {
2✔
74
        buf.WriteByte('[')
2✔
75
}
2✔
76

77
func (j *json) AppendArraySep(buf *bytes.Buffer) {
2✔
78
        buf.WriteByte(',')
2✔
79
}
2✔
80

81
func (j *json) AppendArrayEnd(buf *bytes.Buffer) {
2✔
82
        buf.WriteByte(']')
2✔
83
}
2✔
84

85
func (j *json) AppendKey(buf *bytes.Buffer, key string) {
2✔
86
        buf.WriteString(`,"`)
2✔
87
        buf.WriteString(key)
2✔
88
        buf.WriteString(`":`)
2✔
89
}
2✔
90

91
func (j *json) AppendString(buf *bytes.Buffer, s string) {
18✔
92
        appendString(buf, s, true)
18✔
93
}
18✔
94

95
func (j *json) AppendBool(buf *bytes.Buffer, b bool) {
4✔
96
        buf.AppendBool(b)
4✔
97
}
4✔
98

99
func (j *json) AppendInt(buf *bytes.Buffer, i int64) {
2✔
100
        buf.AppendInt(i)
2✔
101
}
2✔
102

103
func (j *json) AppendUint(buf *bytes.Buffer, i uint64) {
2✔
104
        buf.AppendUint(i)
2✔
105
}
2✔
106

107
func (j *json) AppendFloat(buf *bytes.Buffer, f float64) {
2✔
108
        buf.AppendFloat(f, 'g', -1, 64)
2✔
109
}
2✔
110

111
func (j *json) AppendTime(buf *bytes.Buffer, t time.Time) {
6✔
112
        switch TimeFormat {
6✔
113
        case TimeFormatUnix:
4✔
114
                buf.AppendInt(t.Unix())
4✔
115
        default:
2✔
116
                buf.WriteByte('"')
2✔
117
                buf.AppendTime(t, TimeFormat)
2✔
118
                buf.WriteByte('"')
2✔
119
        }
120
}
121

122
func (j *json) AppendDuration(buf *bytes.Buffer, d time.Duration) {
2✔
123
        buf.WriteByte('"')
2✔
124
        buf.AppendDuration(d)
2✔
125
        buf.WriteByte('"')
2✔
126
}
2✔
127

128
func (j *json) AppendInterface(buf *bytes.Buffer, v any) {
4✔
129
        if v == nil {
6✔
130
                buf.WriteString("null")
2✔
131
                return
2✔
132
        }
2✔
133

134
        j.AppendString(buf, fmt.Sprintf("%+v", v))
2✔
135
}
136

137
type logfmt struct{}
138

139
// LogfmtFormat formats a log line in logfmt format.
140
func LogfmtFormat() Formatter {
58✔
141
        return &logfmt{}
58✔
142
}
58✔
143

144
func (l *logfmt) needsQuote(s string) bool {
72✔
145
        for i := range len(s) {
678✔
146
                b := s[i]
606✔
147
                if b <= ' ' || b == '=' || b == '"' {
634✔
148
                        return true
28✔
149
                }
28✔
150
        }
151
        return false
44✔
152
}
153

154
func (l *logfmt) WriteMessage(buf *bytes.Buffer, ts time.Time, lvl Level, msg string) {
22✔
155
        if !ts.IsZero() {
26✔
156
                buf.WriteString(TimestampKey + "=")
4✔
157
                l.AppendTime(buf, ts)
4✔
158
                buf.WriteString(" " + LevelKey + "=")
4✔
159
        } else {
22✔
160
                buf.WriteString(LevelKey + "=")
18✔
161
        }
18✔
162
        buf.WriteString(lvl.String())
22✔
163
        buf.WriteString(" " + MessageKey + "=")
22✔
164
        appendString(buf, msg, l.needsQuote(msg))
22✔
165
}
166

167
func (l *logfmt) AppendBeginMarker(*bytes.Buffer) {}
22✔
168

169
func (l *logfmt) AppendEndMarker(*bytes.Buffer) {}
22✔
170

171
func (l *logfmt) AppendLineBreak(buf *bytes.Buffer) {
22✔
172
        buf.WriteByte('\n')
22✔
173
}
22✔
174

175
func (l *logfmt) AppendArrayStart(_ *bytes.Buffer) {}
8✔
176

177
func (l *logfmt) AppendArraySep(buf *bytes.Buffer) {
16✔
178
        buf.WriteByte(',')
16✔
179
}
16✔
180

181
func (l *logfmt) AppendArrayEnd(_ *bytes.Buffer) {}
8✔
182

183
func (l *logfmt) AppendKey(buf *bytes.Buffer, key string) {
68✔
184
        buf.WriteByte(' ')
68✔
185
        buf.WriteString(key)
68✔
186
        buf.WriteByte('=')
68✔
187
}
68✔
188

189
func (l *logfmt) AppendString(buf *bytes.Buffer, s string) {
50✔
190
        appendString(buf, s, l.needsQuote(s))
50✔
191
}
50✔
192

193
func (l *logfmt) AppendBool(buf *bytes.Buffer, b bool) {
6✔
194
        buf.AppendBool(b)
6✔
195
}
6✔
196

197
func (l *logfmt) AppendInt(buf *bytes.Buffer, i int64) {
30✔
198
        buf.AppendInt(i)
30✔
199
}
30✔
200

201
func (l *logfmt) AppendUint(buf *bytes.Buffer, i uint64) {
12✔
202
        buf.AppendUint(i)
12✔
203
}
12✔
204

205
func (l *logfmt) AppendFloat(buf *bytes.Buffer, f float64) {
6✔
206
        buf.AppendFloat(f, 'f', 3, 64)
6✔
207
}
6✔
208

209
func (l *logfmt) AppendTime(buf *bytes.Buffer, t time.Time) {
10✔
210
        switch TimeFormat {
10✔
211
        case TimeFormatUnix:
8✔
212
                buf.AppendInt(t.Unix())
8✔
213
        default:
2✔
214
                buf.AppendTime(t, TimeFormat)
2✔
215
        }
216
}
217

218
func (l *logfmt) AppendDuration(buf *bytes.Buffer, d time.Duration) {
4✔
219
        buf.AppendDuration(d)
4✔
220
}
4✔
221

222
func (l *logfmt) AppendInterface(buf *bytes.Buffer, v any) {
6✔
223
        if v == nil {
8✔
224
                return
2✔
225
        }
2✔
226

227
        l.AppendString(buf, fmt.Sprintf("%+v", v))
4✔
228
}
229

230
const (
231
        // Foreground text colors.
232
        _ = iota + 30
233
        colorRed
234
        colorGreen
235
        colorYellow
236
        colorBlue
237
        _
238
        colorCyan
239
        colorWhite
240

241
        colorReset = 0
242
        colorBold  = 1
243
)
244

245
var noColor = newColor(colorReset)
246

247
type color []int
248

249
func newColor(attr ...int) color {
10✔
250
        return attr
10✔
251
}
10✔
252

253
func (c color) Write(buf *bytes.Buffer) {
12✔
254
        buf.WriteByte('\x1b')
12✔
255
        buf.WriteByte('[')
12✔
256
        for i := range c {
24✔
257
                if i > 0 {
12✔
258
                        buf.WriteByte(';')
×
259
                }
×
260
                buf.AppendInt(int64(c[i]))
12✔
261
        }
262
        buf.WriteByte('m')
12✔
263
}
264

265
func withColor(c color, buf *bytes.Buffer, fn func()) {
6✔
266
        c.Write(buf)
6✔
267
        fn()
6✔
268
        noColor.Write(buf)
6✔
269
}
6✔
270

271
type console struct{}
272

273
// ConsoleFormat formats a log line in a console format.
274
func ConsoleFormat() Formatter {
32✔
275
        return &console{}
32✔
276
}
32✔
277

278
func (c *console) lvlColor(lvl Level) color {
2✔
279
        switch lvl {
2✔
280
        case Crit:
×
281
                return newColor(colorRed, colorBold)
×
282
        case Error:
2✔
283
                return newColor(colorRed)
2✔
284
        case Warn:
×
285
                return newColor(colorYellow)
×
286
        case Info:
×
287
                return newColor(colorGreen)
×
288
        case Debug:
×
289
                return newColor(colorBlue)
×
290
        }
291
        return newColor(colorWhite)
×
292
}
293

294
func (c *console) WriteMessage(buf *bytes.Buffer, ts time.Time, lvl Level, msg string) {
2✔
295
        if !ts.IsZero() {
4✔
296
                withColor(newColor(colorBlue), buf, func() {
4✔
297
                        c.AppendTime(buf, ts)
2✔
298
                })
2✔
299
                buf.WriteByte(' ')
2✔
300
        }
301
        withColor(c.lvlColor(lvl), buf, func() {
4✔
302
                buf.WriteString(strings.ToUpper(lvl.String()))
2✔
303
        })
2✔
304
        buf.WriteByte(' ')
2✔
305
        appendString(buf, msg, false)
2✔
306
}
307

308
func (c *console) AppendBeginMarker(*bytes.Buffer) {}
2✔
309

310
func (c *console) AppendEndMarker(*bytes.Buffer) {}
2✔
311

312
func (c *console) AppendLineBreak(buf *bytes.Buffer) {
2✔
313
        buf.WriteByte('\n')
2✔
314
}
2✔
315

316
func (c *console) AppendArrayStart(_ *bytes.Buffer) {}
2✔
317

318
func (c *console) AppendArraySep(buf *bytes.Buffer) {
2✔
319
        buf.WriteByte(',')
2✔
320
}
2✔
321

322
func (c *console) AppendArrayEnd(_ *bytes.Buffer) {}
2✔
323

324
func (c *console) AppendKey(buf *bytes.Buffer, key string) {
2✔
325
        buf.WriteByte(' ')
2✔
326

2✔
327
        col := newColor(colorCyan)
2✔
328
        if strings.HasPrefix(key, "err") {
4✔
329
                col = newColor(colorRed)
2✔
330
        }
2✔
331

332
        withColor(col, buf, func() {
4✔
333
                buf.WriteString(key)
2✔
334
                buf.WriteByte('=')
2✔
335
        })
2✔
336
}
337

338
func (c *console) AppendString(buf *bytes.Buffer, s string) {
18✔
339
        appendString(buf, s, false)
18✔
340
}
18✔
341

342
func (c *console) AppendBool(buf *bytes.Buffer, b bool) {
4✔
343
        buf.AppendBool(b)
4✔
344
}
4✔
345

346
func (c *console) AppendInt(buf *bytes.Buffer, i int64) {
2✔
347
        buf.AppendInt(i)
2✔
348
}
2✔
349

350
func (c *console) AppendUint(buf *bytes.Buffer, i uint64) {
2✔
351
        buf.AppendUint(i)
2✔
352
}
2✔
353

354
func (c *console) AppendFloat(buf *bytes.Buffer, f float64) {
2✔
355
        buf.AppendFloat(f, 'f', 3, 64)
2✔
356
}
2✔
357

358
func (c *console) AppendTime(buf *bytes.Buffer, t time.Time) {
4✔
359
        buf.AppendTime(t, time.Kitchen)
4✔
360
}
4✔
361

362
func (c *console) AppendDuration(buf *bytes.Buffer, d time.Duration) {
2✔
363
        buf.AppendDuration(d)
2✔
364
}
2✔
365

366
func (c *console) AppendInterface(buf *bytes.Buffer, v any) {
4✔
367
        if v == nil {
6✔
368
                return
2✔
369
        }
2✔
370

371
        c.AppendString(buf, fmt.Sprintf("%+v", v))
2✔
372
}
373

374
const hex = "0123456789abcdef"
375

376
//nolint:cyclop // Keeping unsplit for performance.
377
func appendString(buf *bytes.Buffer, s string, quote bool) {
112✔
378
        if quote {
160✔
379
                buf.WriteByte('"')
48✔
380
        }
48✔
381

382
        start := 0
112✔
383
        for i := 0; i < len(s); {
1,448✔
384
                b := s[i]
1,336✔
385
                if b-0x20 <= 0x5e && b != '"' && b != '\\' {
2,618✔
386
                        i++
1,282✔
387
                        continue
1,282✔
388
                }
389

390
                if start < i {
90✔
391
                        buf.WriteString(s[start:i])
36✔
392
                }
36✔
393
                if tryAddASCII(buf, s[i]) {
96✔
394
                        i++
42✔
395
                        start = i
42✔
396
                        continue
42✔
397
                }
398

399
                r, size := utf8.DecodeRuneInString(s[i:])
12✔
400
                if r == utf8.RuneError && size == 1 {
12✔
401
                        buf.WriteString(`\ufffd`)
×
402
                        i++
×
403
                        start = i
×
404
                        continue
×
405
                }
406
                buf.WriteString(s[i : i+size])
12✔
407
                i += size
12✔
408
                start = i
12✔
409
        }
410

411
        if start < len(s) {
212✔
412
                if start == 0 {
176✔
413
                        buf.WriteString(s)
76✔
414
                } else {
100✔
415
                        buf.WriteString(s[start:])
24✔
416
                }
24✔
417
        }
418

419
        if quote {
160✔
420
                buf.WriteByte('"')
48✔
421
        }
48✔
422
}
423

424
func tryAddASCII(buf *bytes.Buffer, b byte) bool {
54✔
425
        if b >= utf8.RuneSelf {
66✔
426
                return false
12✔
427
        }
12✔
428
        switch b {
42✔
429
        case '\\', '"':
24✔
430
                buf.WriteByte('\\')
24✔
431
                buf.WriteByte(b)
24✔
432
        case '\n':
6✔
433
                buf.WriteString("\\n")
6✔
434
        case '\r':
6✔
435
                buf.WriteString("\\r")
6✔
436
        case '\t':
6✔
437
                buf.WriteString("\\t")
6✔
438
        default:
×
439
                buf.WriteString(`\u00`)
×
440
                buf.WriteByte(hex[b>>4])
×
441
                buf.WriteByte(hex[b&0xF])
×
442
        }
443
        return true
42✔
444
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc