tracestate.go 6.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217
  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. package trace
  15. import (
  16. "encoding/json"
  17. "fmt"
  18. "regexp"
  19. "strings"
  20. )
  21. var (
  22. maxListMembers = 32
  23. listDelimiter = ","
  24. // based on the W3C Trace Context specification, see
  25. // https://www.w3.org/TR/trace-context-1/#tracestate-header
  26. noTenantKeyFormat = `[a-z][_0-9a-z\-\*\/]{0,255}`
  27. withTenantKeyFormat = `[a-z0-9][_0-9a-z\-\*\/]{0,240}@[a-z][_0-9a-z\-\*\/]{0,13}`
  28. valueFormat = `[\x20-\x2b\x2d-\x3c\x3e-\x7e]{0,255}[\x21-\x2b\x2d-\x3c\x3e-\x7e]`
  29. keyRe = regexp.MustCompile(`^((` + noTenantKeyFormat + `)|(` + withTenantKeyFormat + `))$`)
  30. valueRe = regexp.MustCompile(`^(` + valueFormat + `)$`)
  31. memberRe = regexp.MustCompile(`^\s*((` + noTenantKeyFormat + `)|(` + withTenantKeyFormat + `))=(` + valueFormat + `)\s*$`)
  32. errInvalidKey errorConst = "invalid tracestate key"
  33. errInvalidValue errorConst = "invalid tracestate value"
  34. errInvalidMember errorConst = "invalid tracestate list-member"
  35. errMemberNumber errorConst = "too many list-members in tracestate"
  36. errDuplicate errorConst = "duplicate list-member in tracestate"
  37. )
  38. type member struct {
  39. Key string
  40. Value string
  41. }
  42. func newMember(key, value string) (member, error) {
  43. if !keyRe.MatchString(key) {
  44. return member{}, fmt.Errorf("%w: %s", errInvalidKey, key)
  45. }
  46. if !valueRe.MatchString(value) {
  47. return member{}, fmt.Errorf("%w: %s", errInvalidValue, value)
  48. }
  49. return member{Key: key, Value: value}, nil
  50. }
  51. func parseMemeber(m string) (member, error) {
  52. matches := memberRe.FindStringSubmatch(m)
  53. if len(matches) != 5 {
  54. return member{}, fmt.Errorf("%w: %s", errInvalidMember, m)
  55. }
  56. return member{
  57. Key: matches[1],
  58. Value: matches[4],
  59. }, nil
  60. }
  61. // String encodes member into a string compliant with the W3C Trace Context
  62. // specification.
  63. func (m member) String() string {
  64. return fmt.Sprintf("%s=%s", m.Key, m.Value)
  65. }
  66. // TraceState provides additional vendor-specific trace identification
  67. // information across different distributed tracing systems. It represents an
  68. // immutable list consisting of key/value pairs, each pair is referred to as a
  69. // list-member.
  70. //
  71. // TraceState conforms to the W3C Trace Context specification
  72. // (https://www.w3.org/TR/trace-context-1). All operations that create or copy
  73. // a TraceState do so by validating all input and will only produce TraceState
  74. // that conform to the specification. Specifically, this means that all
  75. // list-member's key/value pairs are valid, no duplicate list-members exist,
  76. // and the maximum number of list-members (32) is not exceeded.
  77. type TraceState struct { //nolint:revive // revive complains about stutter of `trace.TraceState`
  78. // list is the members in order.
  79. list []member
  80. }
  81. var _ json.Marshaler = TraceState{}
  82. // ParseTraceState attempts to decode a TraceState from the passed
  83. // string. It returns an error if the input is invalid according to the W3C
  84. // Trace Context specification.
  85. func ParseTraceState(tracestate string) (TraceState, error) {
  86. if tracestate == "" {
  87. return TraceState{}, nil
  88. }
  89. wrapErr := func(err error) error {
  90. return fmt.Errorf("failed to parse tracestate: %w", err)
  91. }
  92. var members []member
  93. found := make(map[string]struct{})
  94. for _, memberStr := range strings.Split(tracestate, listDelimiter) {
  95. if len(memberStr) == 0 {
  96. continue
  97. }
  98. m, err := parseMemeber(memberStr)
  99. if err != nil {
  100. return TraceState{}, wrapErr(err)
  101. }
  102. if _, ok := found[m.Key]; ok {
  103. return TraceState{}, wrapErr(errDuplicate)
  104. }
  105. found[m.Key] = struct{}{}
  106. members = append(members, m)
  107. if n := len(members); n > maxListMembers {
  108. return TraceState{}, wrapErr(errMemberNumber)
  109. }
  110. }
  111. return TraceState{list: members}, nil
  112. }
  113. // MarshalJSON marshals the TraceState into JSON.
  114. func (ts TraceState) MarshalJSON() ([]byte, error) {
  115. return json.Marshal(ts.String())
  116. }
  117. // String encodes the TraceState into a string compliant with the W3C
  118. // Trace Context specification. The returned string will be invalid if the
  119. // TraceState contains any invalid members.
  120. func (ts TraceState) String() string {
  121. members := make([]string, len(ts.list))
  122. for i, m := range ts.list {
  123. members[i] = m.String()
  124. }
  125. return strings.Join(members, listDelimiter)
  126. }
  127. // Get returns the value paired with key from the corresponding TraceState
  128. // list-member if it exists, otherwise an empty string is returned.
  129. func (ts TraceState) Get(key string) string {
  130. for _, member := range ts.list {
  131. if member.Key == key {
  132. return member.Value
  133. }
  134. }
  135. return ""
  136. }
  137. // Insert adds a new list-member defined by the key/value pair to the
  138. // TraceState. If a list-member already exists for the given key, that
  139. // list-member's value is updated. The new or updated list-member is always
  140. // moved to the beginning of the TraceState as specified by the W3C Trace
  141. // Context specification.
  142. //
  143. // If key or value are invalid according to the W3C Trace Context
  144. // specification an error is returned with the original TraceState.
  145. //
  146. // If adding a new list-member means the TraceState would have more members
  147. // than is allowed an error is returned instead with the original TraceState.
  148. func (ts TraceState) Insert(key, value string) (TraceState, error) {
  149. m, err := newMember(key, value)
  150. if err != nil {
  151. return ts, err
  152. }
  153. cTS := ts.Delete(key)
  154. if cTS.Len()+1 > maxListMembers {
  155. // TODO (MrAlias): When the second version of the Trace Context
  156. // specification is published this needs to not return an error.
  157. // Instead it should drop the "right-most" member and insert the new
  158. // member at the front.
  159. //
  160. // https://github.com/w3c/trace-context/pull/448
  161. return ts, fmt.Errorf("failed to insert: %w", errMemberNumber)
  162. }
  163. cTS.list = append(cTS.list, member{})
  164. copy(cTS.list[1:], cTS.list)
  165. cTS.list[0] = m
  166. return cTS, nil
  167. }
  168. // Delete returns a copy of the TraceState with the list-member identified by
  169. // key removed.
  170. func (ts TraceState) Delete(key string) TraceState {
  171. members := make([]member, ts.Len())
  172. copy(members, ts.list)
  173. for i, member := range ts.list {
  174. if member.Key == key {
  175. members = append(members[:i], members[i+1:]...)
  176. // TraceState should contain no duplicate members.
  177. break
  178. }
  179. }
  180. return TraceState{list: members}
  181. }
  182. // Len returns the number of list-members in the TraceState.
  183. func (ts TraceState) Len() int {
  184. return len(ts.list)
  185. }