| // Copyright The OpenTelemetry Authors | |
| // SPDX-License-Identifier: Apache-2.0 | |
| package attribute | |
| import ( | |
| "bytes" | |
| "sync" | |
| "sync/atomic" | |
| ) | |
| type ( | |
| // Encoder is a mechanism for serializing an attribute set into a specific | |
| // string representation that supports caching, to avoid repeated | |
| // serialization. An example could be an exporter encoding the attribute | |
| // set into a wire representation. | |
| Encoder interface { | |
| // Encode returns the serialized encoding of the attribute set using | |
| // its Iterator. This result may be cached by an attribute.Set. | |
| Encode(iterator Iterator) string | |
| // ID returns a value that is unique for each class of attribute | |
| // encoder. Attribute encoders allocate these using `NewEncoderID`. | |
| ID() EncoderID | |
| } | |
| // EncoderID is used to identify distinct Encoder | |
| // implementations, for caching encoded results. | |
| EncoderID struct { | |
| value uint64 | |
| } | |
| // defaultAttrEncoder uses a sync.Pool of buffers to reduce the number of | |
| // allocations used in encoding attributes. This implementation encodes a | |
| // comma-separated list of key=value, with '/'-escaping of '=', ',', and | |
| // '\'. | |
| defaultAttrEncoder struct { | |
| // pool is a pool of attribute set builders. The buffers in this pool | |
| // grow to a size that most attribute encodings will not allocate new | |
| // memory. | |
| pool sync.Pool // *bytes.Buffer | |
| } | |
| ) | |
| // escapeChar is used to ensure uniqueness of the attribute 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 = &defaultAttrEncoder{} | |
| // encoderIDCounter is for generating IDs for other attribute encoders. | |
| encoderIDCounter atomic.Uint64 | |
| defaultEncoderOnce sync.Once | |
| defaultEncoderID = NewEncoderID() | |
| defaultEncoderInstance *defaultAttrEncoder | |
| ) | |
| // NewEncoderID returns a unique attribute encoder ID. It should be called | |
| // once per each type of attribute encoder. Preferably in init() or in var | |
| // definition. | |
| func NewEncoderID() EncoderID { | |
| return EncoderID{value: encoderIDCounter.Add(1)} | |
| } | |
| // DefaultEncoder returns an attribute encoder that encodes attributes in such | |
| // a way that each escaped attribute's key is followed by an equal sign and | |
| // then by an escaped attribute'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 = &defaultAttrEncoder{ | |
| pool: sync.Pool{ | |
| New: func() any { | |
| return &bytes.Buffer{} | |
| }, | |
| }, | |
| } | |
| }) | |
| return defaultEncoderInstance | |
| } | |
| // Encode is a part of an implementation of the AttributeEncoder interface. | |
| func (d *defaultAttrEncoder) Encode(iter Iterator) string { | |
| buf := d.pool.Get().(*bytes.Buffer) | |
| defer d.pool.Put(buf) | |
| buf.Reset() | |
| for iter.Next() { | |
| i, keyValue := iter.IndexedAttribute() | |
| if i > 0 { | |
| _ = buf.WriteByte(',') | |
| } | |
| copyAndEscape(buf, string(keyValue.Key)) | |
| _ = buf.WriteByte('=') | |
| if keyValue.Value.Type() == STRING { | |
| copyAndEscape(buf, keyValue.Value.AsString()) | |
| } else { | |
| _, _ = buf.WriteString( | |
| keyValue.Value.Emit(), | |
| ) //nolint:staticcheck // Preserve the existing default encoder output. | |
| } | |
| } | |
| return buf.String() | |
| } | |
| // ID is a part of an implementation of the AttributeEncoder interface. | |
| func (*defaultAttrEncoder) 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.WriteByte(escapeChar) | |
| } | |
| _, _ = buf.WriteRune(ch) | |
| } | |
| } | |
| // Valid reports whether this encoder ID was allocated by | |
| // [NewEncoderID]. Invalid encoder IDs will not be cached. | |
| func (id EncoderID) Valid() bool { | |
| return id.value != 0 | |
| } |