Skip to content

[DO NOT MERGE] attribute: add String method for KeyValue#8205

Draft
pellared wants to merge 16 commits into
open-telemetry:mainfrom
pellared:kv-str
Draft

[DO NOT MERGE] attribute: add String method for KeyValue#8205
pellared wants to merge 16 commits into
open-telemetry:mainfrom
pellared:kv-str

Conversation

@pellared
Copy link
Copy Markdown
Member

@pellared pellared commented Apr 14, 2026

Blocked by:

Fixes #8146
Towards #7810

I decided to not implement "array fast paths" for the slice attribute types as currently KeyValue.String is only/mainly for debugging and I didn't want to make the PR bigger. I can make a separate PR if someone wants me to address it. EDIT: So far Copilot is not happy with it #8205 (comment) 😉

Benchmarks:

$ go test -run=^$ -bench=./.*String
goos: linux
goarch: amd64
pkg: go.opentelemetry.io/otel/attribute
cpu: 13th Gen Intel(R) Core(TM) i7-13800H
BenchmarkBool/String-20                 114869631               10.42 ns/op            0 B/op          0 allocs/op
BenchmarkBool/KeyValueString-20         23787632                57.34 ns/op           16 B/op          1 allocs/op
BenchmarkInt/String-20                  100000000               11.21 ns/op            0 B/op          0 allocs/op
BenchmarkInt/KeyValueString-20          25864422                52.07 ns/op           16 B/op          1 allocs/op
BenchmarkInt64/String-20                107127098               11.23 ns/op            0 B/op          0 allocs/op
BenchmarkInt64/KeyValueString-20        24733572                51.34 ns/op           16 B/op          1 allocs/op
BenchmarkFloat64/String-20              22436743                51.34 ns/op            2 B/op          1 allocs/op
BenchmarkFloat64/KeyValueString-20       8951968               127.5 ns/op            16 B/op          1 allocs/op
BenchmarkString/String-20               100000000               10.08 ns/op            0 B/op          0 allocs/op
BenchmarkString/KeyValueString-20       23652170                56.59 ns/op           16 B/op          1 allocs/op
BenchmarkByteSlice/String-20            21427165                48.63 ns/op           16 B/op          1 allocs/op
BenchmarkByteSlice/KeyValueString-20    17251906                71.10 ns/op           24 B/op          1 allocs/op

@pellared pellared marked this pull request as ready for review April 14, 2026 21:53
@codecov
Copy link
Copy Markdown

codecov Bot commented Apr 14, 2026

Codecov Report

❌ Patch coverage is 96.77419% with 2 lines in your changes missing coverage. Please review.
✅ Project coverage is 82.9%. Comparing base (a410ac5) to head (61c7420).

Files with missing lines Patch % Lines
attribute/kv.go 96.6% 2 Missing ⚠️
Additional details and impacted files

Impacted file tree graph

@@          Coverage Diff          @@
##            main   #8205   +/-   ##
=====================================
  Coverage   82.9%   82.9%           
=====================================
  Files        314     314           
  Lines      25083   25143   +60     
=====================================
+ Hits       20799   20856   +57     
- Misses      3910    3913    +3     
  Partials     374     374           
Files with missing lines Coverage Δ
attribute/value.go 97.6% <100.0%> (ø)
attribute/kv.go 97.7% <96.6%> (-2.3%) ⬇️

... and 1 file with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@MrAlias
Copy link
Copy Markdown
Contributor

MrAlias commented Apr 14, 2026

Is there precedence in OTel for this form of a key-value encoding?

@pellared
Copy link
Copy Markdown
Member Author

pellared commented Apr 15, 2026

Is there precedence in OTel for this form of a key-value encoding?

There is some precedence, but I would frame it as debug-output precedent rather than a spec-defined canonical encoding.

Inside this repo, log.KeyValue.String() already uses the same key:value form and explicitly documents it as debugging-only / not stable. We also use the same tuple shape in attribute/hash_test.go when formatting []KeyValue for diagnostics. In our STDOUT exporters we also JSON encode the attribute.KeyValue. At the same time, we do not have a single canonical delimiter across the repo: attribute.DefaultEncoder() / sdk/resource.Resource.String() use key=value,..., and baggage / tracestate also use = where they follow W3C-defined encodings.

