136 lines
		
	
	
		
			4.0 KiB
		
	
	
	
		
			Go
		
	
	
	
			
		
		
	
	
			136 lines
		
	
	
		
			4.0 KiB
		
	
	
	
		
			Go
		
	
	
	
| // Copyright The OpenTelemetry Authors
 | |
| // SPDX-License-Identifier: Apache-2.0
 | |
| 
 | |
| package attribute // import "go.opentelemetry.io/otel/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 a 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 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: atomic.AddUint64(&encoderIDCounter, 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() interface{} {
 | |
| 					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.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 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.WriteRune(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
 | |
| }
 |