File: trace_state.h

package info (click to toggle)
opentelemetry-cpp 1.23.0-2
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 11,372 kB
  • sloc: cpp: 96,239; sh: 1,766; makefile: 36; python: 31
file content (323 lines) | stat: -rw-r--r-- 9,850 bytes parent folder | download | duplicates (10)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0

#pragma once

#include <ctype.h>
#include <cstddef>
#include <string>
#include <vector>

#include "opentelemetry/common/kv_properties.h"
#include "opentelemetry/nostd/function_ref.h"
#include "opentelemetry/nostd/shared_ptr.h"
#include "opentelemetry/nostd/string_view.h"
#include "opentelemetry/nostd/unique_ptr.h"
#include "opentelemetry/version.h"

#if OPENTELEMETRY_HAVE_WORKING_REGEX
#  include <regex>
#endif

OPENTELEMETRY_BEGIN_NAMESPACE
namespace trace
{

/**
 * TraceState carries tracing-system specific context in a list of key-value pairs. TraceState
 * allows different vendors to propagate additional information and inter-operate with their legacy
 * id formats.
 *
 * For more information, see the W3C Trace Context specification:
 * https://www.w3.org/TR/trace-context
 */
class OPENTELEMETRY_EXPORT TraceState
{
public:
  static constexpr int kKeyMaxSize         = 256;
  static constexpr int kValueMaxSize       = 256;
  static constexpr int kMaxKeyValuePairs   = 32;
  static constexpr auto kKeyValueSeparator = '=';
  static constexpr auto kMembersSeparator  = ',';

  OPENTELEMETRY_API_SINGLETON static nostd::shared_ptr<TraceState> GetDefault()
  {
    static nostd::shared_ptr<TraceState> ts{new TraceState()};
    return ts;
  }

  /**
   * Returns shared_ptr to a newly created TraceState parsed from the header provided.
   * @param header Encoding of the tracestate header defined by
   * the W3C Trace Context specification https://www.w3.org/TR/trace-context/
   * @return TraceState A new TraceState instance or DEFAULT
   */
  static nostd::shared_ptr<TraceState> FromHeader(nostd::string_view header) noexcept
  {

    common::KeyValueStringTokenizer kv_str_tokenizer(header);
    size_t cnt = kv_str_tokenizer.NumTokens();  // upper bound on number of kv pairs
    if (cnt > kMaxKeyValuePairs)
    {
      // trace state should be discarded if count exceeds
      return GetDefault();
    }

    nostd::shared_ptr<TraceState> ts(new TraceState(cnt));
    bool kv_valid;
    nostd::string_view key, value;
    while (kv_str_tokenizer.next(kv_valid, key, value) && ts->kv_properties_->Size() < cnt)
    {
      if (kv_valid == false)
      {
        return GetDefault();
      }

      if (!IsValidKey(key) || !IsValidValue(value))
      {
        // invalid header. return empty TraceState
        ts->kv_properties_.reset(new common::KeyValueProperties());
        break;
      }

      ts->kv_properties_->AddEntry(key, value);
    }

    return ts;
  }

  /**
   * Creates a w3c tracestate header from TraceState object
   */
  std::string ToHeader() const noexcept
  {
    std::string header_s;
    bool first = true;
    kv_properties_->GetAllEntries(
        [&header_s, &first](nostd::string_view key, nostd::string_view value) noexcept {
          if (!first)
          {
            header_s.append(1, kMembersSeparator);
          }
          else
          {
            first = false;
          }
          header_s.append(std::string(key.data(), key.size()));
          header_s.append(1, kKeyValueSeparator);
          header_s.append(std::string(value.data(), value.size()));
          return true;
        });
    return header_s;
  }

  /**
   *  Returns `value` associated with `key` passed as argument
   *  Returns empty string if key is invalid  or not found
   */
  bool Get(nostd::string_view key, std::string &value) const noexcept
  {
    if (!IsValidKey(key))
    {
      return false;
    }

    return kv_properties_->GetValue(key, value);
  }

  /**
   * Returns shared_ptr of `new` TraceState object with following mutations applied to the existing
   * instance: Update Key value: The updated value must be moved to beginning of List Add : The new
   * key-value pair SHOULD be added to beginning of List
   *
   * If the provided key-value pair is invalid, or results in transtate that violates the
   * tracecontext specification, empty TraceState instance will be returned.
   *
   * If the existing object has maximum list members, it's copy is returned.
   */
  nostd::shared_ptr<TraceState> Set(const nostd::string_view &key,
                                    const nostd::string_view &value) noexcept
  {
    auto curr_size = kv_properties_->Size();
    if (!IsValidKey(key) || !IsValidValue(value))
    {
      // max size reached or invalid key/value. Returning empty TraceState
      return TraceState::GetDefault();
    }
    auto allocate_size = curr_size;
    if (curr_size < kMaxKeyValuePairs)
    {
      allocate_size += 1;
    }
    nostd::shared_ptr<TraceState> ts(new TraceState(allocate_size));
    if (curr_size < kMaxKeyValuePairs)
    {
      // add new field first
      ts->kv_properties_->AddEntry(key, value);
    }
    // add rest of the fields.
    kv_properties_->GetAllEntries([&ts](nostd::string_view key, nostd::string_view value) {
      ts->kv_properties_->AddEntry(key, value);
      return true;
    });
    return ts;
  }