I also checked a couple of other SDKs: .NET’s and Python console exporters prints attributes as Key:Value, while Java has internal key-value debug encodings that use = or JSON depending on the structure. Therefore, that is no single canonical cross-SDK string form.

From the spec side, there it is not defined how a attribute pair should be formatted to a string. Yet, for maps the current spec actually says they SHOULD be represented as JSON objects. This was what we originally used as inspiration. I think this also makes biggest sense for us since e.g. log.Map accepts a slice of KeyValue.

Given that, I think the safest justification is: key:value is a debugging representation for an attribute tuple, with the Value portion following the existing AnyValue rules, and we should document it as non-stable just like log.KeyValue.String().
EDIT: Addressed via b2513ac

However, I also started to think if we should not follow https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/common/README.md#maps more closely and have something like "key":value with JSON string-escaping logic. This is also what actually the STDOUT exporters are doing.
EDIT: Addressed in ot have a single canonical delimiter across the repo: attribute.DefaultEncoder() / sdk/resource.Resource.String() use key=value,..., and baggage / tracestate also use = where they follow W3C-defined encodings.

I also checked a couple of other SDKs: .NET’s and Python console exporters prints attributes as Key:Value, while Java has internal key-value debug encodings that use = or JSON depending on the structure. Therefore, that is no single canonical cross-SDK string form.

If we think it is worth standardizing, I would be open to a small spec follow-up to define a recommended string form for an attribute pair in non-OTLP / debug contexts.
EDIT: I created open-telemetry/opentelemetry-specification#5028

@pellared pellared marked this pull request as draft April 15, 2026 11:17
@pellared pellared changed the title attribute: add String method for KeyValue [DO NOT MERGE] attribute: add String method for KeyValue Apr 15, 2026
Comment thread attribute/value.go Outdated
@pellared pellared marked this pull request as ready for review April 15, 2026 12:28
@pellared
Copy link
Copy Markdown
Member Author

pellared commented Apr 15, 2026

@open-telemetry/go-approvers, can you make a high-level review this PR? For sure a benchmark is missing (added in TODO). There is no need to "approve", but I want to have an agreement on the direction. Please also check #8205 (comment)

@dashpole
Copy link
Copy Markdown
Collaborator

I like standardizing this, and I think JSON is readable and universal. Do we want to go further, and able to print out a slice of attribute.KeyVal as [...] or an attribute.Set the same way too

@pellared
Copy link
Copy Markdown
Member Author

pellared commented Apr 15, 2026

Do we want to go further, and able to print out a slice of attribute.KeyVal as [...]

I am not sure in which places you want to have it applied. For sure this would be part of #7935.

or an attribute.Set the same way too

Good idea! I created #8209 for tracking this one.

@dashpole
Copy link
Copy Markdown
Collaborator

I am not sure in which places you want to have it applied.

The thing that came to mind was metricdatatest

@pellared
Copy link
Copy Markdown
Member Author

I am not sure in which places you want to have it applied.

The thing that came to mind was metricdatatest

I created #8210

@pellared pellared changed the title [DO NOT MERGE] attribute: add String method for KeyValue attribute: add String method for KeyValue Apr 20, 2026
@pellared
Copy link
Copy Markdown
Member Author

pellared commented Apr 20, 2026

@open-telemetry/go-approvers, this is ready for review

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds a String() method to attribute.KeyValue so it implements fmt.Stringer and prints a human-readable, spec-aligned JSON representation (intended for debugging), addressing the request from #8146 and advancing #7810.

Changes:

  • Implement attribute.KeyValue.String() that renders a single-entry JSON object using the non-OTLP attribute representation rules.
  • Add unit tests covering all attribute.Value kinds (including special float values) for KeyValue.String().
  • Update benchmarks to measure KeyValue.String() alongside Value.String() and Value.Emit(), and note the change in CHANGELOG.md.

Reviewed changes

Copilot reviewed 5 out of 5 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
attribute/kv.go Adds KeyValue.String() implementation using existing JSON append helpers.
attribute/kv_test.go Adds test coverage for KeyValue.String() across value types.
attribute/value.go Introduces/reuses sizing constants and updates slice builder growth estimates.
attribute/benchmark_test.go Refactors benchmarks to include KeyValue.String() and compare with Value.String()/Emit().
CHANGELOG.md Documents the new KeyValue.String() feature.

