@@ -5,6 +5,7 @@ package w3ctrace
55
66import (
77 "bytes"
8+ "regexp"
89 "strings"
910)
1011
@@ -17,109 +18,117 @@ const maxKVPairs = 32
1718// Length of entries that should be filtered first in case, if tracestate has more than `maxKVPairs` items
1819const thresholdLen = 128
1920
20- // State is list of key=value pairs representing vendor-specific data in the trace context
21- type State []string
21+ var instanaListMemberRegex = regexp .MustCompile ("^\\ s*" + VendorInstana + "\\ s*=\\ s*([^,]*)\\ s*$" )
2222
23- // ParseState parses the value of `tracestate` header. Empty list items are omitted.
24- func ParseState (traceStateValue string ) (State , error ) {
25- states := filterEmptyItems (strings .Split (traceStateValue , "," ))
26- if len (states ) < maxKVPairs {
27- return states , nil
28- }
23+ // State is list of key=value pairs representing vendor-specific data in the trace context
24+ type State struct {
25+ // The key-value pairs of other vendors. The "in" list member, if present, will be stored separately
26+ // (Note: list member is the term the W3C trace context specfication uses for the key-value pairs.)
27+ listMembers []string
2928
30- itemsToFilter := len (states ) - maxKVPairs
31- filteredStates := states [:0 ]
29+ // The value for the Instana-specific list member ("in=...").
30+ instanaTraceStateValue string
31+ }
3232
33- i := 0
34- for ; itemsToFilter > 0 && i < len (states ); i ++ {
33+ // makeState creates a new State with the given values
34+ func NewState (listMembers []string , instanaTraceStateValue string ) State {
35+ return State {
36+ listMembers : listMembers ,
37+ instanaTraceStateValue : instanaTraceStateValue ,
38+ }
39+ }
3540
36- if len (states [i ]) > thresholdLen {
37- itemsToFilter --
38- continue
41+ // ParseState parses the value of `tracestate` header. Empty list items are omitted.
42+ func ParseState (traceStateValue string ) (State , error ) {
43+ listMembers := filterEmptyItems (strings .Split (traceStateValue , "," ))
44+
45+ // Look for the Instana list member first, before discarding any list members due to length restrictions.
46+ var instanaTraceStateValue string
47+ instanaTraceStateIdx := - 1
48+ for i , vd := range listMembers {
49+ matchResult := instanaListMemberRegex .FindStringSubmatch (vd )
50+ if len (matchResult ) == 2 {
51+ instanaTraceStateValue = strings .TrimSpace (matchResult [1 ])
52+ instanaTraceStateIdx = i
53+ break
3954 }
40-
41- filteredStates = append (filteredStates , states [i ])
4255 }
43- filteredStates = append (filteredStates , states [i :]... )
4456
45- if len (filteredStates ) > maxKVPairs {
46- return filteredStates [:maxKVPairs ], nil
57+ if instanaTraceStateIdx >= 0 {
58+ // remove the entry for instana from the array of list members
59+ listMembers = append (listMembers [:instanaTraceStateIdx ], listMembers [instanaTraceStateIdx + 1 :]... )
4760 }
4861
49- return filteredStates , nil
50- }
51-
52- // Add returns a new state prepended with provided vendor-specific data. It removes any existing
53- // entries for this vendor and returns the same state if vendor is empty. If the number of entries
54- // in a state reaches the MaxStateEntries, rest of the items will be truncated
55- func (st State ) Add (vendor , data string ) State {
56- if vendor == "" {
57- return st
62+ // Depending on whether we found an Instana list member, we can either allow 31 or 32 list members from other vendors.
63+ maxListMembers := maxKVPairs
64+ if instanaTraceStateValue != "" {
65+ maxListMembers --
5866 }
5967
60- newSt := make (State , 1 , len (st )+ 1 )
61- newSt [0 ] = vendor + "=" + data
62- newSt = append (newSt , st .Remove (vendor )... )
63-
64- // truncate the state if it reached the max number of entries
65- if len (newSt ) > MaxStateEntries {
66- newSt = newSt [:MaxStateEntries ]
68+ if len (listMembers ) < maxListMembers {
69+ return State {listMembers : listMembers , instanaTraceStateValue : instanaTraceStateValue }, nil
6770 }
6871
69- return newSt
70- }
71-
72- // Fetch retrieves stored vendor-specific data for given vendor
73- func (st State ) Fetch (vendor string ) (string , bool ) {
74- i := st .Index (vendor )
75- if i < 0 {
76- return "" , false
72+ itemsToFilter := len (listMembers ) - maxListMembers
73+ filteredListMembers := listMembers [:0 ]
74+ i := 0
75+ for ; itemsToFilter > 0 && i < len (listMembers ); i ++ {
76+ if len (listMembers [i ]) > thresholdLen {
77+ itemsToFilter --
78+ continue
79+ }
80+ filteredListMembers = append (filteredListMembers , listMembers [i ])
7781 }
82+ filteredListMembers = append (filteredListMembers , listMembers [i :]... )
7883
79- return strings .TrimPrefix (st [i ], vendor + "=" ), true
80- }
81-
82- // Index returns the index of vendor-specific data for given vendor in the state.
83- // It returns -1 if the state does not contain data for this vendor.
84- func (st State ) Index (vendor string ) int {
85- prefix := vendor + "="
86-
87- for i , vd := range st {
88- if strings .HasPrefix (vd , prefix ) {
89- return i
90- }
84+ if len (filteredListMembers ) > maxListMembers {
85+ return State {listMembers : filteredListMembers [:maxListMembers ], instanaTraceStateValue : instanaTraceStateValue }, nil
9186 }
9287
93- return - 1
88+ return State { listMembers : filteredListMembers , instanaTraceStateValue : instanaTraceStateValue }, nil
9489}
9590
96- // Remove returns a new state without data for specified vendor. It returns the same state if vendor is empty
97- func (st State ) Remove (vendor string ) State {
98- if vendor == "" {
99- return st
91+ // SetInstanaValue returns a new state prepended with the provided Instana value. If the original state had an Instana
92+ // list member pair, it is discarded/overwritten.
93+ func (st State ) SetInstanaTraceStateValue (instanaTraceStateValue string ) State {
94+ var listMembers []string
95+ if st .instanaTraceStateValue == "" && instanaTraceStateValue != "" && len (st .listMembers ) == maxKVPairs {
96+ // The incoming tracestate had the maximum number of list members but no Instana list member, now we are adding an
97+ // Instana list member, so we would exceed the maximum by one. Hence, we need to discard one of the other list
98+ // members to stay within the limits mandated by the specification.
99+ listMembers = st .listMembers [:maxKVPairs - 1 ]
100+ } else {
101+ listMembers = st .listMembers
100102 }
101103
102- prefix := vendor + "="
103-
104- var newSt State
105- for _ , vd := range st {
106- if ! strings .HasPrefix (vd , prefix ) {
107- newSt = append (newSt , vd )
108- }
109- }
104+ return State {listMembers : listMembers , instanaTraceStateValue : instanaTraceStateValue }
105+ }
110106
111- return newSt
107+ // FetchInstanaTraceStateValue retrieves the value of the Instana tracestate list member, if any.
108+ func (st State ) FetchInstanaTraceStateValue () (string , bool ) {
109+ return st .instanaTraceStateValue , st .instanaTraceStateValue != ""
112110}
113111
114112// String returns string representation of a trace state. The returned value is compatible with the
115- // `tracestate` header format
113+ // `tracestate` header format. If the state has an Instana-specific list member, that one is always rendered first. This
114+ // is optimized for the use case of injecting the string represenation of the tracestate header into downstream
115+ // requests.
116116func (st State ) String () string {
117- if len (st ) == 0 {
117+ if len (st . listMembers ) == 0 && st . instanaTraceStateValue == "" {
118118 return ""
119119 }
120+ if len (st .listMembers ) == 0 {
121+ return VendorInstana + "=" + st .instanaTraceStateValue
122+ }
120123
121124 buf := bytes .NewBuffer (nil )
122- for _ , vd := range st {
125+ if st .instanaTraceStateValue != "" {
126+ buf .WriteString (VendorInstana )
127+ buf .WriteString ("=" )
128+ buf .WriteString (st .instanaTraceStateValue )
129+ buf .WriteString ("," )
130+ }
131+ for _ , vd := range st .listMembers {
123132 buf .WriteString (vd )
124133 buf .WriteByte (',' )
125134 }
0 commit comments