  /**
   * Returns shared_ptr to a `new` TraceState object after removing the attribute with given key (
   * if present )
   * @returns empty TraceState object if key is invalid
   * @returns copy of original TraceState object if key is not present (??)
   */
  nostd::shared_ptr<TraceState> Delete(const nostd::string_view &key) noexcept
  {
    if (!IsValidKey(key))
    {
      return TraceState::GetDefault();
    }
    auto curr_size     = kv_properties_->Size();
    auto allocate_size = curr_size;
    std::string unused;
    if (kv_properties_->GetValue(key, unused))
    {
      allocate_size -= 1;
    }
    nostd::shared_ptr<TraceState> ts(new TraceState(allocate_size));
    kv_properties_->GetAllEntries(
        [&ts, &key](nostd::string_view e_key, nostd::string_view e_value) {
          if (key != e_key)
            ts->kv_properties_->AddEntry(e_key, e_value);
          return true;
        });
    return ts;
  }

  // Returns true if there are no keys, false otherwise.
  bool Empty() const noexcept { return kv_properties_->Size() == 0; }

  // @return all key-values entris by repeatedly invoking the function reference passed as argument
  // for each entry
  bool GetAllEntries(
      nostd::function_ref<bool(nostd::string_view, nostd::string_view)> callback) const noexcept
  {
    return kv_properties_->GetAllEntries(callback);
  }
  /** Returns whether key is a valid key. See https://www.w3.org/TR/trace-context/#key
   * Identifiers MUST begin with a lowercase letter or a digit, and can only contain
   * lowercase letters (a-z), digits (0-9), underscores (_), dashes (-), asterisks (*),
   * and forward slashes (/).
   * For multi-tenant vendor scenarios, an at sign (@) can be used to prefix the vendor name.
   *
   */
  static bool IsValidKey(nostd::string_view key)
  {
#if OPENTELEMETRY_HAVE_WORKING_REGEX
    return IsValidKeyRegEx(key);
#else
    return IsValidKeyNonRegEx(key);
#endif
  }

  /** Returns whether value is a valid value. See https://www.w3.org/TR/trace-context/#value
   * The value is an opaque string containing up to 256 printable ASCII (RFC0020)
   *  characters ((i.e., the range 0x20 to 0x7E) except comma , and equal =)
   */
  static bool IsValidValue(nostd::string_view value)
  {
#if OPENTELEMETRY_HAVE_WORKING_REGEX
    return IsValidValueRegEx(value);
#else
    return IsValidValueNonRegEx(value);
#endif
  }

private:
  TraceState() : kv_properties_(new common::KeyValueProperties()) {}
  TraceState(size_t size) : kv_properties_(new common::KeyValueProperties(size)) {}

  static nostd::string_view TrimString(nostd::string_view str, size_t left, size_t right)
  {
    while (str[static_cast<std::size_t>(right)] == ' ' && left < right)
    {
      right--;
    }
    while (str[static_cast<std::size_t>(left)] == ' ' && left < right)
    {
      left++;
    }
    return str.substr(left, right - left + 1);
  }

#if OPENTELEMETRY_HAVE_WORKING_REGEX
  static bool IsValidKeyRegEx(nostd::string_view key)
  {
    static std::regex reg_key("^[a-z0-9][a-z0-9*_\\-/]{0,255}$");
    static std::regex reg_key_multitenant(
        "^[a-z0-9][a-z0-9*_\\-/]{0,240}(@)[a-z0-9][a-z0-9*_\\-/]{0,13}$");
    std::string key_s(key.data(), key.size());
    if (std::regex_match(key_s, reg_key) || std::regex_match(key_s, reg_key_multitenant))
    {
      return true;
    }
    return false;
  }

  static bool IsValidValueRegEx(nostd::string_view value)
  {
    // Hex 0x20 to 0x2B, 0x2D to 0x3C, 0x3E to 0x7E
    static std::regex reg_value(
        "^[\\x20-\\x2B\\x2D-\\x3C\\x3E-\\x7E]{0,255}[\\x21-\\x2B\\x2D-\\x3C\\x3E-\\x7E]$");
    // Need to benchmark without regex, as a string object is created here.
    return std::regex_match(std::string(value.data(), value.size()), reg_value);
  }
#else
  static bool IsValidKeyNonRegEx(nostd::string_view key)
  {
    if (key.empty() || key.size() > kKeyMaxSize || !IsLowerCaseAlphaOrDigit(key[0]))
    {
      return false;
    }

    int ats = 0;

    for (const char c : key)
    {
      if (!IsLowerCaseAlphaOrDigit(c) && c != '_' && c != '-' && c != '@' && c != '*' && c != '/')
      {
        return false;
      }
      if ((c == '@') && (++ats > 1))
      {
        return false;
      }
    }
    return true;
  }

  static bool IsValidValueNonRegEx(nostd::string_view value)
  {
    if (value.empty() || value.size() > kValueMaxSize)
    {
      return false;
    }

    for (const char c : value)
    {
      if (c < ' ' || c > '~' || c == ',' || c == '=')
      {
        return false;
      }
    }
    return true;
  }
#endif

  static bool IsLowerCaseAlphaOrDigit(char c) { return isdigit(c) || islower(c); }

private:
  // Store entries in a C-style array to avoid using std::array or std::vector.
  nostd::unique_ptr<common::KeyValueProperties> kv_properties_;
};

}  // namespace trace
OPENTELEMETRY_END_NAMESPACE