tracestate.go (6691B)
1 // Copyright The OpenTelemetry Authors 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package trace // import "go.opentelemetry.io/otel/trace" 16 17 import ( 18 "encoding/json" 19 "fmt" 20 "regexp" 21 "strings" 22 ) 23 24 const ( 25 maxListMembers = 32 26 27 listDelimiter = "," 28 29 // based on the W3C Trace Context specification, see 30 // https://www.w3.org/TR/trace-context-1/#tracestate-header 31 noTenantKeyFormat = `[a-z][_0-9a-z\-\*\/]{0,255}` 32 withTenantKeyFormat = `[a-z0-9][_0-9a-z\-\*\/]{0,240}@[a-z][_0-9a-z\-\*\/]{0,13}` 33 valueFormat = `[\x20-\x2b\x2d-\x3c\x3e-\x7e]{0,255}[\x21-\x2b\x2d-\x3c\x3e-\x7e]` 34 35 errInvalidKey errorConst = "invalid tracestate key" 36 errInvalidValue errorConst = "invalid tracestate value" 37 errInvalidMember errorConst = "invalid tracestate list-member" 38 errMemberNumber errorConst = "too many list-members in tracestate" 39 errDuplicate errorConst = "duplicate list-member in tracestate" 40 ) 41 42 var ( 43 keyRe = regexp.MustCompile(`^((` + noTenantKeyFormat + `)|(` + withTenantKeyFormat + `))$`) 44 valueRe = regexp.MustCompile(`^(` + valueFormat + `)$`) 45 memberRe = regexp.MustCompile(`^\s*((` + noTenantKeyFormat + `)|(` + withTenantKeyFormat + `))=(` + valueFormat + `)\s*$`) 46 ) 47 48 type member struct { 49 Key string 50 Value string 51 } 52 53 func newMember(key, value string) (member, error) { 54 if !keyRe.MatchString(key) { 55 return member{}, fmt.Errorf("%w: %s", errInvalidKey, key) 56 } 57 if !valueRe.MatchString(value) { 58 return member{}, fmt.Errorf("%w: %s", errInvalidValue, value) 59 } 60 return member{Key: key, Value: value}, nil 61 } 62 63 func parseMember(m string) (member, error) { 64 matches := memberRe.FindStringSubmatch(m) 65 if len(matches) != 5 { 66 return member{}, fmt.Errorf("%w: %s", errInvalidMember, m) 67 } 68 69 return member{ 70 Key: matches[1], 71 Value: matches[4], 72 }, nil 73 } 74 75 // String encodes member into a string compliant with the W3C Trace Context 76 // specification. 77 func (m member) String() string { 78 return fmt.Sprintf("%s=%s", m.Key, m.Value) 79 } 80 81 // TraceState provides additional vendor-specific trace identification 82 // information across different distributed tracing systems. It represents an 83 // immutable list consisting of key/value pairs, each pair is referred to as a 84 // list-member. 85 // 86 // TraceState conforms to the W3C Trace Context specification 87 // (https://www.w3.org/TR/trace-context-1). All operations that create or copy 88 // a TraceState do so by validating all input and will only produce TraceState 89 // that conform to the specification. Specifically, this means that all 90 // list-member's key/value pairs are valid, no duplicate list-members exist, 91 // and the maximum number of list-members (32) is not exceeded. 92 type TraceState struct { //nolint:revive // revive complains about stutter of `trace.TraceState` 93 // list is the members in order. 94 list []member 95 } 96 97 var _ json.Marshaler = TraceState{} 98 99 // ParseTraceState attempts to decode a TraceState from the passed 100 // string. It returns an error if the input is invalid according to the W3C 101 // Trace Context specification. 102 func ParseTraceState(tracestate string) (TraceState, error) { 103 if tracestate == "" { 104 return TraceState{}, nil 105 } 106 107 wrapErr := func(err error) error { 108 return fmt.Errorf("failed to parse tracestate: %w", err) 109 } 110 111 var members []member 112 found := make(map[string]struct{}) 113 for _, memberStr := range strings.Split(tracestate, listDelimiter) { 114 if len(memberStr) == 0 { 115 continue 116 } 117 118 m, err := parseMember(memberStr) 119 if err != nil { 120 return TraceState{}, wrapErr(err) 121 } 122 123 if _, ok := found[m.Key]; ok { 124 return TraceState{}, wrapErr(errDuplicate) 125 } 126 found[m.Key] = struct{}{} 127 128 members = append(members, m) 129 if n := len(members); n > maxListMembers { 130 return TraceState{}, wrapErr(errMemberNumber) 131 } 132 } 133 134 return TraceState{list: members}, nil 135 } 136 137 // MarshalJSON marshals the TraceState into JSON. 138 func (ts TraceState) MarshalJSON() ([]byte, error) { 139 return json.Marshal(ts.String()) 140 } 141 142 // String encodes the TraceState into a string compliant with the W3C 143 // Trace Context specification. The returned string will be invalid if the 144 // TraceState contains any invalid members. 145 func (ts TraceState) String() string { 146 members := make([]string, len(ts.list)) 147 for i, m := range ts.list { 148 members[i] = m.String() 149 } 150 return strings.Join(members, listDelimiter) 151 } 152 153 // Get returns the value paired with key from the corresponding TraceState 154 // list-member if it exists, otherwise an empty string is returned. 155 func (ts TraceState) Get(key string) string { 156 for _, member := range ts.list { 157 if member.Key == key { 158 return member.Value 159 } 160 } 161 162 return "" 163 } 164 165 // Insert adds a new list-member defined by the key/value pair to the 166 // TraceState. If a list-member already exists for the given key, that 167 // list-member's value is updated. The new or updated list-member is always 168 // moved to the beginning of the TraceState as specified by the W3C Trace 169 // Context specification. 170 // 171 // If key or value are invalid according to the W3C Trace Context 172 // specification an error is returned with the original TraceState. 173 // 174 // If adding a new list-member means the TraceState would have more members 175 // then is allowed, the new list-member will be inserted and the right-most 176 // list-member will be dropped in the returned TraceState. 177 func (ts TraceState) Insert(key, value string) (TraceState, error) { 178 m, err := newMember(key, value) 179 if err != nil { 180 return ts, err 181 } 182 183 cTS := ts.Delete(key) 184 if cTS.Len()+1 <= maxListMembers { 185 cTS.list = append(cTS.list, member{}) 186 } 187 // When the number of members exceeds capacity, drop the "right-most". 188 copy(cTS.list[1:], cTS.list) 189 cTS.list[0] = m 190 191 return cTS, nil 192 } 193 194 // Delete returns a copy of the TraceState with the list-member identified by 195 // key removed. 196 func (ts TraceState) Delete(key string) TraceState { 197 members := make([]member, ts.Len()) 198 copy(members, ts.list) 199 for i, member := range ts.list { 200 if member.Key == key { 201 members = append(members[:i], members[i+1:]...) 202 // TraceState should contain no duplicate members. 203 break 204 } 205 } 206 return TraceState{list: members} 207 } 208 209 // Len returns the number of list-members in the TraceState. 210 func (ts TraceState) Len() int { 211 return len(ts.list) 212 }