trace_state.h 9.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323
  1. // Copyright The OpenTelemetry Authors
  2. // SPDX-License-Identifier: Apache-2.0
  3. #pragma once
  4. #include <ctype.h>
  5. #include <cstddef>
  6. #include <string>
  7. #include <vector>
  8. #include "opentelemetry/common/kv_properties.h"
  9. #include "opentelemetry/nostd/function_ref.h"
  10. #include "opentelemetry/nostd/shared_ptr.h"
  11. #include "opentelemetry/nostd/string_view.h"
  12. #include "opentelemetry/nostd/unique_ptr.h"
  13. #include "opentelemetry/version.h"
  14. #if OPENTELEMETRY_HAVE_WORKING_REGEX
  15. # include <regex>
  16. #endif
  17. OPENTELEMETRY_BEGIN_NAMESPACE
  18. namespace trace
  19. {
  20. /**
  21. * TraceState carries tracing-system specific context in a list of key-value pairs. TraceState
  22. * allows different vendors to propagate additional information and inter-operate with their legacy
  23. * id formats.
  24. *
  25. * For more information, see the W3C Trace Context specification:
  26. * https://www.w3.org/TR/trace-context
  27. */
  28. class OPENTELEMETRY_EXPORT TraceState
  29. {
  30. public:
  31. static constexpr int kKeyMaxSize = 256;
  32. static constexpr int kValueMaxSize = 256;
  33. static constexpr int kMaxKeyValuePairs = 32;
  34. static constexpr auto kKeyValueSeparator = '=';
  35. static constexpr auto kMembersSeparator = ',';
  36. OPENTELEMETRY_API_SINGLETON static nostd::shared_ptr<TraceState> GetDefault()
  37. {
  38. static nostd::shared_ptr<TraceState> ts{new TraceState()};
  39. return ts;
  40. }
  41. /**
  42. * Returns shared_ptr to a newly created TraceState parsed from the header provided.
  43. * @param header Encoding of the tracestate header defined by
  44. * the W3C Trace Context specification https://www.w3.org/TR/trace-context/
  45. * @return TraceState A new TraceState instance or DEFAULT
  46. */
  47. static nostd::shared_ptr<TraceState> FromHeader(nostd::string_view header) noexcept
  48. {
  49. common::KeyValueStringTokenizer kv_str_tokenizer(header);
  50. size_t cnt = kv_str_tokenizer.NumTokens(); // upper bound on number of kv pairs
  51. if (cnt > kMaxKeyValuePairs)
  52. {
  53. // trace state should be discarded if count exceeds
  54. return GetDefault();
  55. }
  56. nostd::shared_ptr<TraceState> ts(new TraceState(cnt));
  57. bool kv_valid;
  58. nostd::string_view key, value;
  59. while (kv_str_tokenizer.next(kv_valid, key, value) && ts->kv_properties_->Size() < cnt)
  60. {
  61. if (kv_valid == false)
  62. {
  63. return GetDefault();
  64. }
  65. if (!IsValidKey(key) || !IsValidValue(value))
  66. {
  67. // invalid header. return empty TraceState
  68. ts->kv_properties_.reset(new common::KeyValueProperties());
  69. break;
  70. }
  71. ts->kv_properties_->AddEntry(key, value);
  72. }
  73. return ts;
  74. }
  75. /**
  76. * Creates a w3c tracestate header from TraceState object
  77. */
  78. std::string ToHeader() const noexcept
  79. {
  80. std::string header_s;
  81. bool first = true;
  82. kv_properties_->GetAllEntries(
  83. [&header_s, &first](nostd::string_view key, nostd::string_view value) noexcept {
  84. if (!first)
  85. {
  86. header_s.append(1, kMembersSeparator);
  87. }
  88. else
  89. {
  90. first = false;
  91. }
  92. header_s.append(std::string(key.data(), key.size()));
  93. header_s.append(1, kKeyValueSeparator);
  94. header_s.append(std::string(value.data(), value.size()));
  95. return true;
  96. });
  97. return header_s;
  98. }
  99. /**
  100. * Returns `value` associated with `key` passed as argument
  101. * Returns empty string if key is invalid or not found
  102. */
  103. bool Get(nostd::string_view key, std::string &value) const noexcept
  104. {
  105. if (!IsValidKey(key))
  106. {
  107. return false;
  108. }
  109. return kv_properties_->GetValue(key, value);
  110. }
  111. /**
  112. * Returns shared_ptr of `new` TraceState object with following mutations applied to the existing
  113. * instance: Update Key value: The updated value must be moved to beginning of List Add : The new
  114. * key-value pair SHOULD be added to beginning of List
  115. *
  116. * If the provided key-value pair is invalid, or results in transtate that violates the
  117. * tracecontext specification, empty TraceState instance will be returned.
  118. *
  119. * If the existing object has maximum list members, it's copy is returned.
  120. */
  121. nostd::shared_ptr<TraceState> Set(const nostd::string_view &key,
  122. const nostd::string_view &value) noexcept
  123. {
  124. auto curr_size = kv_properties_->Size();
  125. if (!IsValidKey(key) || !IsValidValue(value))
  126. {
  127. // max size reached or invalid key/value. Returning empty TraceState
  128. return TraceState::GetDefault();
  129. }
  130. auto allocate_size = curr_size;
  131. if (curr_size < kMaxKeyValuePairs)
  132. {
  133. allocate_size += 1;
  134. }
  135. nostd::shared_ptr<TraceState> ts(new TraceState(allocate_size));
  136. if (curr_size < kMaxKeyValuePairs)
  137. {
  138. // add new field first
  139. ts->kv_properties_->AddEntry(key, value);
  140. }
  141. // add rest of the fields.
  142. kv_properties_->GetAllEntries([&ts](nostd::string_view key, nostd::string_view value) {
  143. ts->kv_properties_->AddEntry(key, value);
  144. return true;
  145. });
  146. return ts;
  147. }
  148. /**
  149. * Returns shared_ptr to a `new` TraceState object after removing the attribute with given key (
  150. * if present )
  151. * @returns empty TraceState object if key is invalid
  152. * @returns copy of original TraceState object if key is not present (??)
  153. */
  154. nostd::shared_ptr<TraceState> Delete(const nostd::string_view &key) noexcept
  155. {
  156. if (!IsValidKey(key))
  157. {
  158. return TraceState::GetDefault();
  159. }
  160. auto curr_size = kv_properties_->Size();
  161. auto allocate_size = curr_size;
  162. std::string unused;
  163. if (kv_properties_->GetValue(key, unused))
  164. {
  165. allocate_size -= 1;
  166. }
  167. nostd::shared_ptr<TraceState> ts(new TraceState(allocate_size));
  168. kv_properties_->GetAllEntries(
  169. [&ts, &key](nostd::string_view e_key, nostd::string_view e_value) {
  170. if (key != e_key)
  171. ts->kv_properties_->AddEntry(e_key, e_value);
  172. return true;
  173. });
  174. return ts;
  175. }
  176. // Returns true if there are no keys, false otherwise.
  177. bool Empty() const noexcept { return kv_properties_->Size() == 0; }
  178. // @return all key-values entris by repeatedly invoking the function reference passed as argument
  179. // for each entry
  180. bool GetAllEntries(
  181. nostd::function_ref<bool(nostd::string_view, nostd::string_view)> callback) const noexcept
  182. {
  183. return kv_properties_->GetAllEntries(callback);
  184. }
  185. /** Returns whether key is a valid key. See https://www.w3.org/TR/trace-context/#key
  186. * Identifiers MUST begin with a lowercase letter or a digit, and can only contain
  187. * lowercase letters (a-z), digits (0-9), underscores (_), dashes (-), asterisks (*),
  188. * and forward slashes (/).
  189. * For multi-tenant vendor scenarios, an at sign (@) can be used to prefix the vendor name.
  190. *
  191. */
  192. static bool IsValidKey(nostd::string_view key)
  193. {
  194. #if OPENTELEMETRY_HAVE_WORKING_REGEX
  195. return IsValidKeyRegEx(key);
  196. #else
  197. return IsValidKeyNonRegEx(key);
  198. #endif
  199. }
  200. /** Returns whether value is a valid value. See https://www.w3.org/TR/trace-context/#value
  201. * The value is an opaque string containing up to 256 printable ASCII (RFC0020)
  202. * characters ((i.e., the range 0x20 to 0x7E) except comma , and equal =)
  203. */
  204. static bool IsValidValue(nostd::string_view value)
  205. {
  206. #if OPENTELEMETRY_HAVE_WORKING_REGEX
  207. return IsValidValueRegEx(value);
  208. #else
  209. return IsValidValueNonRegEx(value);
  210. #endif
  211. }
  212. private:
  213. TraceState() : kv_properties_(new common::KeyValueProperties()) {}
  214. TraceState(size_t size) : kv_properties_(new common::KeyValueProperties(size)) {}
  215. static nostd::string_view TrimString(nostd::string_view str, size_t left, size_t right)
  216. {
  217. while (str[static_cast<std::size_t>(right)] == ' ' && left < right)
  218. {
  219. right--;
  220. }
  221. while (str[static_cast<std::size_t>(left)] == ' ' && left < right)
  222. {
  223. left++;
  224. }
  225. return str.substr(left, right - left + 1);
  226. }
  227. #if OPENTELEMETRY_HAVE_WORKING_REGEX
  228. static bool IsValidKeyRegEx(nostd::string_view key)
  229. {
  230. static std::regex reg_key("^[a-z0-9][a-z0-9*_\\-/]{0,255}$");
  231. static std::regex reg_key_multitenant(
  232. "^[a-z0-9][a-z0-9*_\\-/]{0,240}(@)[a-z0-9][a-z0-9*_\\-/]{0,13}$");
  233. std::string key_s(key.data(), key.size());
  234. if (std::regex_match(key_s, reg_key) || std::regex_match(key_s, reg_key_multitenant))
  235. {
  236. return true;
  237. }
  238. return false;
  239. }
  240. static bool IsValidValueRegEx(nostd::string_view value)
  241. {
  242. // Hex 0x20 to 0x2B, 0x2D to 0x3C, 0x3E to 0x7E
  243. static std::regex reg_value(
  244. "^[\\x20-\\x2B\\x2D-\\x3C\\x3E-\\x7E]{0,255}[\\x21-\\x2B\\x2D-\\x3C\\x3E-\\x7E]$");
  245. // Need to benchmark without regex, as a string object is created here.
  246. return std::regex_match(std::string(value.data(), value.size()), reg_value);
  247. }
  248. #else
  249. static bool IsValidKeyNonRegEx(nostd::string_view key)
  250. {
  251. if (key.empty() || key.size() > kKeyMaxSize || !IsLowerCaseAlphaOrDigit(key[0]))
  252. {
  253. return false;
  254. }
  255. int ats = 0;
  256. for (const char c : key)
  257. {
  258. if (!IsLowerCaseAlphaOrDigit(c) && c != '_' && c != '-' && c != '@' && c != '*' && c != '/')
  259. {
  260. return false;
  261. }
  262. if ((c == '@') && (++ats > 1))
  263. {
  264. return false;
  265. }
  266. }
  267. return true;
  268. }
  269. static bool IsValidValueNonRegEx(nostd::string_view value)
  270. {
  271. if (value.empty() || value.size() > kValueMaxSize)
  272. {
  273. return false;
  274. }
  275. for (const char c : value)
  276. {
  277. if (c < ' ' || c > '~' || c == ',' || c == '=')
  278. {
  279. return false;
  280. }
  281. }
  282. return true;
  283. }
  284. #endif
  285. static bool IsLowerCaseAlphaOrDigit(char c) { return isdigit(c) || islower(c); }
  286. private:
  287. // Store entries in a C-style array to avoid using std::array or std::vector.
  288. nostd::unique_ptr<common::KeyValueProperties> kv_properties_;
  289. };
  290. } // namespace trace
  291. OPENTELEMETRY_END_NAMESPACE