encoder.go

  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}