diff --git a/format.go b/format.go index 4e8e160..cf364fc 100644 --- a/format.go +++ b/format.go @@ -7,7 +7,6 @@ import ( "reflect" "strconv" "strings" - "sync" "time" ) @@ -211,7 +210,6 @@ func formatLogfmtValue(value interface{}) string { if value == nil { return "nil" } - if t, ok := value.(time.Time); ok { // Performance optimization: No need for escaping since the provided // timeFormat doesn't have any escape characters, and escaping is @@ -235,49 +233,19 @@ func formatLogfmtValue(value interface{}) string { } } -var stringBufPool = sync.Pool{ - New: func() interface{} { return new(bytes.Buffer) }, -} - +// escapeString checks if the provided string needs escaping/quoting, and +// calls strconv.Quote if needed func escapeString(s string) string { - needsQuotes := false - needsEscape := false + needsQuoting := false for _, r := range s { - if r <= ' ' || r == '=' || r == '"' { - needsQuotes = true - } - if r == '\\' || r == '"' || r == '\n' || r == '\r' || r == '\t' { - needsEscape = true + // quote everything below " (0x34) and above~ (0x7E), plus equal-sign + if r <= '"' || r > '~' || r == '=' { + needsQuoting = true + break } } - if needsEscape == false && needsQuotes == false { + if !needsQuoting { return s } - e := stringBufPool.Get().(*bytes.Buffer) - e.WriteByte('"') - for _, r := range s { - switch r { - case '\\', '"': - e.WriteByte('\\') - e.WriteByte(byte(r)) - case '\n': - e.WriteString("\\n") - case '\r': - e.WriteString("\\r") - case '\t': - e.WriteString("\\t") - default: - e.WriteRune(r) - } - } - e.WriteByte('"') - var ret string - if needsQuotes { - ret = e.String() - } else { - ret = string(e.Bytes()[1 : e.Len()-1]) - } - e.Reset() - stringBufPool.Put(e) - return ret + return strconv.Quote(s) } diff --git a/log15_test.go b/log15_test.go index fb85fc3..b9a5ba6 100644 --- a/log15_test.go +++ b/log15_test.go @@ -158,11 +158,11 @@ func TestLogfmt(t *testing.T) { l, buf := testFormatter(LogfmtFormat()) l.Error("some message", "x", 1, "y", 3.2, "equals", "=", "quote", "\"", - "nil", nilVal, "carriage_return", "bang"+string('\r')+"foo", "tab", "bar baz", "newline", "foo\nbar") + "nilval", nilVal, "nil", nil, "carriage_return", "bang"+string('\r')+"foo", "tab", "bar baz", "newline", "foo\nbar") // skip timestamp in comparison got := buf.Bytes()[27:buf.Len()] - expected := []byte(`lvl=eror msg="some message" x=1 y=3.200 equals="=" quote="\"" nil=nil carriage_return="bang\rfoo" tab="bar\tbaz" newline="foo\nbar"` + "\n") + expected := []byte(`lvl=eror msg="some message" x=1 y=3.200 equals="=" quote="\"" nilval=nil nil=nil carriage_return="bang\rfoo" tab="bar\tbaz" newline="foo\nbar"` + "\n") if !bytes.Equal(got, expected) { t.Fatalf("Got %s, expected %s", got, expected) }