From e6e44dee90ec354369e98bb32e176878d95fea63 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20Paj=C4=85k?= Date: Tue, 2 Apr 2024 10:50:07 +0200 Subject: [PATCH] log: Add String method to Value and KeyValue (#5117) --- CHANGELOG.md | 1 + log/keyvalue.go | 43 +++++++++++++++++++++++++++++++++++++++++++ log/keyvalue_test.go | 19 +++++++++++++++++++ 3 files changed, 63 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index c6a9a9c1aad..3a5af468a04 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -24,6 +24,7 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm - Add support for `Summary` metrics in the `go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp` and `go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc` exporters. (#5100) - Add `otel.scope.name` and `otel.scope.version` tags to spans exported by `go.opentelemetry.io/otel/exporters/zipkin`. (#5108) - Add support for `AddLink` to `go.opentelemetry.io/otel/bridge/opencensus`. (#5116) +- Add `String` method to `Value` and `KeyValue` in `go.opentelemetry.io/otel/log`. (#5117) ### Changed diff --git a/log/keyvalue.go b/log/keyvalue.go index da3d55c4fc8..10920d21f4a 100644 --- a/log/keyvalue.go +++ b/log/keyvalue.go @@ -8,8 +8,10 @@ package log // import "go.opentelemetry.io/otel/log" import ( "bytes" "errors" + "fmt" "math" "slices" + "strconv" "unsafe" "go.opentelemetry.io/otel/internal/global" @@ -265,6 +267,39 @@ func (v Value) Equal(w Value) bool { } } +// String returns Value's value as a string, formatted like [fmt.Sprint]. +// +// The returned string is meant for debugging; +// the string representation is not stable. +func (v Value) String() string { + switch v.Kind() { + case KindString: + return v.asString() + case KindInt64: + return strconv.FormatInt(int64(v.num), 10) + case KindFloat64: + return strconv.FormatFloat(v.asFloat64(), 'g', -1, 64) + case KindBool: + return strconv.FormatBool(v.asBool()) + case KindBytes: + return fmt.Sprint(v.asBytes()) + case KindMap: + return fmt.Sprint(v.asMap()) + case KindSlice: + return fmt.Sprint(v.asSlice()) + case KindEmpty: + return "" + default: + // Try to handle this as gracefully as possible. + // + // Don't panic here. The goal here is to have developers find this + // first if a slog.Kind is is not handled. It is + // preferable to have user's open issue asking why their attributes + // have a "unhandled: " prefix than say that their code is panicking. + return fmt.Sprintf("", v.Kind()) + } +} + // 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 { @@ -321,3 +356,11 @@ func Map(key string, value ...KeyValue) KeyValue { func Empty(key string) KeyValue { return KeyValue{key, Value{}} } + +// String returns key-value pair as a string, formatted like "key:value". +// +// The returned string is meant for debugging; +// the string representation is not stable. +func (a KeyValue) String() string { + return fmt.Sprintf("%s:%s", a.Key, a.Value) +} diff --git a/log/keyvalue_test.go b/log/keyvalue_test.go index f7c6602de74..2f0211160cf 100644 --- a/log/keyvalue_test.go +++ b/log/keyvalue_test.go @@ -264,6 +264,25 @@ func TestEmpty(t *testing.T) { t.Run("AsMap", testErrKind(v.AsMap, "AsMap", k)) } +func TestValueString(t *testing.T) { + for _, test := range []struct { + v log.Value + want string + }{ + {log.Int64Value(-3), "-3"}, + {log.Float64Value(.15), "0.15"}, + {log.BoolValue(true), "true"}, + {log.StringValue("foo"), "foo"}, + {log.BytesValue([]byte{2, 4, 6}), "[2 4 6]"}, + {log.SliceValue(log.IntValue(3), log.StringValue("foo")), "[3 foo]"}, + {log.MapValue(log.Int("a", 1), log.Bool("b", true)), "[a:1 b:true]"}, + {log.Value{}, ""}, + } { + got := test.v.String() + assert.Equal(t, test.want, got) + } +} + type logSink struct { logr.LogSink