From 9c272a9cedc8f183dc8f9be348d39a2595e29bd4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20Paj=C4=85k?= Date: Fri, 19 Jan 2024 12:45:01 +0100 Subject: [PATCH] Handle bytes --- log/internal/writer_logger.go | 2 ++ log/value.go | 46 ++++++++++++++++++++++++++++++----- log/value_test.go | 33 +++++++++++++++---------- 3 files changed, 62 insertions(+), 19 deletions(-) diff --git a/log/internal/writer_logger.go b/log/internal/writer_logger.go index 2e71f9a537d..6b619b8d4c8 100644 --- a/log/internal/writer_logger.go +++ b/log/internal/writer_logger.go @@ -65,6 +65,8 @@ func (l *writerLogger) appendValue(v log.Value) { l.write(strconv.FormatFloat(v.Float64(), 'g', -1, 64)) // strconv.FormatFloat allocates memory. case log.KindBool: l.write(strconv.FormatBool(v.Bool())) + case log.KindBytes: + l.write(fmt.Sprint(v.Bytes())) case log.KindGroup: l.write(fmt.Sprint(v.Group())) case log.KindEmpty: diff --git a/log/value.go b/log/value.go index da9cc9af611..38e21d6f2d7 100644 --- a/log/value.go +++ b/log/value.go @@ -8,6 +8,7 @@ package log // import "go.opentelemetry.io/otel/log" import ( + "bytes" "fmt" "math" "strconv" @@ -19,17 +20,16 @@ import ( type Value struct { _ [0]func() // disallow == // num holds the value for Kinds Int64, Uint64, Float64, and Bool, - // the string length for KindString. + // the length for Kinds String, Bytes. num uint64 // If any is of type Kind, then the value is in num as described above. - // If any is of type stringptr, then the Kind is String and the string value - // consists of the length in num and the pointer in any. - // (This implies that key-value pairs cannot store values of type stringptr.) + // Otherwise (if is of type stringptr, bytesptr or groupptr) then it contains the value. any any } type ( stringptr *byte // used in Value.any when the Value is a string + bytesptr *byte // used in Value.any when the Value is a []byte groupptr *KeyValue // used in Value.any when the Value is a []Attr ) @@ -44,6 +44,7 @@ const ( KindInt64 KindString KindUint64 + KindBytes KindGroup ) @@ -54,6 +55,7 @@ var kindStrings = []string{ "Int64", "String", "Uint64", + "Bytes", "Group", } @@ -73,6 +75,8 @@ func (v Value) Kind() Kind { return x case stringptr: return KindString + case bytesptr: + return KindBytes case groupptr: return KindGroup default: @@ -114,6 +118,12 @@ func BoolValue(v bool) Value { //nolint:revive // We are passing bool as this is return Value{num: u, any: KindBool} } +// BytesValue returns a [Value] for bytes. +// The caller must not subsequently mutate the argument slice. +func BytesValue(v []byte) Value { + return Value{num: uint64(len(v)), any: bytesptr(unsafe.SliceData(v))} +} + // GroupValue returns a new [Value] for a list of key-value pairs. // The caller must not subsequently mutate the argument slice. func GroupValue(as ...KeyValue) Value { @@ -158,6 +168,8 @@ func (v Value) Any() any { return v.str() case KindBool: return v.bool() + case KindBytes: + return v.bytes() case KindEmpty: return nil default: @@ -225,7 +237,20 @@ func (v Value) float() float64 { return math.Float64frombits(v.num) } -// Group returns v's value as a []Attr. +// Group returns v's value as a []byte. +// It panics if v's [Kind] is not [KindBytes]. +func (v Value) Bytes() []byte { + if sp, ok := v.any.(bytesptr); ok { + return unsafe.Slice((*byte)(sp), v.num) + } + panic("Bytes: bad kind") +} + +func (v Value) bytes() []byte { + return unsafe.Slice((*byte)(v.any.(bytesptr)), v.num) +} + +// Group returns v's value as a []KeyValue. // It panics if v's [Kind] is not [KindGroup]. func (v Value) Group() []KeyValue { if sp, ok := v.any.(groupptr); ok { @@ -259,8 +284,10 @@ func (v Value) Equal(w Value) bool { return v.float() == w.float() case KindGroup: return sliceEqualFunc(v.group(), w.group(), KeyValue.Equal) + case KindBytes: + return bytes.Equal(v.bytes(), w.bytes()) case KindEmpty: - return k1 == k2 + return true default: panic(fmt.Sprintf("bad kind: %s", k1)) } @@ -291,6 +318,8 @@ func (v Value) append(dst []byte) []byte { return strconv.AppendFloat(dst, v.float(), 'g', -1, 64) case KindBool: return strconv.AppendBool(dst, v.bool()) + case KindBytes: + return fmt.Append(dst, v.bytes()) case KindGroup: return fmt.Append(dst, v.group()) case KindEmpty: @@ -337,6 +366,11 @@ func Bool(key string, v bool) KeyValue { return KeyValue{key, BoolValue(v)} } +// Bytes returns an KeyValue for a bytes. +func Bytes(key string, v []byte) KeyValue { + return KeyValue{key, BytesValue(v)} +} + // Group returns an KeyValue for a Group [Value]. // // Use Group to collect several key-value pairs under a single diff --git a/log/value_test.go b/log/value_test.go index a5bd19941e2..47fed37c338 100644 --- a/log/value_test.go +++ b/log/value_test.go @@ -6,7 +6,6 @@ package log import ( "fmt" "testing" - "time" "unsafe" "github.com/stretchr/testify/assert" @@ -26,7 +25,9 @@ func TestValueEqual(t *testing.T) { Float64Value(3.7), BoolValue(true), BoolValue(false), - GroupValue(Bool("b", true), Int("i", 3)), + StringValue("hi"), + BytesValue([]byte{1, 3, 5}), + GroupValue(Bool("b", true), Int("i", 3), Bytes("b", []byte{3, 5, 7})), } for i, v1 := range vals { for j, v2 := range vals { @@ -49,7 +50,9 @@ func TestValueString(t *testing.T) { {Float64Value(.15), "0.15"}, {BoolValue(true), "true"}, {StringValue("foo"), "foo"}, + {BytesValue([]byte{2, 4, 6}), "[2 4 6]"}, {GroupValue(Int("a", 1), Bool("b", true)), "[a=1 b=true]"}, + {Value{}, ""}, } { got := test.v.String() assert.Equal(t, test.want, got) @@ -63,15 +66,16 @@ func TestValueNoAlloc(t *testing.T) { u uint64 f float64 b bool + by []byte s string - x any - tm time.Time ) + bytes := []byte{1, 3, 4} a := int(testing.AllocsPerRun(5, func() { i = Int64Value(1).Int64() u = Uint64Value(1).Uint64() f = Float64Value(1).Float64() b = BoolValue(true).Bool() + by = BytesValue(bytes).Bytes() s = StringValue("foo").String() })) assert.Zero(t, a) @@ -79,26 +83,27 @@ func TestValueNoAlloc(t *testing.T) { _ = u _ = f _ = b + _ = by _ = s - _ = x - _ = tm } func TestKeyValueNoAlloc(t *testing.T) { // Assign values just to make sure the compiler doesn't optimize away the statements. var ( - i int64 - u uint64 - f float64 - b bool - s string - x any + i int64 + u uint64 + f float64 + b bool + by []byte + s string ) + bytes := []byte{1, 3, 4} a := int(testing.AllocsPerRun(5, func() { i = Int64("key", 1).Value.Int64() u = Uint64("key", 1).Value.Uint64() f = Float64("key", 1).Value.Float64() b = Bool("key", true).Value.Bool() + by = Bytes("key", bytes).Value.Bytes() s = String("key", "foo").Value.String() })) assert.Zero(t, a) @@ -106,8 +111,8 @@ func TestKeyValueNoAlloc(t *testing.T) { _ = u _ = f _ = b + _ = by _ = s - _ = x } func TestValueAny(t *testing.T) { @@ -121,7 +126,9 @@ func TestValueAny(t *testing.T) { {uint64(2), Uint64Value(2)}, {int64(11), Int64Value(11)}, {1.5, Float64Value(1.5)}, + {[]byte{1, 2, 3}, BytesValue([]byte{1, 2, 3})}, {[]KeyValue{Int("i", 3)}, GroupValue(Int("i", 3))}, + {nil, Value{}}, } { got := test.in.Any() assert.Equal(t, test.want, got)