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