Skip to content

Commit

Permalink
Merge pull request #119 from hashicorp/b-quoting
Browse files Browse the repository at this point in the history
When rendering multiple line output, still quote the individual lines
  • Loading branch information
evanphx authored Sep 21, 2022
2 parents 9846b38 + 33175f2 commit 0d6179f
Show file tree
Hide file tree
Showing 2 changed files with 108 additions and 3 deletions.
89 changes: 86 additions & 3 deletions intlogger.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ import (
"sync"
"sync/atomic"
"time"
"unicode"
"unicode/utf8"

"github.com/fatih/color"
)
Expand Down Expand Up @@ -420,7 +422,9 @@ func (l *intLogger) logPlain(t time.Time, name string, level Level, msg string,
} else {
l.writer.WriteByte('=')
}
l.writer.WriteString(strconv.Quote(val))
l.writer.WriteByte('"')
writeEscapedForOutput(l.writer, val, true)
l.writer.WriteByte('"')
} else {
l.writer.WriteByte(' ')
l.writer.WriteString(key)
Expand Down Expand Up @@ -448,19 +452,98 @@ func writeIndent(w *writer, str string, indent string) {
if nl == -1 {
if str != "" {
w.WriteString(indent)
w.WriteString(str)
writeEscapedForOutput(w, str, false)
w.WriteString("\n")
}
return
}

w.WriteString(indent)
w.WriteString(str[:nl])
writeEscapedForOutput(w, str[:nl], false)
w.WriteString("\n")
str = str[nl+1:]
}
}

func needsEscaping(str string) bool {
for _, b := range str {
if !unicode.IsPrint(b) || b == '"' {
return true
}
}

return false
}

const (
lowerhex = "0123456789abcdef"
)

var bufPool = sync.Pool{
New: func() interface{} {
return new(bytes.Buffer)
},
}

func writeEscapedForOutput(w io.Writer, str string, escapeQuotes bool) {
if !needsEscaping(str) {
w.Write([]byte(str))
return
}

bb := bufPool.Get().(*bytes.Buffer)
bb.Reset()

defer bufPool.Put(bb)

for _, r := range str {
if escapeQuotes && r == '"' {
bb.WriteString(`\"`)
} else if unicode.IsPrint(r) {
bb.WriteRune(r)
} else {
switch r {
case '\a':
bb.WriteString(`\a`)
case '\b':
bb.WriteString(`\b`)
case '\f':
bb.WriteString(`\f`)
case '\n':
bb.WriteString(`\n`)
case '\r':
bb.WriteString(`\r`)
case '\t':
bb.WriteString(`\t`)
case '\v':
bb.WriteString(`\v`)
default:
switch {
case r < ' ':
bb.WriteString(`\x`)
bb.WriteByte(lowerhex[byte(r)>>4])
bb.WriteByte(lowerhex[byte(r)&0xF])
case !utf8.ValidRune(r):
r = 0xFFFD
fallthrough
case r < 0x10000:
bb.WriteString(`\u`)
for s := 12; s >= 0; s -= 4 {
bb.WriteByte(lowerhex[r>>uint(s)&0xF])
}
default:
bb.WriteString(`\U`)
for s := 28; s >= 0; s -= 4 {
bb.WriteByte(lowerhex[r>>uint(s)&0xF])
}
}
}
}
}

w.Write(bb.Bytes())
}

func (l *intLogger) renderSlice(v reflect.Value) string {
var buf bytes.Buffer

Expand Down
22 changes: 22 additions & 0 deletions logger_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,28 @@ func TestLogger(t *testing.T) {
assert.Equal(t, expected, rest)
})

t.Run("handles backslash r in entries", func(t *testing.T) {
var buf bytes.Buffer

logger := New(&LoggerOptions{
Name: "test",
Output: &buf,
})

logger.Info("this is test", "who", "programmer", "why", "testing\n\rand other\n\rpretty cool things like \x01 and \u1680 and \U00101120")

str := buf.String()
dataIdx := strings.IndexByte(str, ' ')
rest := str[dataIdx+1:]

expected := `[INFO] test: this is test: who=programmer
why=
| testing
| \rand other
| \rpretty cool things like \x01 and \u1680 and \U00101120` + "\n \n"
assert.Equal(t, expected, rest)
})

t.Run("outputs stack traces", func(t *testing.T) {
var buf bytes.Buffer

Expand Down

0 comments on commit 0d6179f

Please sign in to comment.