diff --git a/CHANGELOG.md b/CHANGELOG.md index 06135e0d7d1..b7e471bf39d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,7 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm - Add `WithProxy` option in `go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlptracehttp`. (#4906) - Add `SeverityUndefined` `const` to `go.opentelemetry.io/otel/log`. This value represents an unset severity level. (#5072) +- Add `Empty` function in `go.opentelemetry.io/otel/log` to return a `KeyValue` for an empty value. (#5076) ### Changed diff --git a/log/keyvalue.go b/log/keyvalue.go index bb55f0d2270..da3d55c4fc8 100644 --- a/log/keyvalue.go +++ b/log/keyvalue.go @@ -34,6 +34,7 @@ const ( ) // A Value represents a structured log value. +// A zero value is valid and represents an empty value. type Value struct { // Ensure forward compatibility by explicitly making this not comparable. noCmp [0]func() //nolint: unused // This is indeed used. @@ -264,7 +265,7 @@ func (v Value) Equal(w Value) bool { } } -// An KeyValue is a key-value pair used to represent a log attribute (a +// A KeyValue is a key-value pair used to represent a log attribute (a // superset of [go.opentelemetry.io/otel/attribute.KeyValue]) and map item. type KeyValue struct { Key string @@ -276,42 +277,47 @@ func (a KeyValue) Equal(b KeyValue) bool { return a.Key == b.Key && a.Value.Equal(b.Value) } -// String returns an KeyValue for a string value. +// String returns a KeyValue for a string value. func String(key, value string) KeyValue { return KeyValue{key, StringValue(value)} } -// Int64 returns an KeyValue for an int64 value. +// Int64 returns a KeyValue for an int64 value. func Int64(key string, value int64) KeyValue { return KeyValue{key, Int64Value(value)} } -// Int returns an KeyValue for an int value. +// Int returns a KeyValue for an int value. func Int(key string, value int) KeyValue { return KeyValue{key, IntValue(value)} } -// Float64 returns an KeyValue for a float64 value. +// Float64 returns a KeyValue for a float64 value. func Float64(key string, value float64) KeyValue { return KeyValue{key, Float64Value(value)} } -// Bool returns an KeyValue for a bool value. +// Bool returns a KeyValue for a bool value. func Bool(key string, value bool) KeyValue { return KeyValue{key, BoolValue(value)} } -// Bytes returns an KeyValue for a []byte value. +// Bytes returns a KeyValue for a []byte value. func Bytes(key string, value []byte) KeyValue { return KeyValue{key, BytesValue(value)} } -// Slice returns an KeyValue for a []Value value. +// Slice returns a KeyValue for a []Value value. func Slice(key string, value ...Value) KeyValue { return KeyValue{key, SliceValue(value...)} } -// Map returns an KeyValue for a map value. +// Map returns a KeyValue for a map value. func Map(key string, value ...KeyValue) KeyValue { return KeyValue{key, MapValue(value...)} } + +// Empty returns a KeyValue with an empty value. +func Empty(key string) KeyValue { + return KeyValue{key, Value{}} +} diff --git a/log/keyvalue_test.go b/log/keyvalue_test.go index 098334b8f29..f7c6602de74 100644 --- a/log/keyvalue_test.go +++ b/log/keyvalue_test.go @@ -60,6 +60,7 @@ func TestValueEqual(t *testing.T) { log.MapValue( log.Slice("l", log.IntValue(3), log.StringValue("foo")), log.Bytes("b", []byte{3, 5, 7}), + log.Empty("e"), ), } for i, v1 := range vals { @@ -69,7 +70,7 @@ func TestValueEqual(t *testing.T) { } } -func TestEmpty(t *testing.T) { +func TestValueEmpty(t *testing.T) { v := log.Value{} t.Run("Value.Empty", func(t *testing.T) { assert.True(t, v.Empty()) @@ -246,6 +247,23 @@ func TestMap(t *testing.T) { }) } +func TestEmpty(t *testing.T) { + const key = "key" + kv := log.Empty(key) + + assert.Equal(t, key, kv.Key, "incorrect key") + assert.True(t, kv.Value.Empty(), "value not empty") + + v, k := kv.Value, log.KindEmpty + t.Run("AsBool", testErrKind(v.AsBool, "AsBool", k)) + t.Run("AsFloat64", testErrKind(v.AsFloat64, "AsFloat64", k)) + t.Run("AsInt64", testErrKind(v.AsInt64, "AsInt64", k)) + t.Run("AsString", testErrKind(v.AsString, "AsString", k)) + t.Run("AsBytes", testErrKind(v.AsBytes, "AsBytes", k)) + t.Run("AsSlice", testErrKind(v.AsSlice, "AsSlice", k)) + t.Run("AsMap", testErrKind(v.AsMap, "AsMap", k)) +} + type logSink struct { logr.LogSink