Comment thread attribute/value.go Outdated
Comment thread attribute/kv.go
Comment thread attribute/benchmark_test.go Outdated
pellared and others added 2 commits April 20, 2026 23:06
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
@pellared
Copy link
Copy Markdown
Member Author

Planning to merge tomorrow morning (CEST time).

@MrAlias
Copy link
Copy Markdown
Contributor

MrAlias commented Apr 21, 2026

Shouldn't this wait on open-telemetry/opentelemetry-specification#5028 to merge first?

@pellared
Copy link
Copy Markdown
Member Author

pellared commented Apr 21, 2026

Shouldn't this wait on open-telemetry/opentelemetry-specification#5028 to merge first?

I added in Go doc:

the string representation is not stable.

Note that even if it is merged in the spec, we should wait for the spec for stabilization... WDYT?

@MrAlias
Copy link
Copy Markdown
Contributor

MrAlias commented Apr 21, 2026

I want to be cautious about adding API to the attribute package. Given it is stable and widely used, I think waiting until we have confirmation on design is the prudent option here. +1 on waiting for the spec stabilization.

@pellared pellared changed the title attribute: add String method for KeyValue [DO NOT MERGE] attribute: add String method for KeyValue Apr 21, 2026
@pellared
Copy link
Copy Markdown
Member Author

pellared commented Apr 21, 2026

Changing to draft to prevent accidental merging.

@pellared pellared added the blocked:specification Waiting on clarification of the OpenTelemetry specification before progress can be made label Apr 21, 2026
@pellared pellared marked this pull request as draft April 21, 2026 19:41
pull Bot pushed a commit to thompson-tomo/opentelemetry-specification that referenced this pull request May 20, 2026
…emetry#5028)

### What

Adds in-development guidance for representing a single OpenTelemetry
attribute as a string in non-OTLP protocols.

The recommended form is a single-entry JSON object, for example:

```json
{"http.request.method":"GET"}
```

**Implementation:**
- open-telemetry/opentelemetry-go#8205 in OTel
Go we are using non-OTLP string represnetation for debugging

### Why

- open-telemetry/opentelemetry-go#7810

The narrower purpose is to fill a gap in the existing non-OTLP string
representation guidance. The specification already has stable guidance
for representing `AnyValue` values as strings when a non-OTLP protocol
cannot represent some `AnyValue` types natively. What is missing is the
corresponding representation for a single `Attribute`, which is a key
plus an `AnyValue`. This PR proposes that the single attribute is
represented as a single-member JSON object, with the member value
following the existing non-OTLP `AnyValue` representation rules.

I originally thought about `key:value` (and even `key=value`
beforehand), but it is ambiguous when `:` appears in keys or string
values and is not a robust debugging representation.

Using a JSON object improves clarity for debugging output by:

- aligning with existing non-OTLP, JSON-based guidance for complex
`AnyValue` types
- making the representation easier to read and copy when needed

This also is consistent with how OTel Java, .NET, Python, Go are
representing attributes in standard output exporters. Therefore, I think
it would be most familiar representation for the users.

Why not OTLP/JSON?
For the OTel Go use case, the compact (lossy) non-OTLP representation is
practical for human-readable output such as test failures. `String()` is
intended for debugging and logging, and its output is not expected to be
lossless nor suitable for deserialization. A canonical OTLP JSON
`KeyValue` would need wrapper fields such as
`{"key":"http.request.method","value":{"stringValue":"GET"}}`, which is
much more verbose than `{"http.request.method":"GET"}` and harder to
scan in failure messages.
Moreover, OTel Go `attribute.Value.String()` already follows the
non-OTLP `AnyValue` rules:
<open-telemetry/opentelemetry-go#8142> (as OTel
Go maintainers, we want to use an OTel-wide representation even for
debugging output). We want `attribute.KeyValue.String()` to follow the
same rules, but the string representation of a single attribute is not
yet specified; this PR fills that gap and is currently blocking
<open-telemetry/opentelemetry-go#8205>.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

blocked:specification Waiting on clarification of the OpenTelemetry specification before progress can be made

Projects

None yet

Development

Successfully merging this pull request may close these issues.

attribute: add KeyValue.String

5 participants