Skip to content

Commit 290bc92

Browse files
committed
When rendering multiple line output, still quote the individual lines
1 parent 9846b38 commit 290bc92

File tree

2 files changed

+108
-3
lines changed

2 files changed

+108
-3
lines changed

intlogger.go

Lines changed: 86 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@ import (
1717
"sync"
1818
"sync/atomic"
1919
"time"
20+
"unicode"
21+
"unicode/utf8"
2022

2123
"github.com/fatih/color"
2224
)
@@ -420,7 +422,9 @@ func (l *intLogger) logPlain(t time.Time, name string, level Level, msg string,
420422
} else {
421423
l.writer.WriteByte('=')
422424
}
423-
l.writer.WriteString(strconv.Quote(val))
425+
l.writer.WriteByte('"')
426+
writeEscapedForOutput(l.writer, val, true)
427+
l.writer.WriteByte('"')
424428
} else {
425429
l.writer.WriteByte(' ')
426430
l.writer.WriteString(key)
@@ -448,19 +452,98 @@ func writeIndent(w *writer, str string, indent string) {
448452
if nl == -1 {
449453
if str != "" {
450454
w.WriteString(indent)
451-
w.WriteString(str)
455+
writeEscapedForOutput(w, str, false)
452456
w.WriteString("\n")
453457
}
454458
return
455459
}
456460

457461
w.WriteString(indent)
458-
w.WriteString(str[:nl])
462+
writeEscapedForOutput(w, str[:nl], false)
459463
w.WriteString("\n")
460464
str = str[nl+1:]
461465
}
462466
}
463467

468+
func needsEscaping(str string) bool {
469+
for _, b := range str {
470+
if !unicode.IsPrint(b) || b == '"' {
471+
return true
472+
}
473+
}
474+
475+
return false
476+
}
477+
478+
const (
479+
lowerhex = "0123456789abcdef"
480+
)
481+
482+
var bufPool = sync.Pool{
483+
New: func() interface{} {
484+
return new(bytes.Buffer)
485+
},
486+
}
487+
488+
func writeEscapedForOutput(w io.Writer, str string, escapeQuotes bool) {
489+
if !needsEscaping(str) {
490+
w.Write([]byte(str))
491+
return
492+
}
493+
494+
bb := bufPool.New().(*bytes.Buffer)
495+
bb.Reset()
496+
497+
defer bufPool.Put(bb)
498+
499+
for _, r := range str {
500+
if escapeQuotes && r == '"' {
501+
bb.WriteString(`\"`)
502+
} else if unicode.IsPrint(r) {
503+
bb.WriteRune(r)
504+
} else {
505+
switch r {
506+
case '\a':
507+
bb.WriteString(`\a`)
508+
case '\b':
509+
bb.WriteString(`\b`)
510+
case '\f':
511+
bb.WriteString(`\f`)
512+
case '\n':
513+
bb.WriteString(`\n`)
514+
case '\r':
515+
bb.WriteString(`\r`)
516+
case '\t':
517+
bb.WriteString(`\t`)
518+
case '\v':
519+
bb.WriteString(`\v`)
520+
default:
521+
switch {
522+
case r < ' ':
523+
bb.WriteString(`\x`)
524+
bb.WriteByte(lowerhex[byte(r)>>4])
525+
bb.WriteByte(lowerhex[byte(r)&0xF])
526+
case !utf8.ValidRune(r):
527+
r = 0xFFFD
528+
fallthrough
529+
case r < 0x10000:
530+
bb.WriteString(`\u`)
531+
for s := 12; s >= 0; s -= 4 {
532+
bb.WriteByte(lowerhex[r>>uint(s)&0xF])
533+
}
534+
default:
535+
bb.WriteString(`\U`)
536+
for s := 28; s >= 0; s -= 4 {
537+
bb.WriteByte(lowerhex[r>>uint(s)&0xF])
538+
}
539+
}
540+
}
541+
}
542+
}
543+
544+
w.Write(bb.Bytes())
545+
}
546+
464547
func (l *intLogger) renderSlice(v reflect.Value) string {
465548
var buf bytes.Buffer
466549

logger_test.go

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -176,6 +176,28 @@ func TestLogger(t *testing.T) {
176176
assert.Equal(t, expected, rest)
177177
})
178178

179+
t.Run("handles backslash r in entries", func(t *testing.T) {
180+
var buf bytes.Buffer
181+
182+
logger := New(&LoggerOptions{
183+
Name: "test",
184+
Output: &buf,
185+
})
186+
187+
logger.Info("this is test", "who", "programmer", "why", "testing\n\rand other\n\rpretty cool things")
188+
189+
str := buf.String()
190+
dataIdx := strings.IndexByte(str, ' ')
191+
rest := str[dataIdx+1:]
192+
193+
expected := `[INFO] test: this is test: who=programmer
194+
why=
195+
| testing
196+
| \rand other
197+
| \rpretty cool things` + "\n \n"
198+
assert.Equal(t, expected, rest)
199+
})
200+
179201
t.Run("outputs stack traces", func(t *testing.T) {
180202
var buf bytes.Buffer
181203

0 commit comments

Comments
 (0)