| // Copyright The OpenTelemetry Authors |
| // |
| // Licensed under the Apache License, Version 2.0 (the "License"); |
| // you may not use this file except in compliance with the License. |
| // You may obtain a copy of the License at |
| // |
| // http://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the License is distributed on an "AS IS" BASIS, |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| // See the License for the specific language governing permissions and |
| // limitations under the License. |
| |
| package label |
| |
| import ( |
| "bytes" |
| "sync" |
| "sync/atomic" |
| ) |
| |
| type ( |
| // Encoder is a mechanism for serializing a label set into a |
| // specific string representation that supports caching, to |
| // avoid repeated serialization. An example could be an |
| // exporter encoding the label set into a wire representation. |
| Encoder interface { |
| // Encode returns the serialized encoding of the label |
| // set using its Iterator. This result may be cached |
| // by a label.Set. |
| Encode(iterator Iterator) string |
| |
| // ID returns a value that is unique for each class of |
| // label encoder. Label encoders allocate these using |
| // `NewEncoderID`. |
| ID() EncoderID |
| } |
| |
| // EncoderID is used to identify distinct Encoder |
| // implementations, for caching encoded results. |
| EncoderID struct { |
| value uint64 |
| } |
| |
| // defaultLabelEncoder uses a sync.Pool of buffers to reduce |
| // the number of allocations used in encoding labels. This |
| // implementation encodes a comma-separated list of key=value, |
| // with '/'-escaping of '=', ',', and '\'. |
| defaultLabelEncoder struct { |
| // pool is a pool of labelset builders. The buffers in this |
| // pool grow to a size that most label encodings will not |
| // allocate new memory. |
| pool sync.Pool // *bytes.Buffer |
| } |
| ) |
| |
| // 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 as a conventional choice. |
| const escapeChar = '\\' |
| |
| var ( |
| _ Encoder = &defaultLabelEncoder{} |
| |
| // encoderIDCounter is for generating IDs for other label |
| // encoders. |
| encoderIDCounter uint64 |
| |
| defaultEncoderOnce sync.Once |
| defaultEncoderID = NewEncoderID() |
| defaultEncoderInstance *defaultLabelEncoder |
| ) |
| |
| // NewEncoderID returns a unique label encoder ID. It should be |
| // called once per each type of label encoder. Preferably in init() or |
| // in var definition. |
| func NewEncoderID() EncoderID { |
| return EncoderID{value: atomic.AddUint64(&encoderIDCounter, 1)} |
| } |
| |
| // DefaultEncoder returns a label encoder that encodes labels |
| // in such a way that each escaped label's key is followed by an equal |
| // sign and then by an escaped label's value. All key-value pairs are |
| // separated by a comma. |
| // |
| // Escaping is done by prepending a backslash before either a |
| // backslash, equal sign or a comma. |
| func DefaultEncoder() Encoder { |
| defaultEncoderOnce.Do(func() { |
| defaultEncoderInstance = &defaultLabelEncoder{ |
| pool: sync.Pool{ |
| New: func() interface{} { |
| return &bytes.Buffer{} |
| }, |
| }, |
| } |
| }) |
| return defaultEncoderInstance |
| } |
| |
| // Encode is a part of an implementation of the LabelEncoder |
| // interface. |
| func (d *defaultLabelEncoder) Encode(iter Iterator) string { |
| buf := d.pool.Get().(*bytes.Buffer) |
| defer d.pool.Put(buf) |
| buf.Reset() |
| |
| for iter.Next() { |
| i, keyValue := iter.IndexedLabel() |
| if i > 0 { |
| _, _ = buf.WriteRune(',') |
| } |
| copyAndEscape(buf, string(keyValue.Key)) |
| |
| _, _ = buf.WriteRune('=') |
| |
| if keyValue.Value.Type() == STRING { |
| copyAndEscape(buf, keyValue.Value.AsString()) |
| } else { |
| _, _ = buf.WriteString(keyValue.Value.Emit()) |
| } |
| } |
| return buf.String() |
| } |
| |
| // ID is a part of an implementation of the LabelEncoder interface. |
| func (*defaultLabelEncoder) ID() EncoderID { |
| return defaultEncoderID |
| } |
| |
| // copyAndEscape escapes `=`, `,` and its own escape character (`\`), |
| // making the default encoding unique. |
| func copyAndEscape(buf *bytes.Buffer, val string) { |
| for _, ch := range val { |
| switch ch { |
| case '=', ',', escapeChar: |
| buf.WriteRune(escapeChar) |
| } |
| buf.WriteRune(ch) |
| } |
| } |
| |
| // Valid returns true if this encoder ID was allocated by |
| // `NewEncoderID`. Invalid encoder IDs will not be cached. |
| func (id EncoderID) Valid() bool { |
| return id.value != 0 |
| } |