151 lines
		
	
	
		
			4.4 KiB
		
	
	
	
		
			Go
		
	
	
	
			
		
		
	
	
			151 lines
		
	
	
		
			4.4 KiB
		
	
	
	
		
			Go
		
	
	
	
// 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 "go.opentelemetry.io/otel/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
 | 
						|
}
 |