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 }