encoder.go raw

   1  // Copyright The OpenTelemetry Authors
   2  // SPDX-License-Identifier: Apache-2.0
   3  
   4  package attribute // import "go.opentelemetry.io/otel/attribute"
   5  
   6  import (
   7  	"bytes"
   8  	"sync"
   9  	"sync/atomic"
  10  )
  11  
  12  type (
  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.
  50  const escapeChar = '\\'
  51  
  52  var (
  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.
  66  func 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.
  77  func DefaultEncoder() Encoder {
  78  	defaultEncoderOnce.Do(func() {
  79  		defaultEncoderInstance = &defaultAttrEncoder{
  80  			pool: sync.Pool{
  81  				New: func() any {
  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.
  91  func (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.WriteByte(',')
 100  		}
 101  		copyAndEscape(buf, string(keyValue.Key))
 102  
 103  		_ = buf.WriteByte('=')
 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.
 115  func (*defaultAttrEncoder) ID() EncoderID {
 116  	return defaultEncoderID
 117  }
 118  
 119  // copyAndEscape escapes `=`, `,` and its own escape character (`\`),
 120  // making the default encoding unique.
 121  func copyAndEscape(buf *bytes.Buffer, val string) {
 122  	for _, ch := range val {
 123  		switch ch {
 124  		case '=', ',', escapeChar:
 125  			_ = buf.WriteByte(escapeChar)
 126  		}
 127  		_, _ = buf.WriteRune(ch)
 128  	}
 129  }
 130  
 131  // Valid reports whether this encoder ID was allocated by
 132  // [NewEncoderID]. Invalid encoder IDs will not be cached.
 133  func (id EncoderID) Valid() bool {
 134  	return id.value != 0
 135  }
 136