github.com/instana/go-sensor@v1.62.2-0.20240520081010-4919868049e1/w3ctrace/state.go (about) 1 // (c) Copyright IBM Corp. 2021 2 // (c) Copyright Instana Inc. 2020 3 4 package w3ctrace 5 6 import ( 7 "bytes" 8 "regexp" 9 "strings" 10 ) 11 12 // VendorInstana is the Instana vendor key in the `tracestate` list 13 const VendorInstana = "in" 14 15 // Length of entries that should be filtered first in case, if tracestate has more than `MaxStateEntries` items 16 const thresholdLen = 128 17 18 var instanaListMemberRegex = regexp.MustCompile("^\\s*" + VendorInstana + "\\s*=\\s*([^,]*)\\s*$") 19 20 // State is list of key=value pairs representing vendor-specific data in the trace context 21 type State struct { 22 // The key-value pairs of other vendors. The "in" list member, if present, will be stored separately 23 // (Note: list member is the term the W3C trace context specfication uses for the key-value pairs.) 24 listMembers []string 25 26 // The value for the Instana-specific list member ("in=..."). 27 instanaTraceStateValue string 28 } 29 30 // NewState creates a new State with the given values 31 func NewState(listMembers []string, instanaTraceStateValue string) State { 32 return State{ 33 listMembers: listMembers, 34 instanaTraceStateValue: instanaTraceStateValue, 35 } 36 } 37 38 // FormStateWithInstanaTraceStateValue returns a new state prepended with the provided Instana value. If the original state had an Instana 39 // list member pair, it is discarded/overwritten. 40 func FormStateWithInstanaTraceStateValue(st State, instanaTraceStateValue string) State { 41 var listMembers []string 42 if st.instanaTraceStateValue == "" && instanaTraceStateValue != "" && len(st.listMembers) == MaxStateEntries { 43 // The incoming tracestate had the maximum number of list members but no Instana list member, now we are adding an 44 // Instana list member, so we would exceed the maximum by one. Hence, we need to discard one of the other list 45 // members to stay within the limits mandated by the specification. 46 listMembers = st.listMembers[:MaxStateEntries-1] 47 } else { 48 listMembers = st.listMembers 49 } 50 51 return State{listMembers: listMembers, instanaTraceStateValue: instanaTraceStateValue} 52 } 53 54 // ParseState parses the value of `tracestate` header. Empty list items are omitted. 55 func ParseState(traceStateValue string) State { 56 listMembers := filterEmptyItems(strings.Split(traceStateValue, ",")) 57 58 // Look for the Instana list member first, before discarding any list members due to length restrictions. 59 instanaTraceStateValue, instanaTraceStateIdx := searchInstanaHeader(listMembers) 60 61 if instanaTraceStateIdx >= 0 { 62 // remove the entry for instana from the array of list members 63 listMembers = append(listMembers[:instanaTraceStateIdx], listMembers[instanaTraceStateIdx+1:]...) 64 } 65 66 // Depending on whether we found an Instana list member, we can either allow 31 or 32 list members from other vendors. 67 maxListMembers := MaxStateEntries 68 if instanaTraceStateValue != "" { 69 maxListMembers-- 70 } 71 72 if len(listMembers) < maxListMembers { 73 return State{listMembers: listMembers, instanaTraceStateValue: instanaTraceStateValue} 74 } 75 76 itemsToFilter := len(listMembers) - maxListMembers 77 filteredListMembers := listMembers[:0] 78 i := 0 79 for ; itemsToFilter > 0 && i < len(listMembers); i++ { 80 if len(listMembers[i]) > thresholdLen { 81 itemsToFilter-- 82 continue 83 } 84 filteredListMembers = append(filteredListMembers, listMembers[i]) 85 } 86 filteredListMembers = append(filteredListMembers, listMembers[i:]...) 87 88 if len(filteredListMembers) > maxListMembers { 89 return State{listMembers: filteredListMembers[:maxListMembers], instanaTraceStateValue: instanaTraceStateValue} 90 } 91 92 return State{listMembers: filteredListMembers, instanaTraceStateValue: instanaTraceStateValue} 93 } 94 95 // FetchInstanaTraceStateValue retrieves the value of the Instana tracestate list member, if any. 96 func (st State) FetchInstanaTraceStateValue() (string, bool) { 97 return st.instanaTraceStateValue, st.instanaTraceStateValue != "" 98 } 99 100 // String returns string representation of a trace state. The returned value is compatible with the 101 // `tracestate` header format. If the state has an Instana-specific list member, that one is always rendered first. This 102 // is optimized for the use case of injecting the string representation of the tracestate header into downstream 103 // requests. 104 func (st State) String() string { 105 if len(st.listMembers) == 0 && st.instanaTraceStateValue == "" { 106 return "" 107 } 108 if len(st.listMembers) == 0 { 109 return VendorInstana + "=" + st.instanaTraceStateValue 110 } 111 112 buf := bytes.NewBuffer(nil) 113 if st.instanaTraceStateValue != "" { 114 buf.WriteString(VendorInstana) 115 buf.WriteString("=") 116 buf.WriteString(st.instanaTraceStateValue) 117 buf.WriteString(",") 118 } 119 for _, vd := range st.listMembers { 120 buf.WriteString(vd) 121 buf.WriteByte(',') 122 } 123 buf.Truncate(buf.Len() - 1) // remove trailing comma 124 125 return buf.String() 126 } 127 128 func filterEmptyItems(entries []string) []string { 129 result := entries[:0] 130 for _, v := range entries { 131 if v != "" { 132 result = append(result, v) 133 } 134 } 135 136 return result 137 } 138 139 func searchInstanaHeader(listMembers []string) (string, int) { 140 var instanaTraceStateValue string 141 instanaTraceStateIdx := -1 142 for i, vd := range listMembers { 143 matchResult := instanaListMemberRegex.FindStringSubmatch(vd) 144 if len(matchResult) == 2 { 145 instanaTraceStateValue = strings.TrimSpace(matchResult[1]) 146 instanaTraceStateIdx = i 147 break 148 } 149 } 150 return instanaTraceStateValue, instanaTraceStateIdx 151 }