Compare commits
2 Commits
Author | SHA1 | Date |
---|---|---|
Olivier Poitrey | fde564e937 | |
Olivier Poitrey | 274f2e4c61 |
10
README.md
10
README.md
|
@ -255,11 +255,11 @@ Some settings can be changed and will by applied to all loggers:
|
||||||
All operations are allocation free (those numbers *include* JSON encoding):
|
All operations are allocation free (those numbers *include* JSON encoding):
|
||||||
|
|
||||||
```
|
```
|
||||||
BenchmarkLogEmpty-8 100000000 19.1 ns/op 0 B/op 0 allocs/op
|
BenchmarkLogEmpty-8 100000000 19.1 ns/op 0 B/op 0 allocs/op
|
||||||
BenchmarkDisabled-8 500000000 4.07 ns/op 0 B/op 0 allocs/op
|
BenchmarkDisabled-8 500000000 4.07 ns/op 0 B/op 0 allocs/op
|
||||||
BenchmarkInfo-8 30000000 46.3 ns/op 0 B/op 0 allocs/op
|
BenchmarkInfo-8 30000000 42.5 ns/op 0 B/op 0 allocs/op
|
||||||
BenchmarkContextFields-8 30000000 47.1 ns/op 0 B/op 0 allocs/op
|
BenchmarkContextFields-8 30000000 44.9 ns/op 0 B/op 0 allocs/op
|
||||||
BenchmarkLogFields-8 10000000 186 ns/op 0 B/op 0 allocs/op
|
BenchmarkLogFields-8 10000000 184 ns/op 0 B/op 0 allocs/op
|
||||||
```
|
```
|
||||||
|
|
||||||
Using Uber's zap [comparison benchmark](https://github.com/uber-go/zap#performance):
|
Using Uber's zap [comparison benchmark](https://github.com/uber-go/zap#performance):
|
||||||
|
|
106
json.go
106
json.go
|
@ -21,48 +21,10 @@ func appendJSONString(dst []byte, s string) []byte {
|
||||||
// Check if the character needs encoding. Control characters, slashes,
|
// Check if the character needs encoding. Control characters, slashes,
|
||||||
// and the double quote need json encoding. Bytes above the ascii
|
// and the double quote need json encoding. Bytes above the ascii
|
||||||
// boundary needs utf8 encoding.
|
// boundary needs utf8 encoding.
|
||||||
if s[i] < ' ' || s[i] == '\\' || s[i] == '"' || s[i] > 126 {
|
if s[i] < 0x20 || s[i] > 0x7e || s[i] == '\\' || s[i] == '"' {
|
||||||
// We encountered a character that needs to be encoded. Let's
|
// We encountered a character that needs to be encoded. Switch
|
||||||
// append the previous simple characters to the byte slice
|
// to complex version of the algorithm.
|
||||||
// and switch our operation to read and encode the remainder
|
dst = appendJSONStringComplex(dst, s, i)
|
||||||
// characters byte-by-byte.
|
|
||||||
dst = append(dst, s[:i]...)
|
|
||||||
for i < len(s) {
|
|
||||||
if b := s[i]; b < utf8.RuneSelf {
|
|
||||||
switch b {
|
|
||||||
case '"', '\\':
|
|
||||||
dst = append(dst, '\\', b)
|
|
||||||
case '\b':
|
|
||||||
dst = append(dst, '\\', 'b')
|
|
||||||
case '\f':
|
|
||||||
dst = append(dst, '\\', 'f')
|
|
||||||
case '\n':
|
|
||||||
dst = append(dst, '\\', 'n')
|
|
||||||
case '\r':
|
|
||||||
dst = append(dst, '\\', 'r')
|
|
||||||
case '\t':
|
|
||||||
dst = append(dst, '\\', 't')
|
|
||||||
default:
|
|
||||||
if b >= 0x20 {
|
|
||||||
dst = append(dst, b)
|
|
||||||
} else {
|
|
||||||
dst = append(dst, '\\', 'u', '0', '0',
|
|
||||||
hex[b>>4], hex[b&0xF])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
i++
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
r, size := utf8.DecodeRuneInString(s[i:])
|
|
||||||
if r == utf8.RuneError && size == 1 {
|
|
||||||
dst = append(dst, `\ufffd`...)
|
|
||||||
i++
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
dst = append(dst, s[i:i+size]...)
|
|
||||||
i += size
|
|
||||||
}
|
|
||||||
// End with a double quote
|
|
||||||
return append(dst, '"')
|
return append(dst, '"')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -72,3 +34,63 @@ func appendJSONString(dst []byte, s string) []byte {
|
||||||
// End with a double quote
|
// End with a double quote
|
||||||
return append(dst, '"')
|
return append(dst, '"')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// appendJSONStringComplex is used by appendJSONString to take over an in
|
||||||
|
// progress JSON string encoding that encountered a character that needs
|
||||||
|
// to be encoded.
|
||||||
|
func appendJSONStringComplex(dst []byte, s string, i int) []byte {
|
||||||
|
start := 0
|
||||||
|
for i < len(s) {
|
||||||
|
b := s[i]
|
||||||
|
if b >= utf8.RuneSelf {
|
||||||
|
r, size := utf8.DecodeRuneInString(s[i:])
|
||||||
|
if r == utf8.RuneError && size == 1 {
|
||||||
|
// In case of error, first append previous simple characters to
|
||||||
|
// the byte slice if any and append a remplacement character code
|
||||||
|
// in place of the invalid sequence.
|
||||||
|
if start < i {
|
||||||
|
dst = append(dst, s[start:i]...)
|
||||||
|
}
|
||||||
|
dst = append(dst, `\ufffd`...)
|
||||||
|
i += size
|
||||||
|
start = i
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
i += size
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if b >= 0x20 && b <= 0x7e && b != '\\' && b != '"' {
|
||||||
|
i++
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
// We encountered a character that needs to be encoded.
|
||||||
|
// Let's append the previous simple characters to the byte slice
|
||||||
|
// and switch our operation to read and encode the remainder
|
||||||
|
// characters byte-by-byte.
|
||||||
|
if start < i {
|
||||||
|
dst = append(dst, s[start:i]...)
|
||||||
|
}
|
||||||
|
switch b {
|
||||||
|
case '"', '\\':
|
||||||
|
dst = append(dst, '\\', b)
|
||||||
|
case '\b':
|
||||||
|
dst = append(dst, '\\', 'b')
|
||||||
|
case '\f':
|
||||||
|
dst = append(dst, '\\', 'f')
|
||||||
|
case '\n':
|
||||||
|
dst = append(dst, '\\', 'n')
|
||||||
|
case '\r':
|
||||||
|
dst = append(dst, '\\', 'r')
|
||||||
|
case '\t':
|
||||||
|
dst = append(dst, '\\', 't')
|
||||||
|
default:
|
||||||
|
dst = append(dst, '\\', 'u', '0', '0', hex[b>>4], hex[b&0xF])
|
||||||
|
}
|
||||||
|
i++
|
||||||
|
start = i
|
||||||
|
}
|
||||||
|
if start < len(s) {
|
||||||
|
dst = append(dst, s[start:]...)
|
||||||
|
}
|
||||||
|
return dst
|
||||||
|
}
|
||||||
|
|
31
json_test.go
31
json_test.go
|
@ -1,12 +1,15 @@
|
||||||
package zerolog
|
package zerolog
|
||||||
|
|
||||||
import "testing"
|
import (
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
func TestAppendJSONString(t *testing.T) {
|
func TestAppendJSONString(t *testing.T) {
|
||||||
encodeStringTests := []struct {
|
encodeStringTests := []struct {
|
||||||
in string
|
in string
|
||||||
out string
|
out string
|
||||||
}{
|
}{
|
||||||
|
{"", `""`},
|
||||||
{"\\", `"\\"`},
|
{"\\", `"\\"`},
|
||||||
{"\x00", `"\u0000"`},
|
{"\x00", `"\u0000"`},
|
||||||
{"\x01", `"\u0001"`},
|
{"\x01", `"\u0001"`},
|
||||||
|
@ -40,7 +43,13 @@ func TestAppendJSONString(t *testing.T) {
|
||||||
{"\x1d", `"\u001d"`},
|
{"\x1d", `"\u001d"`},
|
||||||
{"\x1e", `"\u001e"`},
|
{"\x1e", `"\u001e"`},
|
||||||
{"\x1f", `"\u001f"`},
|
{"\x1f", `"\u001f"`},
|
||||||
|
{"✭", `"✭"`},
|
||||||
|
{"foo\xc2\x7fbar", `"foo\ufffd\u007fbar"`}, // invalid sequence
|
||||||
{"ascii", `"ascii"`},
|
{"ascii", `"ascii"`},
|
||||||
|
{"\"a", `"\"a"`},
|
||||||
|
{"\x1fa", `"\u001fa"`},
|
||||||
|
{"foo\"bar\"baz", `"foo\"bar\"baz"`},
|
||||||
|
{"\x1ffoo\x1fbar\x1fbaz", `"\u001ffoo\u001fbar\u001fbaz"`},
|
||||||
{"emoji \u2764\ufe0f!", `"emoji ❤️!"`},
|
{"emoji \u2764\ufe0f!", `"emoji ❤️!"`},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -51,3 +60,23 @@ func TestAppendJSONString(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func BenchmarkAppendJSONString(b *testing.B) {
|
||||||
|
tests := map[string]string{
|
||||||
|
"NoEncoding": `aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa`,
|
||||||
|
"EncodingFirst": `"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa`,
|
||||||
|
"EncodingMiddle": `aaaaaaaaaaaaaaaaaaaaaaaaa"aaaaaaaaaaaaaaaaaaaaaaaa`,
|
||||||
|
"EncodingLast": `aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"`,
|
||||||
|
"MultiBytesFirst": `❤️aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa`,
|
||||||
|
"MultiBytesMiddle": `aaaaaaaaaaaaaaaaaaaaaaaaa❤️aaaaaaaaaaaaaaaaaaaaaaaa`,
|
||||||
|
"MultiBytesLast": `aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa❤️`,
|
||||||
|
}
|
||||||
|
for name, str := range tests {
|
||||||
|
b.Run(name, func(b *testing.B) {
|
||||||
|
buf := make([]byte, 0, 100)
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
_ = appendJSONString(buf, str)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue