You've already forked opentelemetry-go
mirror of
https://github.com/open-telemetry/opentelemetry-go.git
synced 2026-06-03 18:35:08 +02:00
Comply with W3C Baggage specification limits (#7880)
Updates the baggage implementation to comply with https://www.w3.org/TR/baggage/#limits: - Changed maxMembers from 180 to 64 (the W3C compliance requirement) > The resulting baggage-string contains 64 list-members or less. - Removed maxBytesPerMembers (4096) - this per-member limit was not part of the W3C spec - Added limit checking in extractMultiBaggage for multiple baggage headers: - Checks combined byte size across all headers (max 8192 bytes) - Checks combined member count across all headers (max 64 members) This uses non-deterministic truncation when handling baggage limits.
This commit is contained in:
@@ -15,6 +15,12 @@ The next release will require at least [Go 1.25].
|
|||||||
|
|
||||||
- Support testing of [Go 1.26]. (#7902)
|
- Support testing of [Go 1.26]. (#7902)
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- Update `Baggage` in `go.opentelemetry.io/otel/propagation` and `Parse` and `New` in `go.opentelemetry.io/otel/baggage` to comply with W3C Baggage specification limits.
|
||||||
|
`New` and `Parse` now return partial baggage along with an error when limits are exceeded.
|
||||||
|
Errors from baggage extraction are reported to the global error handler. (#7880)
|
||||||
|
|
||||||
<!-- Released section -->
|
<!-- Released section -->
|
||||||
<!-- Don't change this section unless doing release -->
|
<!-- Don't change this section unless doing release -->
|
||||||
|
|
||||||
|
|||||||
+83
-26
@@ -14,8 +14,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
maxMembers = 180
|
maxMembers = 64
|
||||||
maxBytesPerMembers = 4096
|
|
||||||
maxBytesPerBaggageString = 8192
|
maxBytesPerBaggageString = 8192
|
||||||
|
|
||||||
listDelimiter = ","
|
listDelimiter = ","
|
||||||
@@ -29,7 +28,6 @@ var (
|
|||||||
errInvalidProperty = errors.New("invalid baggage list-member property")
|
errInvalidProperty = errors.New("invalid baggage list-member property")
|
||||||
errInvalidMember = errors.New("invalid baggage list-member")
|
errInvalidMember = errors.New("invalid baggage list-member")
|
||||||
errMemberNumber = errors.New("too many list-members in baggage-string")
|
errMemberNumber = errors.New("too many list-members in baggage-string")
|
||||||
errMemberBytes = errors.New("list-member too large")
|
|
||||||
errBaggageBytes = errors.New("baggage-string too large")
|
errBaggageBytes = errors.New("baggage-string too large")
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -309,10 +307,6 @@ func newInvalidMember() Member {
|
|||||||
// an error if the input is invalid according to the W3C Baggage
|
// an error if the input is invalid according to the W3C Baggage
|
||||||
// specification.
|
// specification.
|
||||||
func parseMember(member string) (Member, error) {
|
func parseMember(member string) (Member, error) {
|
||||||
if n := len(member); n > maxBytesPerMembers {
|
|
||||||
return newInvalidMember(), fmt.Errorf("%w: %d", errMemberBytes, n)
|
|
||||||
}
|
|
||||||
|
|
||||||
var props properties
|
var props properties
|
||||||
keyValue, properties, found := strings.Cut(member, propertyDelimiter)
|
keyValue, properties, found := strings.Cut(member, propertyDelimiter)
|
||||||
if found {
|
if found {
|
||||||
@@ -430,6 +424,10 @@ type Baggage struct { //nolint:golint
|
|||||||
// New returns a new valid Baggage. It returns an error if it results in a
|
// New returns a new valid Baggage. It returns an error if it results in a
|
||||||
// Baggage exceeding limits set in that specification.
|
// Baggage exceeding limits set in that specification.
|
||||||
//
|
//
|
||||||
|
// If the resulting Baggage exceeds the maximum allowed members or bytes,
|
||||||
|
// members are dropped until the limits are satisfied and an error is returned
|
||||||
|
// along with the partial result.
|
||||||
|
//
|
||||||
// It expects all the provided members to have already been validated.
|
// It expects all the provided members to have already been validated.
|
||||||
func New(members ...Member) (Baggage, error) {
|
func New(members ...Member) (Baggage, error) {
|
||||||
if len(members) == 0 {
|
if len(members) == 0 {
|
||||||
@@ -441,7 +439,6 @@ func New(members ...Member) (Baggage, error) {
|
|||||||
if !m.hasData {
|
if !m.hasData {
|
||||||
return Baggage{}, errInvalidMember
|
return Baggage{}, errInvalidMember
|
||||||
}
|
}
|
||||||
|
|
||||||
// OpenTelemetry resolves duplicates by last-one-wins.
|
// OpenTelemetry resolves duplicates by last-one-wins.
|
||||||
b[m.key] = baggage.Item{
|
b[m.key] = baggage.Item{
|
||||||
Value: m.value,
|
Value: m.value,
|
||||||
@@ -449,17 +446,42 @@ func New(members ...Member) (Baggage, error) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check member numbers after deduplication.
|
var truncateErr error
|
||||||
|
|
||||||
|
// Check member count after deduplication.
|
||||||
if len(b) > maxMembers {
|
if len(b) > maxMembers {
|
||||||
return Baggage{}, errMemberNumber
|
truncateErr = errors.Join(truncateErr, errMemberNumber)
|
||||||
|
for k := range b {
|
||||||
|
if len(b) <= maxMembers {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
delete(b, k)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bag := Baggage{b}
|
// Check byte size and drop members if necessary.
|
||||||
if n := len(bag.String()); n > maxBytesPerBaggageString {
|
totalBytes := 0
|
||||||
return Baggage{}, fmt.Errorf("%w: %d", errBaggageBytes, n)
|
first := true
|
||||||
|
for k := range b {
|
||||||
|
m := Member{
|
||||||
|
key: k,
|
||||||
|
value: b[k].Value,
|
||||||
|
properties: fromInternalProperties(b[k].Properties),
|
||||||
|
}
|
||||||
|
memberSize := len(m.String())
|
||||||
|
if !first {
|
||||||
|
memberSize++ // comma separator
|
||||||
|
}
|
||||||
|
if totalBytes+memberSize > maxBytesPerBaggageString {
|
||||||
|
truncateErr = errors.Join(truncateErr, fmt.Errorf("%w: %d", errBaggageBytes, totalBytes+memberSize))
|
||||||
|
delete(b, k)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
totalBytes += memberSize
|
||||||
|
first = false
|
||||||
}
|
}
|
||||||
|
|
||||||
return bag, nil
|
return Baggage{b}, truncateErr
|
||||||
}
|
}
|
||||||
|
|
||||||
// Parse attempts to decode a baggage-string from the passed string. It
|
// Parse attempts to decode a baggage-string from the passed string. It
|
||||||
@@ -470,36 +492,71 @@ func New(members ...Member) (Baggage, error) {
|
|||||||
// defined (reading left-to-right) will be the only one kept. This diverges
|
// defined (reading left-to-right) will be the only one kept. This diverges
|
||||||
// from the W3C Baggage specification which allows duplicate list-members, but
|
// from the W3C Baggage specification which allows duplicate list-members, but
|
||||||
// conforms to the OpenTelemetry Baggage specification.
|
// conforms to the OpenTelemetry Baggage specification.
|
||||||
|
//
|
||||||
|
// If the baggage-string exceeds the maximum allowed members (64) or bytes
|
||||||
|
// (8192), members are dropped until the limits are satisfied and an error is
|
||||||
|
// returned along with the partial result.
|
||||||
|
//
|
||||||
|
// Invalid members are skipped and the error is returned along with the
|
||||||
|
// partial result containing the valid members.
|
||||||
func Parse(bStr string) (Baggage, error) {
|
func Parse(bStr string) (Baggage, error) {
|
||||||
if bStr == "" {
|
if bStr == "" {
|
||||||
return Baggage{}, nil
|
return Baggage{}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if n := len(bStr); n > maxBytesPerBaggageString {
|
|
||||||
return Baggage{}, fmt.Errorf("%w: %d", errBaggageBytes, n)
|
|
||||||
}
|
|
||||||
|
|
||||||
b := make(baggage.List)
|
b := make(baggage.List)
|
||||||
|
sizes := make(map[string]int) // Track per-key byte sizes
|
||||||
|
var totalBytes int
|
||||||
|
var truncateErr error
|
||||||
for memberStr := range strings.SplitSeq(bStr, listDelimiter) {
|
for memberStr := range strings.SplitSeq(bStr, listDelimiter) {
|
||||||
|
// Check member count limit.
|
||||||
|
if len(b) >= maxMembers {
|
||||||
|
truncateErr = errors.Join(truncateErr, errMemberNumber)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
m, err := parseMember(memberStr)
|
m, err := parseMember(memberStr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return Baggage{}, err
|
truncateErr = errors.Join(truncateErr, err)
|
||||||
|
continue // skip invalid member, keep processing
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check byte size limit.
|
||||||
|
// Account for comma separator between members.
|
||||||
|
memberBytes := len(m.String())
|
||||||
|
_, existingKey := b[m.key]
|
||||||
|
if !existingKey && len(b) > 0 {
|
||||||
|
memberBytes++ // comma separator only for new keys
|
||||||
|
}
|
||||||
|
|
||||||
|
// Calculate new totalBytes if we add/overwrite this key
|
||||||
|
var newTotalBytes int
|
||||||
|
if oldSize, exists := sizes[m.key]; exists {
|
||||||
|
// Overwriting existing key: subtract old size, add new size
|
||||||
|
newTotalBytes = totalBytes - oldSize + memberBytes
|
||||||
|
} else {
|
||||||
|
// New key
|
||||||
|
newTotalBytes = totalBytes + memberBytes
|
||||||
|
}
|
||||||
|
|
||||||
|
if newTotalBytes > maxBytesPerBaggageString {
|
||||||
|
truncateErr = errors.Join(truncateErr, errBaggageBytes)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
// OpenTelemetry resolves duplicates by last-one-wins.
|
// OpenTelemetry resolves duplicates by last-one-wins.
|
||||||
b[m.key] = baggage.Item{
|
b[m.key] = baggage.Item{
|
||||||
Value: m.value,
|
Value: m.value,
|
||||||
Properties: m.properties.asInternal(),
|
Properties: m.properties.asInternal(),
|
||||||
}
|
}
|
||||||
|
sizes[m.key] = memberBytes
|
||||||
|
totalBytes = newTotalBytes
|
||||||
}
|
}
|
||||||
|
|
||||||
// OpenTelemetry does not allow for duplicate list-members, but the W3C
|
if len(b) == 0 {
|
||||||
// specification does. Now that we have deduplicated, ensure the baggage
|
return Baggage{}, truncateErr
|
||||||
// does not exceed list-member limits.
|
|
||||||
if len(b) > maxMembers {
|
|
||||||
return Baggage{}, errMemberNumber
|
|
||||||
}
|
}
|
||||||
|
return Baggage{b}, truncateErr
|
||||||
return Baggage{b}, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Member returns the baggage list-member identified by key.
|
// Member returns the baggage list-member identified by key.
|
||||||
|
|||||||
+55
-15
@@ -257,12 +257,18 @@ func key(n int) string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestNewBaggageErrorTooManyBytes(t *testing.T) {
|
func TestNewBaggageErrorTooManyBytes(t *testing.T) {
|
||||||
m := make([]Member, (maxBytesPerBaggageString/maxBytesPerMembers)+1)
|
// Create members that together exceed maxBytesPerBaggageString.
|
||||||
|
// Each member needs key + "=" so use keys that sum to > 8192 bytes.
|
||||||
|
keySize := maxBytesPerBaggageString / maxMembers
|
||||||
|
m := make([]Member, maxMembers)
|
||||||
for i := range m {
|
for i := range m {
|
||||||
m[i] = Member{key: key(maxBytesPerMembers), hasData: true}
|
m[i] = Member{key: key(keySize), hasData: true}
|
||||||
}
|
}
|
||||||
_, err := New(m...)
|
b, err := New(m...)
|
||||||
assert.ErrorIs(t, err, errBaggageBytes)
|
assert.ErrorIs(t, err, errBaggageBytes)
|
||||||
|
// Partial result should contain members that fit within the byte limit.
|
||||||
|
assert.Positive(t, b.Len(), "should return partial baggage")
|
||||||
|
assert.LessOrEqual(t, len(b.String()), maxBytesPerBaggageString, "partial baggage should be within byte limit")
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestNewBaggageErrorTooManyMembers(t *testing.T) {
|
func TestNewBaggageErrorTooManyMembers(t *testing.T) {
|
||||||
@@ -270,15 +276,15 @@ func TestNewBaggageErrorTooManyMembers(t *testing.T) {
|
|||||||
for i := range m {
|
for i := range m {
|
||||||
m[i] = Member{key: fmt.Sprintf("%d", i), hasData: true}
|
m[i] = Member{key: fmt.Sprintf("%d", i), hasData: true}
|
||||||
}
|
}
|
||||||
_, err := New(m...)
|
b, err := New(m...)
|
||||||
assert.ErrorIs(t, err, errMemberNumber)
|
assert.ErrorIs(t, err, errMemberNumber)
|
||||||
|
// Partial result should contain exactly maxMembers.
|
||||||
|
assert.Equal(t, maxMembers, b.Len(), "should return first %d members", maxMembers)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestBaggageParse(t *testing.T) {
|
func TestBaggageParse(t *testing.T) {
|
||||||
tooLarge := key(maxBytesPerBaggageString + 1)
|
tooLarge := key(maxBytesPerBaggageString + 1)
|
||||||
|
|
||||||
tooLargeMember := key(maxBytesPerMembers + 1)
|
|
||||||
|
|
||||||
m := make([]string, maxMembers+1)
|
m := make([]string, maxMembers+1)
|
||||||
for i := range m {
|
for i := range m {
|
||||||
m[i] = fmt.Sprintf("a%d=", i)
|
m[i] = fmt.Sprintf("a%d=", i)
|
||||||
@@ -468,7 +474,11 @@ func TestBaggageParse(t *testing.T) {
|
|||||||
{
|
{
|
||||||
name: "invalid member: empty",
|
name: "invalid member: empty",
|
||||||
in: "foo=,,bar=",
|
in: "foo=,,bar=",
|
||||||
err: errInvalidMember,
|
want: baggage.List{
|
||||||
|
"foo": {},
|
||||||
|
"bar": {},
|
||||||
|
},
|
||||||
|
err: errInvalidMember,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "invalid member: no key",
|
name: "invalid member: no key",
|
||||||
@@ -518,17 +528,47 @@ func TestBaggageParse(t *testing.T) {
|
|||||||
{
|
{
|
||||||
name: "invalid baggage string: too large",
|
name: "invalid baggage string: too large",
|
||||||
in: tooLarge,
|
in: tooLarge,
|
||||||
err: errBaggageBytes,
|
// tooLarge is a single key without "=", so parseMember fails
|
||||||
|
err: errInvalidMember,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "invalid baggage string: member too large",
|
name: "baggage string with too many members keeps first 64",
|
||||||
in: tooLargeMember,
|
|
||||||
err: errMemberBytes,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "invalid baggage string: too many members",
|
|
||||||
in: tooManyMembers,
|
in: tooManyMembers,
|
||||||
err: errMemberNumber,
|
want: func() baggage.List {
|
||||||
|
b := make(baggage.List)
|
||||||
|
for i := range maxMembers {
|
||||||
|
b[fmt.Sprintf("a%d", i)] = baggage.Item{Value: ""}
|
||||||
|
}
|
||||||
|
return b
|
||||||
|
}(),
|
||||||
|
err: errMemberNumber,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "baggage string exceeds byte limit returns partial result",
|
||||||
|
in: func() string {
|
||||||
|
// Create members that collectively exceed maxBytesPerBaggageString.
|
||||||
|
// Each member: "kN=" + value. We use values large enough that
|
||||||
|
// a few members fit but the total exceeds 8192 bytes.
|
||||||
|
var parts []string
|
||||||
|
val := strings.Repeat("v", 2000)
|
||||||
|
for i := range 10 {
|
||||||
|
parts = append(parts, fmt.Sprintf("k%d=%s", i, val))
|
||||||
|
}
|
||||||
|
return strings.Join(parts, ",")
|
||||||
|
}(),
|
||||||
|
want: func() baggage.List {
|
||||||
|
// Only members that fit within 8192 bytes should be kept.
|
||||||
|
// Each member is ~2003 bytes ("kN=" + 2000 "v"s), plus comma.
|
||||||
|
// 4 members = 4*2003 + 3 commas = 8015 bytes (fits).
|
||||||
|
// 5 members = 5*2003 + 4 commas = 10019 bytes (exceeds).
|
||||||
|
b := make(baggage.List)
|
||||||
|
val := strings.Repeat("v", 2000)
|
||||||
|
for i := range 4 {
|
||||||
|
b[fmt.Sprintf("k%d", i)] = baggage.Item{Value: val}
|
||||||
|
}
|
||||||
|
return b
|
||||||
|
}(),
|
||||||
|
err: errBaggageBytes,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "percent-encoded octet sequences do not match the UTF-8 encoding scheme",
|
name: "percent-encoded octet sequences do not match the UTF-8 encoding scheme",
|
||||||
|
|||||||
@@ -0,0 +1,96 @@
|
|||||||
|
// Copyright The OpenTelemetry Authors
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
|
// Package errorhandler provides the global error handler for OpenTelemetry.
|
||||||
|
//
|
||||||
|
// This package has no OTel dependencies, allowing it to be imported by any
|
||||||
|
// package in the module without creating import cycles.
|
||||||
|
package errorhandler // import "go.opentelemetry.io/otel/internal/errorhandler"
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"log"
|
||||||
|
"sync"
|
||||||
|
"sync/atomic"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ErrorHandler handles irremediable events.
|
||||||
|
type ErrorHandler interface {
|
||||||
|
// Handle handles any error deemed irremediable by an OpenTelemetry
|
||||||
|
// component.
|
||||||
|
Handle(error)
|
||||||
|
}
|
||||||
|
|
||||||
|
type ErrDelegator struct {
|
||||||
|
delegate atomic.Pointer[ErrorHandler]
|
||||||
|
}
|
||||||
|
|
||||||
|
// Compile-time check that delegator implements ErrorHandler.
|
||||||
|
var _ ErrorHandler = (*ErrDelegator)(nil)
|
||||||
|
|
||||||
|
func (d *ErrDelegator) Handle(err error) {
|
||||||
|
if eh := d.delegate.Load(); eh != nil {
|
||||||
|
(*eh).Handle(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
log.Print(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// setDelegate sets the ErrorHandler delegate.
|
||||||
|
func (d *ErrDelegator) setDelegate(eh ErrorHandler) {
|
||||||
|
d.delegate.Store(&eh)
|
||||||
|
}
|
||||||
|
|
||||||
|
type errorHandlerHolder struct {
|
||||||
|
eh ErrorHandler
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
globalErrorHandler = defaultErrorHandler()
|
||||||
|
delegateErrorHandlerOnce sync.Once
|
||||||
|
)
|
||||||
|
|
||||||
|
// GetErrorHandler returns the global ErrorHandler instance.
|
||||||
|
//
|
||||||
|
// The default ErrorHandler instance returned will log all errors to STDERR
|
||||||
|
// until an override ErrorHandler is set with SetErrorHandler. All
|
||||||
|
// ErrorHandler returned prior to this will automatically forward errors to
|
||||||
|
// the set instance instead of logging.
|
||||||
|
//
|
||||||
|
// Subsequent calls to SetErrorHandler after the first will not forward errors
|
||||||
|
// to the new ErrorHandler for prior returned instances.
|
||||||
|
func GetErrorHandler() ErrorHandler {
|
||||||
|
return globalErrorHandler.Load().(errorHandlerHolder).eh
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetErrorHandler sets the global ErrorHandler to h.
|
||||||
|
//
|
||||||
|
// The first time this is called all ErrorHandler previously returned from
|
||||||
|
// GetErrorHandler will send errors to h instead of the default logging
|
||||||
|
// ErrorHandler. Subsequent calls will set the global ErrorHandler, but not
|
||||||
|
// delegate errors to h.
|
||||||
|
func SetErrorHandler(h ErrorHandler) {
|
||||||
|
current := GetErrorHandler()
|
||||||
|
|
||||||
|
if _, cOk := current.(*ErrDelegator); cOk {
|
||||||
|
if _, ehOk := h.(*ErrDelegator); ehOk && current == h {
|
||||||
|
// Do not assign to the delegate of the default ErrDelegator to be
|
||||||
|
// itself.
|
||||||
|
log.Print(errors.New("no ErrorHandler delegate configured"), " ErrorHandler remains its current value.")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
delegateErrorHandlerOnce.Do(func() {
|
||||||
|
if def, ok := current.(*ErrDelegator); ok {
|
||||||
|
def.setDelegate(h)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
globalErrorHandler.Store(errorHandlerHolder{eh: h})
|
||||||
|
}
|
||||||
|
|
||||||
|
func defaultErrorHandler() *atomic.Value {
|
||||||
|
v := &atomic.Value{}
|
||||||
|
v.Store(errorHandlerHolder{eh: &ErrDelegator{}})
|
||||||
|
return v
|
||||||
|
}
|
||||||
@@ -0,0 +1,113 @@
|
|||||||
|
// Copyright The OpenTelemetry Authors
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
|
package errorhandler
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"errors"
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
type fnErrHandler func(error)
|
||||||
|
|
||||||
|
func (f fnErrHandler) Handle(err error) { f(err) }
|
||||||
|
|
||||||
|
var noopEH = fnErrHandler(func(error) {})
|
||||||
|
|
||||||
|
type nonComparableErrorHandler struct {
|
||||||
|
ErrorHandler
|
||||||
|
|
||||||
|
nonComparable func() //nolint:unused // This is not called.
|
||||||
|
}
|
||||||
|
|
||||||
|
func resetForTest(t testing.TB) {
|
||||||
|
t.Cleanup(func() {
|
||||||
|
globalErrorHandler = defaultErrorHandler()
|
||||||
|
delegateErrorHandlerOnce = sync.Once{}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestErrDelegator(t *testing.T) {
|
||||||
|
buf := new(bytes.Buffer)
|
||||||
|
log.Default().SetOutput(buf)
|
||||||
|
t.Cleanup(func() { log.Default().SetOutput(os.Stderr) })
|
||||||
|
|
||||||
|
e := &ErrDelegator{}
|
||||||
|
|
||||||
|
err := errors.New("testing")
|
||||||
|
e.Handle(err)
|
||||||
|
|
||||||
|
got := buf.String()
|
||||||
|
if !strings.Contains(got, err.Error()) {
|
||||||
|
t.Error("default handler did not log")
|
||||||
|
}
|
||||||
|
buf.Reset()
|
||||||
|
|
||||||
|
var gotErr error
|
||||||
|
e.setDelegate(fnErrHandler(func(e error) { gotErr = e }))
|
||||||
|
e.Handle(err)
|
||||||
|
|
||||||
|
if buf.String() != "" {
|
||||||
|
t.Error("delegate not set")
|
||||||
|
} else if !errors.Is(gotErr, err) {
|
||||||
|
t.Error("error not passed to delegate")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSetErrorHandler(t *testing.T) {
|
||||||
|
t.Run("Set With default is a noop", func(t *testing.T) {
|
||||||
|
resetForTest(t)
|
||||||
|
SetErrorHandler(GetErrorHandler())
|
||||||
|
|
||||||
|
eh, ok := GetErrorHandler().(*ErrDelegator)
|
||||||
|
if !ok {
|
||||||
|
t.Fatal("Global ErrorHandler should be the default ErrorHandler")
|
||||||
|
}
|
||||||
|
|
||||||
|
if eh.delegate.Load() != nil {
|
||||||
|
t.Fatal("ErrorHandler should not delegate when setting itself")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("First Set() should replace the delegate", func(t *testing.T) {
|
||||||
|
resetForTest(t)
|
||||||
|
|
||||||
|
SetErrorHandler(noopEH)
|
||||||
|
|
||||||
|
_, ok := GetErrorHandler().(*ErrDelegator)
|
||||||
|
if ok {
|
||||||
|
t.Fatal("Global ErrorHandler was not changed")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("Set() should delegate existing ErrorHandlers", func(t *testing.T) {
|
||||||
|
resetForTest(t)
|
||||||
|
|
||||||
|
eh := GetErrorHandler()
|
||||||
|
SetErrorHandler(noopEH)
|
||||||
|
|
||||||
|
errDel, ok := eh.(*ErrDelegator)
|
||||||
|
if !ok {
|
||||||
|
t.Fatal("Wrong ErrorHandler returned")
|
||||||
|
}
|
||||||
|
|
||||||
|
if errDel.delegate.Load() == nil {
|
||||||
|
t.Fatal("The ErrDelegator should have a delegate")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("non-comparable types should not panic", func(t *testing.T) {
|
||||||
|
resetForTest(t)
|
||||||
|
|
||||||
|
eh := nonComparableErrorHandler{}
|
||||||
|
assert.NotPanics(t, func() { SetErrorHandler(eh) }, "delegate")
|
||||||
|
assert.NotPanics(t, func() { SetErrorHandler(eh) }, "replacement")
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -5,33 +5,13 @@
|
|||||||
package global // import "go.opentelemetry.io/otel/internal/global"
|
package global // import "go.opentelemetry.io/otel/internal/global"
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"log"
|
"go.opentelemetry.io/otel/internal/errorhandler"
|
||||||
"sync/atomic"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// ErrorHandler handles irremediable events.
|
// ErrorHandler is an alias for errorhandler.ErrorHandler, kept for backward
|
||||||
type ErrorHandler interface {
|
// compatibility with existing callers of internal/global.
|
||||||
// Handle handles any error deemed irremediable by an OpenTelemetry
|
type ErrorHandler = errorhandler.ErrorHandler
|
||||||
// component.
|
|
||||||
Handle(error)
|
|
||||||
}
|
|
||||||
|
|
||||||
type ErrDelegator struct {
|
// ErrDelegator is an alias for errorhandler.ErrDelegator, kept for backward
|
||||||
delegate atomic.Pointer[ErrorHandler]
|
// compatibility with existing callers of internal/global.
|
||||||
}
|
type ErrDelegator = errorhandler.ErrDelegator
|
||||||
|
|
||||||
// Compile-time check that delegator implements ErrorHandler.
|
|
||||||
var _ ErrorHandler = (*ErrDelegator)(nil)
|
|
||||||
|
|
||||||
func (d *ErrDelegator) Handle(err error) {
|
|
||||||
if eh := d.delegate.Load(); eh != nil {
|
|
||||||
(*eh).Handle(err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
log.Print(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// setDelegate sets the ErrorHandler delegate.
|
|
||||||
func (d *ErrDelegator) setDelegate(eh ErrorHandler) {
|
|
||||||
d.delegate.Store(&eh)
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,40 +0,0 @@
|
|||||||
// Copyright The OpenTelemetry Authors
|
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
|
||||||
|
|
||||||
package global
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"errors"
|
|
||||||
"log"
|
|
||||||
"os"
|
|
||||||
"strings"
|
|
||||||
"testing"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestErrDelegator(t *testing.T) {
|
|
||||||
buf := new(bytes.Buffer)
|
|
||||||
log.Default().SetOutput(buf)
|
|
||||||
t.Cleanup(func() { log.Default().SetOutput(os.Stderr) })
|
|
||||||
|
|
||||||
e := &ErrDelegator{}
|
|
||||||
|
|
||||||
err := errors.New("testing")
|
|
||||||
e.Handle(err)
|
|
||||||
|
|
||||||
got := buf.String()
|
|
||||||
if !strings.Contains(got, err.Error()) {
|
|
||||||
t.Error("default handler did not log")
|
|
||||||
}
|
|
||||||
buf.Reset()
|
|
||||||
|
|
||||||
var gotErr error
|
|
||||||
e.setDelegate(fnErrHandler(func(e error) { gotErr = e }))
|
|
||||||
e.Handle(err)
|
|
||||||
|
|
||||||
if buf.String() != "" {
|
|
||||||
t.Error("delegate not set")
|
|
||||||
} else if !errors.Is(gotErr, err) {
|
|
||||||
t.Error("error not passed to delegate")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -8,16 +8,13 @@ import (
|
|||||||
"sync"
|
"sync"
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
|
|
||||||
|
"go.opentelemetry.io/otel/internal/errorhandler"
|
||||||
"go.opentelemetry.io/otel/metric"
|
"go.opentelemetry.io/otel/metric"
|
||||||
"go.opentelemetry.io/otel/propagation"
|
"go.opentelemetry.io/otel/propagation"
|
||||||
"go.opentelemetry.io/otel/trace"
|
"go.opentelemetry.io/otel/trace"
|
||||||
)
|
)
|
||||||
|
|
||||||
type (
|
type (
|
||||||
errorHandlerHolder struct {
|
|
||||||
eh ErrorHandler
|
|
||||||
}
|
|
||||||
|
|
||||||
tracerProviderHolder struct {
|
tracerProviderHolder struct {
|
||||||
tp trace.TracerProvider
|
tp trace.TracerProvider
|
||||||
}
|
}
|
||||||
@@ -32,12 +29,10 @@ type (
|
|||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
globalErrorHandler = defaultErrorHandler()
|
|
||||||
globalTracer = defaultTracerValue()
|
globalTracer = defaultTracerValue()
|
||||||
globalPropagators = defaultPropagatorsValue()
|
globalPropagators = defaultPropagatorsValue()
|
||||||
globalMeterProvider = defaultMeterProvider()
|
globalMeterProvider = defaultMeterProvider()
|
||||||
|
|
||||||
delegateErrorHandlerOnce sync.Once
|
|
||||||
delegateTraceOnce sync.Once
|
delegateTraceOnce sync.Once
|
||||||
delegateTextMapPropagatorOnce sync.Once
|
delegateTextMapPropagatorOnce sync.Once
|
||||||
delegateMeterOnce sync.Once
|
delegateMeterOnce sync.Once
|
||||||
@@ -53,7 +48,7 @@ var (
|
|||||||
// Subsequent calls to SetErrorHandler after the first will not forward errors
|
// Subsequent calls to SetErrorHandler after the first will not forward errors
|
||||||
// to the new ErrorHandler for prior returned instances.
|
// to the new ErrorHandler for prior returned instances.
|
||||||
func GetErrorHandler() ErrorHandler {
|
func GetErrorHandler() ErrorHandler {
|
||||||
return globalErrorHandler.Load().(errorHandlerHolder).eh
|
return errorhandler.GetErrorHandler()
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetErrorHandler sets the global ErrorHandler to h.
|
// SetErrorHandler sets the global ErrorHandler to h.
|
||||||
@@ -63,26 +58,7 @@ func GetErrorHandler() ErrorHandler {
|
|||||||
// ErrorHandler. Subsequent calls will set the global ErrorHandler, but not
|
// ErrorHandler. Subsequent calls will set the global ErrorHandler, but not
|
||||||
// delegate errors to h.
|
// delegate errors to h.
|
||||||
func SetErrorHandler(h ErrorHandler) {
|
func SetErrorHandler(h ErrorHandler) {
|
||||||
current := GetErrorHandler()
|
errorhandler.SetErrorHandler(h)
|
||||||
|
|
||||||
if _, cOk := current.(*ErrDelegator); cOk {
|
|
||||||
if _, ehOk := h.(*ErrDelegator); ehOk && current == h {
|
|
||||||
// Do not assign to the delegate of the default ErrDelegator to be
|
|
||||||
// itself.
|
|
||||||
Error(
|
|
||||||
errors.New("no ErrorHandler delegate configured"),
|
|
||||||
"ErrorHandler remains its current value.",
|
|
||||||
)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
delegateErrorHandlerOnce.Do(func() {
|
|
||||||
if def, ok := current.(*ErrDelegator); ok {
|
|
||||||
def.setDelegate(h)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
globalErrorHandler.Store(errorHandlerHolder{eh: h})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// TracerProvider is the internal implementation for global.TracerProvider.
|
// TracerProvider is the internal implementation for global.TracerProvider.
|
||||||
@@ -174,12 +150,6 @@ func SetMeterProvider(mp metric.MeterProvider) {
|
|||||||
globalMeterProvider.Store(meterProviderHolder{mp: mp})
|
globalMeterProvider.Store(meterProviderHolder{mp: mp})
|
||||||
}
|
}
|
||||||
|
|
||||||
func defaultErrorHandler() *atomic.Value {
|
|
||||||
v := &atomic.Value{}
|
|
||||||
v.Store(errorHandlerHolder{eh: &ErrDelegator{}})
|
|
||||||
return v
|
|
||||||
}
|
|
||||||
|
|
||||||
func defaultTracerValue() *atomic.Value {
|
func defaultTracerValue() *atomic.Value {
|
||||||
v := &atomic.Value{}
|
v := &atomic.Value{}
|
||||||
v.Store(tracerProviderHolder{tp: &tracerProvider{}})
|
v.Store(tracerProviderHolder{tp: &tracerProvider{}})
|
||||||
|
|||||||
@@ -15,12 +15,6 @@ import (
|
|||||||
tracenoop "go.opentelemetry.io/otel/trace/noop"
|
tracenoop "go.opentelemetry.io/otel/trace/noop"
|
||||||
)
|
)
|
||||||
|
|
||||||
type nonComparableErrorHandler struct {
|
|
||||||
ErrorHandler
|
|
||||||
|
|
||||||
nonComparable func() //nolint:unused // This is not called.
|
|
||||||
}
|
|
||||||
|
|
||||||
type nonComparableTracerProvider struct {
|
type nonComparableTracerProvider struct {
|
||||||
trace.TracerProvider
|
trace.TracerProvider
|
||||||
|
|
||||||
@@ -33,63 +27,6 @@ type nonComparableMeterProvider struct {
|
|||||||
nonComparable func() //nolint:unused // This is not called.
|
nonComparable func() //nolint:unused // This is not called.
|
||||||
}
|
}
|
||||||
|
|
||||||
type fnErrHandler func(error)
|
|
||||||
|
|
||||||
func (f fnErrHandler) Handle(err error) { f(err) }
|
|
||||||
|
|
||||||
var noopEH = fnErrHandler(func(error) {})
|
|
||||||
|
|
||||||
func TestSetErrorHandler(t *testing.T) {
|
|
||||||
t.Run("Set With default is a noop", func(t *testing.T) {
|
|
||||||
ResetForTest(t)
|
|
||||||
SetErrorHandler(GetErrorHandler())
|
|
||||||
|
|
||||||
eh, ok := GetErrorHandler().(*ErrDelegator)
|
|
||||||
if !ok {
|
|
||||||
t.Fatal("Global ErrorHandler should be the default ErrorHandler")
|
|
||||||
}
|
|
||||||
|
|
||||||
if eh.delegate.Load() != nil {
|
|
||||||
t.Fatal("ErrorHandler should not delegate when setting itself")
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("First Set() should replace the delegate", func(t *testing.T) {
|
|
||||||
ResetForTest(t)
|
|
||||||
|
|
||||||
SetErrorHandler(noopEH)
|
|
||||||
|
|
||||||
_, ok := GetErrorHandler().(*ErrDelegator)
|
|
||||||
if ok {
|
|
||||||
t.Fatal("Global ErrorHandler was not changed")
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("Set() should delegate existing ErrorHandlers", func(t *testing.T) {
|
|
||||||
ResetForTest(t)
|
|
||||||
|
|
||||||
eh := GetErrorHandler()
|
|
||||||
SetErrorHandler(noopEH)
|
|
||||||
|
|
||||||
errDel, ok := eh.(*ErrDelegator)
|
|
||||||
if !ok {
|
|
||||||
t.Fatal("Wrong ErrorHandler returned")
|
|
||||||
}
|
|
||||||
|
|
||||||
if errDel.delegate.Load() == nil {
|
|
||||||
t.Fatal("The ErrDelegator should have a delegate")
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("non-comparable types should not panic", func(t *testing.T) {
|
|
||||||
ResetForTest(t)
|
|
||||||
|
|
||||||
eh := nonComparableErrorHandler{}
|
|
||||||
assert.NotPanics(t, func() { SetErrorHandler(eh) }, "delegate")
|
|
||||||
assert.NotPanics(t, func() { SetErrorHandler(eh) }, "replacement")
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestSetTracerProvider(t *testing.T) {
|
func TestSetTracerProvider(t *testing.T) {
|
||||||
t.Run("Set With default is a noop", func(t *testing.T) {
|
t.Run("Set With default is a noop", func(t *testing.T) {
|
||||||
ResetForTest(t)
|
ResetForTest(t)
|
||||||
|
|||||||
@@ -12,11 +12,9 @@ import (
|
|||||||
// its Cleanup step.
|
// its Cleanup step.
|
||||||
func ResetForTest(t testing.TB) {
|
func ResetForTest(t testing.TB) {
|
||||||
t.Cleanup(func() {
|
t.Cleanup(func() {
|
||||||
globalErrorHandler = defaultErrorHandler()
|
|
||||||
globalTracer = defaultTracerValue()
|
globalTracer = defaultTracerValue()
|
||||||
globalPropagators = defaultPropagatorsValue()
|
globalPropagators = defaultPropagatorsValue()
|
||||||
globalMeterProvider = defaultMeterProvider()
|
globalMeterProvider = defaultMeterProvider()
|
||||||
delegateErrorHandlerOnce = sync.Once{}
|
|
||||||
delegateTraceOnce = sync.Once{}
|
delegateTraceOnce = sync.Once{}
|
||||||
delegateTextMapPropagatorOnce = sync.Once{}
|
delegateTextMapPropagatorOnce = sync.Once{}
|
||||||
delegateMeterOnce = sync.Once{}
|
delegateMeterOnce = sync.Once{}
|
||||||
|
|||||||
+22
-2
@@ -7,9 +7,16 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
|
|
||||||
"go.opentelemetry.io/otel/baggage"
|
"go.opentelemetry.io/otel/baggage"
|
||||||
|
"go.opentelemetry.io/otel/internal/errorhandler"
|
||||||
)
|
)
|
||||||
|
|
||||||
const baggageHeader = "baggage"
|
const (
|
||||||
|
baggageHeader = "baggage"
|
||||||
|
|
||||||
|
// W3C Baggage specification limits.
|
||||||
|
// https://www.w3.org/TR/baggage/#limits
|
||||||
|
maxMembers = 64
|
||||||
|
)
|
||||||
|
|
||||||
// Baggage is a propagator that supports the W3C Baggage format.
|
// Baggage is a propagator that supports the W3C Baggage format.
|
||||||
//
|
//
|
||||||
@@ -50,6 +57,9 @@ func extractSingleBaggage(parent context.Context, carrier TextMapCarrier) contex
|
|||||||
|
|
||||||
bag, err := baggage.Parse(bStr)
|
bag, err := baggage.Parse(bStr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
errorhandler.GetErrorHandler().Handle(err)
|
||||||
|
}
|
||||||
|
if bag.Len() == 0 {
|
||||||
return parent
|
return parent
|
||||||
}
|
}
|
||||||
return baggage.ContextWithBaggage(parent, bag)
|
return baggage.ContextWithBaggage(parent, bag)
|
||||||
@@ -60,17 +70,27 @@ func extractMultiBaggage(parent context.Context, carrier ValuesGetter) context.C
|
|||||||
if len(bVals) == 0 {
|
if len(bVals) == 0 {
|
||||||
return parent
|
return parent
|
||||||
}
|
}
|
||||||
|
|
||||||
var members []baggage.Member
|
var members []baggage.Member
|
||||||
for _, bStr := range bVals {
|
for _, bStr := range bVals {
|
||||||
currBag, err := baggage.Parse(bStr)
|
currBag, err := baggage.Parse(bStr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
errorhandler.GetErrorHandler().Handle(err)
|
||||||
|
}
|
||||||
|
if currBag.Len() == 0 {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
members = append(members, currBag.Members()...)
|
members = append(members, currBag.Members()...)
|
||||||
|
if len(members) >= maxMembers {
|
||||||
|
break
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
b, err := baggage.New(members...)
|
b, err := baggage.New(members...)
|
||||||
if err != nil || b.Len() == 0 {
|
if err != nil {
|
||||||
|
errorhandler.GetErrorHandler().Handle(err)
|
||||||
|
}
|
||||||
|
if b.Len() == 0 {
|
||||||
return parent
|
return parent
|
||||||
}
|
}
|
||||||
return baggage.ContextWithBaggage(parent, b)
|
return baggage.ContextWithBaggage(parent, b)
|
||||||
|
|||||||
+146
-6
@@ -4,6 +4,7 @@
|
|||||||
package propagation_test
|
package propagation_test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
@@ -95,7 +96,10 @@ func TestExtractValidBaggage(t *testing.T) {
|
|||||||
{
|
{
|
||||||
name: "valid header with an invalid header",
|
name: "valid header with an invalid header",
|
||||||
header: "key1=val1,key2=val2,a,val3",
|
header: "key1=val1,key2=val2,a,val3",
|
||||||
want: members{},
|
want: members{
|
||||||
|
{Key: "key1", Value: "val1"},
|
||||||
|
{Key: "key2", Value: "val2"},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "valid header with no value",
|
name: "valid header with no value",
|
||||||
@@ -139,12 +143,36 @@ func TestExtractValidBaggage(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// generateBaggageHeader creates a baggage header string with n members.
|
||||||
|
func generateBaggageHeader(n int, prefix string) string {
|
||||||
|
parts := make([]string, n)
|
||||||
|
for i := range parts {
|
||||||
|
parts[i] = fmt.Sprintf("%s%d=v%d", prefix, i, i)
|
||||||
|
}
|
||||||
|
return strings.Join(parts, ",")
|
||||||
|
}
|
||||||
|
|
||||||
|
// generateMembers creates n members with keys like "prefix0", "prefix1", etc.
|
||||||
|
func generateMembers(n int, prefix string) members {
|
||||||
|
m := make(members, n)
|
||||||
|
for i := range m {
|
||||||
|
m[i] = member{Key: fmt.Sprintf("%s%d", prefix, i), Value: fmt.Sprintf("v%d", i)}
|
||||||
|
}
|
||||||
|
return m
|
||||||
|
}
|
||||||
|
|
||||||
func TestExtractValidMultipleBaggageHeaders(t *testing.T) {
|
func TestExtractValidMultipleBaggageHeaders(t *testing.T) {
|
||||||
|
// W3C Baggage spec limits: https://www.w3.org/TR/baggage/#limits
|
||||||
|
const maxMembers = 64
|
||||||
|
const maxBytesPerBaggageString = 8192
|
||||||
|
|
||||||
prop := propagation.TextMapPropagator(propagation.Baggage{})
|
prop := propagation.TextMapPropagator(propagation.Baggage{})
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
headers []string
|
headers []string
|
||||||
want members
|
want members
|
||||||
|
wantCount int // Used when want is nil and we only care about count.
|
||||||
|
wantMaxBytes int // Used to check that baggage size doesn't exceed limit.
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
name: "non conflicting headers",
|
name: "non conflicting headers",
|
||||||
@@ -178,6 +206,109 @@ func TestExtractValidMultipleBaggageHeaders(t *testing.T) {
|
|||||||
headers: []string{},
|
headers: []string{},
|
||||||
want: members{},
|
want: members{},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "single header with one invalid skips invalid",
|
||||||
|
headers: []string{"key1=val1,invalid-no-equals,key2=val2"},
|
||||||
|
want: members{
|
||||||
|
{Key: "key1", Value: "val1"},
|
||||||
|
{Key: "key2", Value: "val2"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "multiple headers with one invalid skips invalid and continues",
|
||||||
|
headers: []string{
|
||||||
|
"key1=val1",
|
||||||
|
"invalid-no-equals",
|
||||||
|
"key2=val2",
|
||||||
|
},
|
||||||
|
want: members{
|
||||||
|
{Key: "key1", Value: "val1"},
|
||||||
|
{Key: "key2", Value: "val2"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "single header at max members limit",
|
||||||
|
headers: []string{generateBaggageHeader(maxMembers, "k")},
|
||||||
|
want: generateMembers(maxMembers, "k"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "single header exceeds max members limit keeps 64",
|
||||||
|
headers: []string{generateBaggageHeader(maxMembers+1, "k")},
|
||||||
|
want: generateMembers(maxMembers, "k"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "multiple headers exceeds total max members limit keeps 64",
|
||||||
|
headers: []string{
|
||||||
|
generateBaggageHeader(maxMembers/2, "a"),
|
||||||
|
generateBaggageHeader(maxMembers/2, "b"),
|
||||||
|
generateBaggageHeader(1, "c"),
|
||||||
|
},
|
||||||
|
want: nil, // Non-deterministic truncation by baggage.New()
|
||||||
|
wantCount: maxMembers,
|
||||||
|
wantMaxBytes: maxBytesPerBaggageString,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "single header at max bytes limit",
|
||||||
|
headers: []string{"k=" + strings.Repeat("v", maxBytesPerBaggageString-2)},
|
||||||
|
want: members{
|
||||||
|
{Key: "k", Value: strings.Repeat("v", maxBytesPerBaggageString-2)},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "single header exceeds max bytes limit drops oversized member",
|
||||||
|
headers: []string{"k=" + strings.Repeat("v", maxBytesPerBaggageString-1)},
|
||||||
|
want: members{},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "multiple headers exceed total max bytes keeps one that fits",
|
||||||
|
headers: []string{
|
||||||
|
"k=" + strings.Repeat("v", maxBytesPerBaggageString-2),
|
||||||
|
"y=" + strings.Repeat("v", maxBytesPerBaggageString-2),
|
||||||
|
},
|
||||||
|
want: nil, // Non-deterministic: either k or y will be kept
|
||||||
|
wantCount: 1, // Only one member fits
|
||||||
|
wantMaxBytes: maxBytesPerBaggageString,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "multiple headers within total max bytes",
|
||||||
|
headers: []string{
|
||||||
|
"k=" + strings.Repeat("v", maxBytesPerBaggageString/2-2),
|
||||||
|
// The comma as the separator of member would take 1 byte.
|
||||||
|
"y=" + strings.Repeat("v", maxBytesPerBaggageString/2-2-1),
|
||||||
|
},
|
||||||
|
want: members{
|
||||||
|
{Key: "k", Value: strings.Repeat("v", maxBytesPerBaggageString/2-2)},
|
||||||
|
{Key: "y", Value: strings.Repeat("v", maxBytesPerBaggageString/2-2-1)},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "many headers exceeding member limit caps collection early",
|
||||||
|
headers: func() []string {
|
||||||
|
// 100 headers with 10 members each = 1000 total members.
|
||||||
|
// The cap should stop collecting after ~maxMembers and
|
||||||
|
// New() truncates to exactly maxMembers.
|
||||||
|
h := make([]string, 100)
|
||||||
|
for i := range h {
|
||||||
|
h[i] = generateBaggageHeader(10, fmt.Sprintf("h%d_k", i))
|
||||||
|
}
|
||||||
|
return h
|
||||||
|
}(),
|
||||||
|
wantCount: maxMembers,
|
||||||
|
wantMaxBytes: maxBytesPerBaggageString,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "skips large member that exceeds byte limit and continues",
|
||||||
|
headers: []string{
|
||||||
|
"small1=v1,small2=v2",
|
||||||
|
"large=" + strings.Repeat("x", maxBytesPerBaggageString),
|
||||||
|
"small3=v3",
|
||||||
|
},
|
||||||
|
want: members{
|
||||||
|
{Key: "small1", Value: "v1"},
|
||||||
|
{Key: "small2", Value: "v2"},
|
||||||
|
{Key: "small3", Value: "v3"},
|
||||||
|
},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
@@ -187,8 +318,17 @@ func TestExtractValidMultipleBaggageHeaders(t *testing.T) {
|
|||||||
|
|
||||||
ctx := t.Context()
|
ctx := t.Context()
|
||||||
ctx = prop.Extract(ctx, propagation.HeaderCarrier(req.Header))
|
ctx = prop.Extract(ctx, propagation.HeaderCarrier(req.Header))
|
||||||
expected := tt.want.Baggage(t)
|
got := baggage.FromContext(ctx)
|
||||||
assert.Equal(t, expected, baggage.FromContext(ctx))
|
|
||||||
|
// If want is specified, check exact match
|
||||||
|
if tt.want != nil {
|
||||||
|
expected := tt.want.Baggage(t)
|
||||||
|
assert.Equal(t, expected, got)
|
||||||
|
} else if tt.wantCount > 0 {
|
||||||
|
// If only count is specified, verify count and byte limit
|
||||||
|
assert.Equal(t, tt.wantCount, got.Len(), "expected member count")
|
||||||
|
assert.LessOrEqual(t, len(got.String()), tt.wantMaxBytes, "baggage size exceeds limit")
|
||||||
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user