Skip to content

Commit

Permalink
Make the default label encoding unique (#508)
Browse files Browse the repository at this point in the history
* Make the default label encoding unique

* More tests

* Cleanup
  • Loading branch information
jmacd authored Mar 4, 2020
1 parent ffdbc05 commit 148c9ce
Show file tree
Hide file tree
Showing 2 changed files with 48 additions and 2 deletions.
23 changes: 23 additions & 0 deletions sdk/metric/correct_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -189,6 +189,29 @@ func TestSDKLabelEncoder(t *testing.T) {

func TestDefaultLabelEncoder(t *testing.T) {
encoder := sdk.NewDefaultLabelEncoder()

encoded := encoder.Encode([]core.KeyValue{key.String("A", "B"), key.String("C", "D")})
require.Equal(t, `A=B,C=D`, encoded)

encoded = encoder.Encode([]core.KeyValue{key.String("A", "B,c=d"), key.String(`C\`, "D")})
require.Equal(t, `A=B\,c\=d,C\\=D`, encoded)

encoded = encoder.Encode([]core.KeyValue{key.String(`\`, `=`), key.String(`,`, `\`)})
require.Equal(t, `\\=\=,\,=\\`, encoded)

// Note: the label encoder does not sort or de-dup values,
// that is done in Labels(...).
encoded = encoder.Encode([]core.KeyValue{
key.Int("I", 1),
key.Uint("U", 1),
key.Int32("I32", 1),
key.Uint32("U32", 1),
key.Int64("I64", 1),
key.Uint64("U64", 1),
key.Float64("F64", 1),
key.Float64("F64", 1),
key.String("S", "1"),
key.Bool("B", true),
})
require.Equal(t, "I=1,U=1,I32=1,U32=1,I64=1,U64=1,F64=1,F64=1,S=1,B=true", encoded)
}
27 changes: 25 additions & 2 deletions sdk/metric/labelencoder.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,13 @@ import (
export "go.opentelemetry.io/otel/sdk/export/metric"
)

// escapeChar is used to ensure uniqueness of the label encoding where
// keys or values contain either '=' or ','. Since there is no parser
// needed for this encoding and its only requirement is to be unique,
// this choice is arbitrary. Users will see these in some exporters
// (e.g., stdout), so the backslash ('\') is used a conventional choice.
const escapeChar = '\\'

type defaultLabelEncoder struct {
// pool is a pool of labelset builders. The buffers in this
// pool grow to a size that most label encodings will not
Expand Down Expand Up @@ -54,9 +61,25 @@ func (d *defaultLabelEncoder) Encode(labels []core.KeyValue) string {
if i > 0 {
_, _ = buf.WriteRune(',')
}
_, _ = buf.WriteString(string(kv.Key))
copyAndEscape(buf, string(kv.Key))

_, _ = buf.WriteRune('=')
_, _ = buf.WriteString(kv.Value.Emit())

if kv.Value.Type() == core.STRING {
copyAndEscape(buf, kv.Value.AsString())
} else {
_, _ = buf.WriteString(kv.Value.Emit())
}
}
return buf.String()
}

func copyAndEscape(buf *bytes.Buffer, val string) {
for _, ch := range val {
switch ch {
case '=', ',', escapeChar:
buf.WriteRune(escapeChar)
}
buf.WriteRune(ch)
}
}

0 comments on commit 148c9ce

Please sign in to comment.