github.com/xmidt-org/webpa-common@v1.11.9/tracing/spanned.go (about)

     1  package tracing
     2  
     3  // Spanned can be implemented by message objects to describe the spans
     4  // involved in producing the message.  Generally, this interface should
     5  // be implemented on transient objects that pass through the layers
     6  // of an application.
     7  type Spanned interface {
     8  	Spans() []Span
     9  }
    10  
    11  // Mergeable represents a Spanned which can be merged with other spans
    12  type Mergeable interface {
    13  	Spanned
    14  
    15  	// WithSpans returns an instance of this object with the new Spans, possibly
    16  	// merged into those returned by Spans.  This method should generally return
    17  	// a shallow copy of itself with the new spans, to preserve immutability.
    18  	WithSpans(...Span) interface{}
    19  }
    20  
    21  // Spans extracts the slice of Span instances from a container, if possible.
    22  //
    23  //   If container implements Spanned, then container.Spans() is returned with a true.
    24  //   If container is a Span, a slice of that one element is returned with a true.
    25  //   If container is a []Span, it's returned as is with a true.
    26  //   Otherwise, this function returns nil, false.
    27  func Spans(container interface{}) ([]Span, bool) {
    28  	switch v := container.(type) {
    29  	case Span:
    30  		return []Span{v}, true
    31  	case []Span:
    32  		return v, true
    33  	case Spanned:
    34  		return v.Spans(), true
    35  	default:
    36  		return nil, false
    37  	}
    38  }
    39  
    40  // MergeSpans attempts to merge the given spans into a container.  If container does not
    41  // implement Mergeable, or if spans is empty, then this function returns container as is with a false.
    42  // Otherwise, each element of spans is merged with container, and result of container.WithSpans is
    43  // returned with a true.
    44  //
    45  // Similar to Spans, each element of spans may be of type Span, []Span, or Spanned.  Any other type is skipped without error.
    46  func MergeSpans(container interface{}, spans ...interface{}) (interface{}, bool) {
    47  	if len(spans) == 0 {
    48  		return container, false
    49  	}
    50  
    51  	if mergeable, ok := container.(Mergeable); ok {
    52  		var mergedSpans []Span
    53  
    54  		for _, s := range spans {
    55  			switch v := s.(type) {
    56  			case Span:
    57  				mergedSpans = append(mergedSpans, v)
    58  			case []Span:
    59  				mergedSpans = append(mergedSpans, v...)
    60  			case Spanned:
    61  				mergedSpans = append(mergedSpans, v.Spans()...)
    62  			}
    63  		}
    64  
    65  		// we still don't want to merge if we wound up with nothing to merge
    66  		if len(mergedSpans) == 0 {
    67  			return container, false
    68  		}
    69  
    70  		// if there are existing spans, preserve order by appending the collected spans we
    71  		// have so far.  also, allocate a copy to avoid polluting the spans of the original container.
    72  		if existingSpans := mergeable.Spans(); len(existingSpans) > 0 {
    73  			copyOf := make([]Span, len(existingSpans))
    74  			copy(copyOf, existingSpans)
    75  			mergedSpans = append(copyOf, mergedSpans...)
    76  		}
    77  
    78  		return mergeable.WithSpans(mergedSpans...), true
    79  	}
    80  
    81  	return container, false
    82  }
    83  
    84  // NopMergeable is just a Mergeable with no other state.  This is useful for tests.
    85  type NopMergeable []Span
    86  
    87  func (nm NopMergeable) Spans() []Span {
    88  	return nm
    89  }
    90  
    91  func (nm NopMergeable) WithSpans(spans ...Span) interface{} {
    92  	return NopMergeable(spans)
    93  }