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 base64 helper methods #613

Open
wants to merge 2 commits 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.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions array.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package zerolog

import (
"encoding/base64"
"net"
"sync"
"time"
Expand Down Expand Up @@ -85,6 +86,19 @@ func (a *Array) Hex(val []byte) *Array {
return a
}

// Base64 appends the val as a standard padded base64 string to the array.
func (a *Array) Base64(val []byte) *Array {
a.buf = enc.AppendBase64(base64.StdEncoding, enc.AppendArrayDelim(a.buf), val)
return a
}

// Base64Custom appends the val as a base64 string to the array.
// The specific form of base64 can be specified with the first parameter.
func (a *Array) Base64Custom(b64 *base64.Encoding, val []byte) *Array {
a.buf = enc.AppendBase64(b64, enc.AppendArrayDelim(a.buf), val)
return a
}

// RawJSON adds already encoded JSON to the array.
func (a *Array) RawJSON(val []byte) *Array {
a.buf = appendJSON(enc.AppendArrayDelim(a.buf), val)
Expand Down
5 changes: 4 additions & 1 deletion array_test.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package zerolog

import (
"encoding/base64"
"net"
"testing"
"time"
Expand All @@ -24,6 +25,8 @@ func TestArray(t *testing.T) {
Str("a").
Bytes([]byte("b")).
Hex([]byte{0x1f}).
Base64([]byte{0x12, 0xef, 0x29, 0x30, 0xff}).
Base64Custom(base64.RawURLEncoding, []byte{0xcc, 0xbb, 0xaa, 0xff}).
RawJSON([]byte(`{"some":"json"}`)).
Time(time.Time{}).
IPAddr(net.IP{192, 168, 0, 10}).
Expand All @@ -32,7 +35,7 @@ func TestArray(t *testing.T) {
Str("bar", "baz").
Int("n", 1),
)
want := `[true,1,2,3,4,5,6,7,8,9,10,11.98122,12.987654321,"a","b","1f",{"some":"json"},"0001-01-01T00:00:00Z","192.168.0.10",0,{"bar":"baz","n":1}]`
want := `[true,1,2,3,4,5,6,7,8,9,10,11.98122,12.987654321,"a","b","1f","Eu8pMP8=","zLuq_w",{"some":"json"},"0001-01-01T00:00:00Z","192.168.0.10",0,{"bar":"baz","n":1}]`
if got := decodeObjectToStr(a.write([]byte{})); got != want {
t.Errorf("Array.write()\ngot: %s\nwant: %s", got, want)
}
Expand Down
14 changes: 14 additions & 0 deletions context.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package zerolog

import (
"context"
"encoding/base64"
"fmt"
"io/ioutil"
"math"
Expand Down Expand Up @@ -108,6 +109,19 @@ func (c Context) Hex(key string, val []byte) Context {
return c
}

// Base64 adds the field key with val as a standard padded base64 string to the logger context.
func (c Context) Base64(key string, val []byte) Context {
c.l.context = enc.AppendBase64(base64.StdEncoding, enc.AppendKey(c.l.context, key), val)
return c
}

// Base64Custom adds the field key with val as a base64 string to the logger context.
// The specific form of base64 can be specified with the first parameter.
func (c Context) Base64Custom(b64 *base64.Encoding, key string, val []byte) Context {
c.l.context = enc.AppendBase64(b64, enc.AppendKey(c.l.context, key), val)
return c
}

// RawJSON adds already encoded JSON to context.
//
// No sanity check is performed on b; it must not contain carriage returns and
Expand Down
20 changes: 20 additions & 0 deletions event.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package zerolog

import (
"context"
"encoding/base64"
"fmt"
"net"
"os"
Expand Down Expand Up @@ -308,6 +309,25 @@ func (e *Event) Hex(key string, val []byte) *Event {
return e
}

// Base64 adds the field key with val as a standard padded base64 string to the *Event context.
func (e *Event) Base64(key string, val []byte) *Event {
if e == nil {
return e
}
e.buf = enc.AppendBase64(base64.StdEncoding, enc.AppendKey(e.buf, key), val)
return e
}

// Base64Custom adds the field key with val as a base64 string to the *Event context.
// The specific form of base64 can be specified with the first parameter.
func (e *Event) Base64Custom(b64 *base64.Encoding, key string, val []byte) *Event {
if e == nil {
return e
}
e.buf = enc.AppendBase64(b64, enc.AppendKey(e.buf, key), val)
return e
}

// RawJSON adds already encoded JSON to the log line under key.
//
// No sanity check is performed on b; it must not contain carriage returns and
Expand Down
4 changes: 4 additions & 0 deletions internal/cbor/cbor.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,10 @@ const (
additionalTypeEmbeddedJSON uint16 = 262
additionalTypeTagHexString uint16 = 263

// Expected later encoding for CBOR-to-JSON converters - from https://www.rfc-editor.org/rfc/rfc8949.html#section-3.4.5.2
additionalTypeTagBase64Standard byte = 22
additionalTypeTagBase64RawURL byte = 23

// Unspecified number of elements.
additionalTypeInfiniteCount byte = 31
)
Expand Down
11 changes: 11 additions & 0 deletions internal/cbor/decode_stream.go
Original file line number Diff line number Diff line change
Expand Up @@ -386,6 +386,17 @@ func decodeTagData(src *bufio.Reader) []byte {
}
src.UnreadByte()
return decodeStringToDataUrl(src, "application/cbor")
case additionalTypeTagBase64Standard, additionalTypeTagBase64RawURL:
data := decodeString(src, true)
enc := base64.StdEncoding
if byte(val) == additionalTypeTagBase64RawURL {
enc = base64.RawURLEncoding
}
output := make([]byte, enc.EncodedLen(len(data))+2)
output[0] = '"'
output[len(output)-1] = '"'
enc.Encode(output[1:len(output)-1], data)
return output
default:
panic(fmt.Errorf("Unsupported Additional Tag Type: %d in decodeTagData", val))
}
Expand Down
17 changes: 17 additions & 0 deletions internal/cbor/types.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package cbor

import (
"encoding/base64"
"fmt"
"math"
"net"
Expand Down Expand Up @@ -484,3 +485,19 @@ func (e Encoder) AppendHex(dst []byte, val []byte) []byte {
dst = append(dst, byte(additionalTypeTagHexString&0xff))
return e.AppendBytes(dst, val)
}

// AppendBase64 adds a TAG and inserts base64 bytes as a string.
func (e Encoder) AppendBase64(enc *base64.Encoding, dst []byte, val []byte) []byte {
switch enc {
case base64.StdEncoding:
dst = append(dst, majorTypeTags|additionalTypeIntUint8)
dst = append(dst, additionalTypeTagBase64Standard)
return e.AppendBytes(dst, val)
case base64.RawURLEncoding:
dst = append(dst, majorTypeTags|additionalTypeIntUint8)
dst = append(dst, additionalTypeTagBase64RawURL)
return e.AppendBytes(dst, val)
default:
return e.AppendString(dst, enc.EncodeToString(val))
}
}
19 changes: 18 additions & 1 deletion internal/json/bytes.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
package json

import "unicode/utf8"
import (
"encoding/base64"
"unicode/utf8"
)

// AppendBytes is a mirror of appendString with []byte arg
func (Encoder) AppendBytes(dst, s []byte) []byte {
Expand Down Expand Up @@ -28,6 +31,20 @@ func (Encoder) AppendHex(dst, s []byte) []byte {
return append(dst, '"')
}

// AppendBase64 encodes the input bytes to a base64 string and appends
// the encoded string to the input byte slice.
func (Encoder) AppendBase64(e *base64.Encoding, dst, s []byte) []byte {
dst = append(dst, '"')
start := len(dst)
targetLen := start + e.EncodedLen(len(s))
for cap(dst) < targetLen {
dst = append(dst[:cap(dst)], 0)
}
dst = dst[:targetLen]
e.Encode(dst[start:], s)
return append(dst, '"')
}

// appendBytesComplex is a mirror of the appendStringComplex
// with []byte arg
func appendBytesComplex(dst, s []byte, i int) []byte {
Expand Down
26 changes: 26 additions & 0 deletions internal/json/bytes_test.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package json

import (
"crypto/rand"
"encoding/base64"
"testing"
"unicode"
)
Expand All @@ -25,6 +27,30 @@ func TestAppendHex(t *testing.T) {
}
}

var base64Encodings = []struct {
name string
enc *base64.Encoding
}{
{"base64.StdEncoding", base64.StdEncoding},
{"base64.RawStdEncoding", base64.RawStdEncoding},
{"base64.URLEncoding", base64.URLEncoding},
{"base64.RawURLEncoding", base64.RawURLEncoding},
}

func TestAppendBase64(t *testing.T) {
random := make([]byte, 19)
_, _ = rand.Read(random)
tests := [][]byte{{}, {'\x00'}, {'\xff'}, random}
for _, input := range tests {
for _, tt := range base64Encodings {
b := enc.AppendBase64(tt.enc, []byte{}, input)
if got, want := string(b), "\""+tt.enc.EncodeToString(input)+"\""; got != want {
t.Errorf("appendBase64(%s, %x) = %s, want %s", tt.name, input, got, want)
}
}
}
}

func TestStringBytes(t *testing.T) {
t.Parallel()
// Test that encodeState.stringBytes and encodeState.string use the same encoding.
Expand Down
13 changes: 10 additions & 3 deletions log_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package zerolog
import (
"bytes"
"context"
"encoding/base64"
"errors"
"fmt"
"net"
Expand Down Expand Up @@ -104,6 +105,8 @@ func TestWith(t *testing.T) {
Stringer("stringer_nil", nil).
Bytes("bytes", []byte("bar")).
Hex("hex", []byte{0x12, 0xef}).
Base64("base64", []byte{0x12, 0xef, 0x29, 0x30, 0xff}).
Base64Custom(base64.RawURLEncoding, "base64url", []byte{0xcc, 0xbb, 0xaa, 0xff}).
RawJSON("json", []byte(`{"some":"json"}`)).
AnErr("some_err", nil).
Err(errors.New("some error")).
Expand All @@ -126,7 +129,7 @@ func TestWith(t *testing.T) {
caller := fmt.Sprintf("%s:%d", file, line+3)
log := ctx.Caller().Logger()
log.Log().Msg("")
if got, want := decodeIfBinaryToString(out.Bytes()), `{"string":"foo","stringer":"127.0.0.1","stringer_nil":null,"bytes":"bar","hex":"12ef","json":{"some":"json"},"error":"some error","bool":true,"int":1,"int8":2,"int16":3,"int32":4,"int64":5,"uint":6,"uint8":7,"uint16":8,"uint32":9,"uint64":10,"float32":11.101,"float64":12.30303,"time":"0001-01-01T00:00:00Z","caller":"`+caller+`"}`+"\n"; got != want {
if got, want := decodeIfBinaryToString(out.Bytes()), `{"string":"foo","stringer":"127.0.0.1","stringer_nil":null,"bytes":"bar","hex":"12ef","base64":"Eu8pMP8=","base64url":"zLuq_w","json":{"some":"json"},"error":"some error","bool":true,"int":1,"int8":2,"int16":3,"int32":4,"int64":5,"uint":6,"uint8":7,"uint16":8,"uint32":9,"uint64":10,"float32":11.101,"float64":12.30303,"time":"0001-01-01T00:00:00Z","caller":"`+caller+`"}`+"\n"; got != want {
t.Errorf("invalid log output:\ngot: %v\nwant: %v", got, want)
}

Expand All @@ -140,7 +143,7 @@ func TestWith(t *testing.T) {
}()
// The above line is a little contrived, but the line above should be the line due
// to the extra frame skip.
if got, want := decodeIfBinaryToString(out.Bytes()), `{"string":"foo","stringer":"127.0.0.1","stringer_nil":null,"bytes":"bar","hex":"12ef","json":{"some":"json"},"error":"some error","bool":true,"int":1,"int8":2,"int16":3,"int32":4,"int64":5,"uint":6,"uint8":7,"uint16":8,"uint32":9,"uint64":10,"float32":11.101,"float64":12.30303,"time":"0001-01-01T00:00:00Z","caller":"`+caller+`"}`+"\n"; got != want {
if got, want := decodeIfBinaryToString(out.Bytes()), `{"string":"foo","stringer":"127.0.0.1","stringer_nil":null,"bytes":"bar","hex":"12ef","base64":"Eu8pMP8=","base64url":"zLuq_w","json":{"some":"json"},"error":"some error","bool":true,"int":1,"int8":2,"int16":3,"int32":4,"int64":5,"uint":6,"uint8":7,"uint16":8,"uint32":9,"uint64":10,"float32":11.101,"float64":12.30303,"time":"0001-01-01T00:00:00Z","caller":"`+caller+`"}`+"\n"; got != want {
t.Errorf("invalid log output:\ngot: %v\nwant: %v", got, want)
}
}
Expand Down Expand Up @@ -321,6 +324,8 @@ func TestFields(t *testing.T) {
Stringer("stringer_nil", nil).
Bytes("bytes", []byte("bar")).
Hex("hex", []byte{0x12, 0xef}).
Base64("base64", []byte{0x12, 0xef, 0x29, 0x30, 0xff}).
Base64Custom(base64.RawURLEncoding, "base64url", []byte{0xcc, 0xbb, 0xaa, 0xff}).
RawJSON("json", []byte(`{"some":"json"}`)).
RawCBOR("cbor", []byte{0x83, 0x01, 0x82, 0x02, 0x03, 0x82, 0x04, 0x05}).
Func(func(e *Event) { e.Str("func", "func_output") }).
Expand Down Expand Up @@ -348,7 +353,7 @@ func TestFields(t *testing.T) {
TimeDiff("diff", now, now.Add(-10*time.Second)).
Ctx(context.Background()).
Msg("")
if got, want := decodeIfBinaryToString(out.Bytes()), `{"caller":"`+caller+`","string":"foo","stringer":"127.0.0.1","stringer_nil":null,"bytes":"bar","hex":"12ef","json":{"some":"json"},"cbor":"data:application/cbor;base64,gwGCAgOCBAU=","func":"func_output","error":"some error","bool":true,"int":1,"int8":2,"int16":3,"int32":4,"int64":5,"uint":6,"uint8":7,"uint16":8,"uint32":9,"uint64":10,"IPv4":"192.168.0.100","IPv6":"2001:db8:85a3::8a2e:370:7334","Mac":"00:14:22:01:23:45","Prefix":"192.168.0.100/24","float32":11.1234,"float64":12.321321321,"dur":1000,"time":"0001-01-01T00:00:00Z","diff":10000}`+"\n"; got != want {
if got, want := decodeIfBinaryToString(out.Bytes()), `{"caller":"`+caller+`","string":"foo","stringer":"127.0.0.1","stringer_nil":null,"bytes":"bar","hex":"12ef","base64":"Eu8pMP8=","base64url":"zLuq_w","json":{"some":"json"},"cbor":"data:application/cbor;base64,gwGCAgOCBAU=","func":"func_output","error":"some error","bool":true,"int":1,"int8":2,"int16":3,"int32":4,"int64":5,"uint":6,"uint8":7,"uint16":8,"uint32":9,"uint64":10,"IPv4":"192.168.0.100","IPv6":"2001:db8:85a3::8a2e:370:7334","Mac":"00:14:22:01:23:45","Prefix":"192.168.0.100/24","float32":11.1234,"float64":12.321321321,"dur":1000,"time":"0001-01-01T00:00:00Z","diff":10000}`+"\n"; got != want {
t.Errorf("invalid log output:\ngot: %v\nwant: %v", got, want)
}
}
Expand Down Expand Up @@ -446,6 +451,8 @@ func TestFieldsDisabled(t *testing.T) {
Stringer("stringer", net.IP{127, 0, 0, 1}).
Bytes("bytes", []byte("bar")).
Hex("hex", []byte{0x12, 0xef}).
Base64("base64", []byte{0x12, 0xef, 0x29, 0x30, 0xff}).
Base64Custom(base64.RawURLEncoding, "base64url", []byte{0xcc, 0xbb, 0xaa, 0xff}).
AnErr("some_err", nil).
Err(errors.New("some error")).
Func(func(e *Event) { e.Str("func", "func_output") }).
Expand Down
Loading