Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add global parameter for customizing how .Bytes works in JSON #615

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
7 changes: 7 additions & 0 deletions encoder_json.go
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
//go:build !binary_log
// +build !binary_log

package zerolog
Expand All @@ -21,6 +22,12 @@ func init() {
json.JSONMarshalFunc = func(v interface{}) ([]byte, error) {
return InterfaceMarshalFunc(v)
}
if JSONBytesMarshalFunc == nil {
JSONBytesMarshalFunc = json.AppendBytesDefault
}
json.AppendBytesFunc = func(dst, src []byte) []byte {
return JSONBytesMarshalFunc(dst, src)
}
}

func appendJSON(dst []byte, j []byte) []byte {
Expand Down
2 changes: 1 addition & 1 deletion event.go
Original file line number Diff line number Diff line change
Expand Up @@ -290,7 +290,7 @@ func (e *Event) Stringers(key string, vals []fmt.Stringer) *Event {
// Bytes adds the field key with val as a string to the *Event context.
//
// Runes outside of normal ASCII ranges will be hex-encoded in the resulting
// JSON.
// JSON. The marshaling can be customized by setting JSONBytesMarshalFunc.
func (e *Event) Bytes(key string, val []byte) *Event {
if e == nil {
return e
Expand Down
21 changes: 21 additions & 0 deletions globals.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package zerolog

import (
"encoding/base64"
"encoding/json"
"strconv"
"sync/atomic"
Expand Down Expand Up @@ -80,6 +81,12 @@ var (
return err
}

// JSONBytesMarshalFunc allows customization of how Bytes() produces JSON output.
// The default implementation leaves ASCII characters as-is and encodes Unicode runes as hex escapes.
//
// The function should append the encoded bytes to dst and return the extended buffer.
JSONBytesMarshalFunc func(dst, src []byte) []byte

// InterfaceMarshalFunc allows customization of interface marshaling.
// Default: "encoding/json.Marshal"
InterfaceMarshalFunc = json.Marshal
Expand Down Expand Up @@ -164,3 +171,17 @@ func DisableSampling(v bool) {
func samplingDisabled() bool {
return atomic.LoadInt32(disableSampling) == 1
}

// JSONBytesMarshalBase64 returns a function compatible with JSONBytesMarshalFunc that encodes bytes using the given base64 encoder.
func JSONBytesMarshalBase64(enc *base64.Encoding) func(dst, src []byte) []byte {
return func(dst, src []byte) []byte {
start := len(dst)
targetLen := start + enc.EncodedLen(len(src))
for cap(dst) < targetLen {
dst = append(dst[:cap(dst)], 0)
}
dst = dst[:targetLen]
enc.Encode(dst[start:], src)
return dst
}
}
2 changes: 2 additions & 0 deletions internal/json/base.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ package json
// you might get a nil pointer dereference panic at runtime.
var JSONMarshalFunc func(v interface{}) ([]byte, error)

var AppendBytesFunc func(dst, src []byte) []byte = AppendBytesDefault

type Encoder struct{}

// AppendKey appends a new key to the output JSON.
Expand Down
11 changes: 7 additions & 4 deletions internal/json/bytes.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,17 @@ import "unicode/utf8"
// AppendBytes is a mirror of appendString with []byte arg
func (Encoder) AppendBytes(dst, s []byte) []byte {
dst = append(dst, '"')
dst = AppendBytesFunc(dst, s)
return append(dst, '"')
}

func AppendBytesDefault(dst, s []byte) []byte {
for i := 0; i < len(s); i++ {
if !noEscapeTable[s[i]] {
dst = appendBytesComplex(dst, s, i)
return append(dst, '"')
return appendBytesComplex(dst, s, i)
}
}
dst = append(dst, s...)
return append(dst, '"')
return append(dst, s...)
}

// AppendHex encodes the input bytes to a hex string and appends
Expand Down
46 changes: 46 additions & 0 deletions log_json_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
//go:build !binary_log
// +build !binary_log

package zerolog

import (
"bytes"
"encoding/base64"
"os"
"testing"
)

func TestJSONBytesMarshalFunc(t *testing.T) {
out := &bytes.Buffer{}
log := New(out)
log.Log().Bytes("bytes", []byte{'a', 'b', 'c', 1, 2, 3, 0xff}).Msg("msg")
if got, want := decodeIfBinaryToString(out.Bytes()), `{"bytes":"abc\u0001\u0002\u0003\ufffd","message":"msg"}`+"\n"; got != want {
t.Errorf("invalid log output:\ngot: %v\nwant: %v", got, want)
}
out.Reset()
origBytesMarshalFunc := JSONBytesMarshalFunc
defer func() {
JSONBytesMarshalFunc = origBytesMarshalFunc
}()

JSONBytesMarshalFunc = JSONBytesMarshalBase64(base64.StdEncoding)
log.Log().Bytes("bytes", []byte{'a', 'b', 'c', 1, 2, 3, 0xff}).Msg("msg")
if got, want := decodeIfBinaryToString(out.Bytes()), `{"bytes":"YWJjAQID/w==","message":"msg"}`+"\n"; got != want {
t.Errorf("invalid log output:\ngot: %v\nwant: %v", got, want)
}
out.Reset()

JSONBytesMarshalFunc = JSONBytesMarshalBase64(base64.RawURLEncoding)
log.Log().Bytes("bytes", []byte{'a', 'b', 'c', 1, 2, 3, 0xff}).Msg("msg")
if got, want := decodeIfBinaryToString(out.Bytes()), `{"bytes":"YWJjAQID_w","message":"msg"}`+"\n"; got != want {
t.Errorf("invalid log output:\ngot: %v\nwant: %v", got, want)
}
out.Reset()
}

func ExampleJSONBytesMarshalBase64() {
log := New(os.Stdout)
JSONBytesMarshalFunc = JSONBytesMarshalBase64(base64.StdEncoding)
log.Info().Bytes("bytes", []byte{'a', 'b', 'c', 1, 2, 3, 0xff}).Msg("msg")
// Output: {"level":"info","bytes":"YWJjAQID/w==","message":"msg"}
}