From d9d2b3a87fd467cee8b924632dee727ec5c84f09 Mon Sep 17 00:00:00 2001 From: sonalgaud12 Date: Wed, 8 Apr 2026 00:20:15 +0530 Subject: [PATCH 1/2] attribute: add Value.String() and deprecate Emit; update usages --- CHANGELOG.md | 1 + attribute/key_test.go | 36 +++++++++++++++++++------------- attribute/value.go | 12 +++++++++-- exporters/prometheus/exporter.go | 8 +++---- 4 files changed, 36 insertions(+), 21 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9d5f5a43230..2f5e6abf4a5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm ### Added - Add `ByteSlice` and `ByteSliceValue` functions for new `BYTESLICE` attribute type in `go.opentelemetry.io/otel/attribute`. (#7948) +- Add `String` method to `attribute.Value` in `go.opentelemetry.io/otel/attribute` implementing `fmt.Stringer`; deprecate `Emit` in favor of `String`; update `go.opentelemetry.io/otel/exporters/prometheus` to use `Value.String`. (#8144) ### Changed diff --git a/attribute/key_test.go b/attribute/key_test.go index c33aa266beb..705eee90bb1 100644 --- a/attribute/key_test.go +++ b/attribute/key_test.go @@ -52,79 +52,85 @@ func TestJSONValue(t *testing.T) { string(data)) } -func TestEmit(t *testing.T) { +func TestString(t *testing.T) { for _, testcase := range []struct { name string v attribute.Value want string }{ { - name: `test Key.Emit() can emit a string representing self.BOOL`, + name: `test Value.String() can emit a string representing self.BOOL`, v: attribute.BoolValue(true), want: "true", }, { - name: `test Key.Emit() can emit a string representing self.BOOLSLICE`, + name: `test Value.String() can emit a string representing self.BOOLSLICE`, v: attribute.BoolSliceValue([]bool{true, false, true}), want: `[true false true]`, }, { - name: `test Key.Emit() can emit a string representing self.INT64SLICE`, + name: `test Value.String() can emit a string representing self.INT64SLICE`, v: attribute.Int64SliceValue([]int64{1, 42}), want: `[1,42]`, }, { - name: `test Key.Emit() can emit a string representing self.INT64`, + name: `test Value.String() can emit a string representing self.INT64`, v: attribute.Int64Value(42), want: "42", }, { - name: `test Key.Emit() can representing an int value`, + name: `test Value.String() can represent an int value`, v: attribute.IntValue(7), want: "7", }, { - name: `test Key.Emit() can represent an []int value`, + name: `test Value.String() can represent an []int value`, v: attribute.IntSliceValue([]int{1, 2, 3}), want: `[1,2,3]`, }, { - name: `test Key.Emit() can emit a string representing self.FLOAT64SLICE`, + name: `test Value.String() can emit a string representing self.FLOAT64SLICE`, v: attribute.Float64SliceValue([]float64{1.0, 42.5}), want: `[1,42.5]`, }, { - name: `test Key.Emit() can emit a string representing self.FLOAT64`, + name: `test Value.String() can emit a string representing self.FLOAT64`, v: attribute.Float64Value(42.1), want: "42.1", }, { - name: `test Key.Emit() can emit a string representing self.STRING`, + name: `test Value.String() can emit a string representing self.STRING`, v: attribute.StringValue("foo"), want: "foo", }, { - name: `test Key.Emit() can emit a string representing self.STRINGSLICE`, + name: `test Value.String() can emit a string representing self.STRINGSLICE`, v: attribute.StringSliceValue([]string{"foo", "bar"}), want: `["foo","bar"]`, }, { - name: `test Key.Emit() can emit a string representing self.BYTESLICE`, + name: `test Value.String() can emit a string representing self.BYTESLICE`, v: attribute.ByteSliceValue([]byte("foo")), want: "Zm9v", }, { - name: `test Key.Emit() can emit a string representing self.EMPTY`, + name: `test Value.String() can emit a string representing self.EMPTY`, v: attribute.Value{}, want: "", }, } { t.Run(testcase.name, func(t *testing.T) { - // proto: func (v attribute.Value) Emit() string { - have := testcase.v.Emit() + have := testcase.v.String() if have != testcase.want { t.Errorf("Want: %s, but have: %s", testcase.want, have) } }) } } + +func TestEmitDeprecated(t *testing.T) { + v := attribute.StringValue("foo") + if v.Emit() != v.String() { + t.Errorf("Emit() should delegate to String(), got %q and %q", v.Emit(), v.String()) + } +} diff --git a/attribute/value.go b/attribute/value.go index c0d340592e2..edd0511b91e 100644 --- a/attribute/value.go +++ b/attribute/value.go @@ -268,8 +268,9 @@ func (v Value) AsInterface() any { return unknownValueType{} } -// Emit returns a string representation of Value's data. -func (v Value) Emit() string { +// String returns a string representation of Value's data. +// It implements the [fmt.Stringer] interface. +func (v Value) String() string { switch v.Type() { case BOOLSLICE: return fmt.Sprint(v.asBoolSlice()) @@ -308,6 +309,13 @@ func (v Value) Emit() string { } } +// Emit returns a string representation of Value's data. +// +// Deprecated: Use [Value.String] instead. +func (v Value) Emit() string { + return v.String() +} + // MarshalJSON returns the JSON encoding of the Value. func (v Value) MarshalJSON() ([]byte, error) { var jsonVal struct { diff --git a/exporters/prometheus/exporter.go b/exporters/prometheus/exporter.go index 0f2259af381..a37cf3c6cfb 100644 --- a/exporters/prometheus/exporter.go +++ b/exporters/prometheus/exporter.go @@ -617,7 +617,7 @@ func getAttrs(attrs attribute.Set, labelNamer otlptranslator.LabelNamer) ([]stri for itr.Next() { kv := itr.Attribute() keys = append(keys, string(kv.Key)) - values = append(values, kv.Value.Emit()) + values = append(values, kv.Value.String()) } } else { // It sanitizes invalid characters and handles duplicate keys @@ -631,10 +631,10 @@ func getAttrs(attrs attribute.Set, labelNamer otlptranslator.LabelNamer) ([]stri return nil, nil, err } if _, ok := keysMap[key]; !ok { - keysMap[key] = []string{kv.Value.Emit()} + keysMap[key] = []string{kv.Value.String()} } else { // if the sanitized key is a duplicate, append to the list of keys - keysMap[key] = append(keysMap[key], kv.Value.Emit()) + keysMap[key] = append(keysMap[key], kv.Value.String()) } } for key, vals := range keysMap { @@ -812,7 +812,7 @@ func attributesToLabels(attrs []attribute.KeyValue, labelNamer otlptranslator.La if err != nil { return nil, err } - labels[name] = attr.Value.Emit() + labels[name] = attr.Value.String() } return labels, nil } From 8031d1502fb3d01e7c5f65402d7c7abae7e59946 Mon Sep 17 00:00:00 2001 From: sonalgaud12 Date: Wed, 8 Apr 2026 00:22:19 +0530 Subject: [PATCH 2/2] channgelog --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2f5e6abf4a5..54e344af361 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,7 +11,7 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm ### Added - Add `ByteSlice` and `ByteSliceValue` functions for new `BYTESLICE` attribute type in `go.opentelemetry.io/otel/attribute`. (#7948) -- Add `String` method to `attribute.Value` in `go.opentelemetry.io/otel/attribute` implementing `fmt.Stringer`; deprecate `Emit` in favor of `String`; update `go.opentelemetry.io/otel/exporters/prometheus` to use `Value.String`. (#8144) +- Add `String` method to `attribute.Value` in `go.opentelemetry.io/otel/attribute` implementing `fmt.Stringer`; deprecate `Emit` in favor of `String`; update `go.opentelemetry.io/otel/exporters/prometheus` to use `Value.String`. (#8154) ### Changed