1// Copyright The OpenTelemetry Authors
2// SPDX-License-Identifier: Apache-2.0
3
4package attribute // import "go.opentelemetry.io/otel/attribute"
5
6import (
7 "bytes"
8 "sync"
9 "sync/atomic"
10)
11
12type (
13 // Encoder is a mechanism for serializing an attribute set into a specific
14 // string representation that supports caching, to avoid repeated
15 // serialization. An example could be an exporter encoding the attribute
16 // set into a wire representation.
17 Encoder interface {
18 // Encode returns the serialized encoding of the attribute set using
19 // its Iterator. This result may be cached by a attribute.Set.
20 Encode(iterator Iterator) string
21
22 // ID returns a value that is unique for each class of attribute
23 // encoder. Attribute encoders allocate these using `NewEncoderID`.
24 ID() EncoderID
25 }
26
27 // EncoderID is used to identify distinct Encoder
28 // implementations, for caching encoded results.
29 EncoderID struct {
30 value uint64
31 }
32
33 // defaultAttrEncoder uses a sync.Pool of buffers to reduce the number of
34 // allocations used in encoding attributes. This implementation encodes a
35 // comma-separated list of key=value, with '/'-escaping of '=', ',', and
36 // '\'.
37 defaultAttrEncoder struct {
38 // pool is a pool of attribute set builders. The buffers in this pool
39 // grow to a size that most attribute encodings will not allocate new
40 // memory.
41 pool sync.Pool // *bytes.Buffer
42 }
43)
44
45// escapeChar is used to ensure uniqueness of the attribute encoding where
46// keys or values contain either '=' or ','. Since there is no parser needed
47// for this encoding and its only requirement is to be unique, this choice is
48// arbitrary. Users will see these in some exporters (e.g., stdout), so the
49// backslash ('\') is used as a conventional choice.
50const escapeChar = '\\'
51
52var (
53 _ Encoder = &defaultAttrEncoder{}
54
55 // encoderIDCounter is for generating IDs for other attribute encoders.
56 encoderIDCounter uint64
57
58 defaultEncoderOnce sync.Once
59 defaultEncoderID = NewEncoderID()
60 defaultEncoderInstance *defaultAttrEncoder
61)
62
63// NewEncoderID returns a unique attribute encoder ID. It should be called
64// once per each type of attribute encoder. Preferably in init() or in var
65// definition.
66func NewEncoderID() EncoderID {
67 return EncoderID{value: atomic.AddUint64(&encoderIDCounter, 1)}
68}
69
70// DefaultEncoder returns an attribute encoder that encodes attributes in such
71// a way that each escaped attribute's key is followed by an equal sign and
72// then by an escaped attribute's value. All key-value pairs are separated by
73// a comma.
74//
75// Escaping is done by prepending a backslash before either a backslash, equal
76// sign or a comma.
77func DefaultEncoder() Encoder {
78 defaultEncoderOnce.Do(func() {
79 defaultEncoderInstance = &defaultAttrEncoder{
80 pool: sync.Pool{
81 New: func() interface{} {
82 return &bytes.Buffer{}
83 },
84 },
85 }
86 })
87 return defaultEncoderInstance
88}
89
90// Encode is a part of an implementation of the AttributeEncoder interface.
91func (d *defaultAttrEncoder) Encode(iter Iterator) string {
92 buf := d.pool.Get().(*bytes.Buffer)
93 defer d.pool.Put(buf)
94 buf.Reset()
95
96 for iter.Next() {
97 i, keyValue := iter.IndexedAttribute()
98 if i > 0 {
99 _, _ = buf.WriteRune(',')
100 }
101 copyAndEscape(buf, string(keyValue.Key))
102
103 _, _ = buf.WriteRune('=')
104
105 if keyValue.Value.Type() == STRING {
106 copyAndEscape(buf, keyValue.Value.AsString())
107 } else {
108 _, _ = buf.WriteString(keyValue.Value.Emit())
109 }
110 }
111 return buf.String()
112}
113
114// ID is a part of an implementation of the AttributeEncoder interface.
115func (*defaultAttrEncoder) ID() EncoderID {
116 return defaultEncoderID
117}
118
119// copyAndEscape escapes `=`, `,` and its own escape character (`\`),
120// making the default encoding unique.
121func copyAndEscape(buf *bytes.Buffer, val string) {
122 for _, ch := range val {
123 switch ch {
124 case '=', ',', escapeChar:
125 _, _ = buf.WriteRune(escapeChar)
126 }
127 _, _ = buf.WriteRune(ch)
128 }
129}
130
131// Valid returns true if this encoder ID was allocated by
132// `NewEncoderID`. Invalid encoder IDs will not be cached.
133func (id EncoderID) Valid() bool {
134 return id.value != 0
135}