|
| 1 | +#pragma once |
| 2 | + |
| 3 | +#include <array> |
| 4 | +#include <iostream> |
| 5 | +#include <map> |
| 6 | +#include <string> |
| 7 | +#include "opentelemetry/common/key_value_iterable.h" |
| 8 | +#include "opentelemetry/context/context.h" |
| 9 | +#include "opentelemetry/nostd/shared_ptr.h" |
| 10 | +#include "opentelemetry/nostd/span.h" |
| 11 | +#include "opentelemetry/nostd/string_view.h" |
| 12 | +#include "opentelemetry/nostd/variant.h" |
| 13 | +#include "opentelemetry/trace/default_span.h" |
| 14 | +#include "opentelemetry/trace/propagation/http_text_format.h" |
| 15 | +#include "opentelemetry/trace/span.h" |
| 16 | +#include "opentelemetry/trace/span_context.h" |
| 17 | + |
| 18 | +OPENTELEMETRY_BEGIN_NAMESPACE |
| 19 | +namespace trace |
| 20 | +{ |
| 21 | +namespace propagation |
| 22 | +{ |
| 23 | + |
| 24 | +static const nostd::string_view kB3CombinedHeader = "b3"; |
| 25 | + |
| 26 | +static const nostd::string_view kB3TraceIdHeader = "X-B3-TraceId"; |
| 27 | +static const nostd::string_view kB3SpanIdHeader = "X-B3-SpanId"; |
| 28 | +static const nostd::string_view kB3SampledHeader = "X-B3-Sampled"; |
| 29 | + |
| 30 | +/* |
| 31 | + B3, single header: |
| 32 | + b3: 80f198ee56343ba864fe8b2a57d3eff7-e457b5a2e4d86bd1-1-05e3ac9a4f6e3b90 |
| 33 | + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^ ^ ^^^^^^^^^^^^^^^^ |
| 34 | + 0 TraceId 31 33 SpanId 48 | 52 ParentSpanId 68 |
| 35 | + 50 Debug flag |
| 36 | + Multiheader version: X-B3-Sampled |
| 37 | + X-B3-TraceId X-B3-SpanId X-B3-ParentSpanId (ignored) |
| 38 | +*/ |
| 39 | + |
| 40 | +static const int kTraceIdHexStrLength = 32; |
| 41 | +static const int kSpanIdHexStrLength = 16; |
| 42 | +static const int kTraceFlagHexStrLength = 1; |
| 43 | + |
| 44 | +// The B3PropagatorExtractor class provides an interface that enables extracting context from |
| 45 | +// headers of HTTP requests. HTTP frameworks and clients can integrate with B3Propagator by |
| 46 | +// providing the object containing the headers, and a getter function for the extraction. Based on: |
| 47 | +// https://github.com/open-telemetry/opentelemetry-specification/blob/master/specification/context/api-propagators.md#b3-extract |
| 48 | +template <typename T> |
| 49 | +class B3PropagatorExtractor : public HTTPTextFormat<T> |
| 50 | +{ |
| 51 | +public: |
| 52 | + // Rules that manages how context will be extracted from carrier. |
| 53 | + using Getter = nostd::string_view (*)(const T &carrier, nostd::string_view trace_type); |
| 54 | + |
| 55 | + // Returns the context that is stored in the HTTP header carrier with the getter as extractor. |
| 56 | + context::Context Extract(Getter getter, |
| 57 | + const T &carrier, |
| 58 | + context::Context &context) noexcept override |
| 59 | + { |
| 60 | + SpanContext span_context = ExtractImpl(getter, carrier); |
| 61 | + nostd::shared_ptr<Span> sp{new DefaultSpan(span_context)}; |
| 62 | + return context.SetValue(kSpanKey, sp); |
| 63 | + } |
| 64 | + |
| 65 | + static SpanContext GetCurrentSpan(const context::Context &context) |
| 66 | + { |
| 67 | + context::Context ctx(context); |
| 68 | + context::ContextValue span = ctx.GetValue(kSpanKey); |
| 69 | + if (nostd::holds_alternative<nostd::shared_ptr<Span>>(span)) |
| 70 | + { |
| 71 | + return nostd::get<nostd::shared_ptr<Span>>(span).get()->GetContext(); |
| 72 | + } |
| 73 | + return SpanContext::GetInvalid(); |
| 74 | + } |
| 75 | + |
| 76 | + static TraceId GenerateTraceIdFromString(nostd::string_view trace_id) |
| 77 | + { |
| 78 | + uint8_t buf[kTraceIdHexStrLength / 2]; |
| 79 | + GenerateBuffFromHexStrPad0(trace_id, sizeof(buf), buf); |
| 80 | + return TraceId(buf); |
| 81 | + } |
| 82 | + |
| 83 | + static SpanId GenerateSpanIdFromString(nostd::string_view span_id) |
| 84 | + { |
| 85 | + uint8_t buf[kSpanIdHexStrLength / 2]; |
| 86 | + GenerateBuffFromHexStrPad0(span_id, sizeof(buf), buf); |
| 87 | + return SpanId(buf); |
| 88 | + } |
| 89 | + |
| 90 | + static TraceFlags GenerateTraceFlagsFromString(nostd::string_view trace_flags) |
| 91 | + { |
| 92 | + if (trace_flags.length() != 1 || (trace_flags[0] != '1' && trace_flags[0] != 'd')) |
| 93 | + { // check for invalid length of flags and treat 'd' as sampled |
| 94 | + return TraceFlags(0); |
| 95 | + } |
| 96 | + return TraceFlags(TraceFlags::kIsSampled); |
| 97 | + } |
| 98 | + |
| 99 | +private: |
| 100 | + // Converts hex numbers (string_view) into bytes stored in a buffer and pads buffer with 0. |
| 101 | + static void GenerateBuffFromHexStrPad0(nostd::string_view hexStr, int bufSize, uint8_t *buf) |
| 102 | + { // we are doing this starting from "right" side for left-padding |
| 103 | + nostd::string_view::size_type posInp = hexStr.length(); |
| 104 | + int posOut = bufSize; |
| 105 | + while (posOut--) |
| 106 | + { |
| 107 | + int val = 0; |
| 108 | + if (posInp) |
| 109 | + { |
| 110 | + int hexDigit2 = HexToInt(hexStr[--posInp]); // low nibble |
| 111 | + int hexDigit1 = 0; |
| 112 | + if (posInp) |
| 113 | + { |
| 114 | + hexDigit1 = HexToInt(hexStr[--posInp]); |
| 115 | + } |
| 116 | + if (hexDigit1 < 0 || hexDigit2 < 0) |
| 117 | + { // malformed hex sequence. Fill entire buffer with zeroes. |
| 118 | + for (int j = 0; j < bufSize; j++) |
| 119 | + { |
| 120 | + buf[j] = 0; |
| 121 | + } |
| 122 | + return; |
| 123 | + } |
| 124 | + val = hexDigit1 * 16 + hexDigit2; |
| 125 | + } |
| 126 | + buf[posOut] = val; |
| 127 | + } |
| 128 | + } |
| 129 | + |
| 130 | + // Converts a single character to a corresponding integer (e.g. '1' to 1), return -1 |
| 131 | + // if the character is not a valid number in hex. |
| 132 | + static int8_t HexToInt(char c) |
| 133 | + { |
| 134 | + if (c >= '0' && c <= '9') |
| 135 | + { |
| 136 | + return (int8_t)(c - '0'); |
| 137 | + } |
| 138 | + else if (c >= 'a' && c <= 'f') |
| 139 | + { |
| 140 | + return (int8_t)(c - 'a' + 10); |
| 141 | + } |
| 142 | + else if (c >= 'A' && c <= 'F') |
| 143 | + { |
| 144 | + return (int8_t)(c - 'A' + 10); |
| 145 | + } |
| 146 | + else |
| 147 | + { |
| 148 | + return -1; |
| 149 | + } |
| 150 | + } |
| 151 | + |
| 152 | + static SpanContext ExtractImpl(Getter getter, const T &carrier) |
| 153 | + { |
| 154 | + // all these are hex values |
| 155 | + nostd::string_view trace_id; |
| 156 | + nostd::string_view span_id; |
| 157 | + nostd::string_view trace_flags; |
| 158 | + |
| 159 | + // first let's try a single-header variant |
| 160 | + auto singleB3Header = getter(carrier, kB3CombinedHeader); |
| 161 | + if (!singleB3Header.empty()) |
| 162 | + { |
| 163 | + // From: https://github.com/openzipkin/b3-propagation/blob/master/RATIONALE.md |
| 164 | + // trace_id can be 16 or 32 chars |
| 165 | + auto firstSep = singleB3Header.find('-'); |
| 166 | + trace_id = singleB3Header.substr(0, firstSep); |
| 167 | + if (firstSep != nostd::string_view::npos) |
| 168 | + { // at least two fields are required |
| 169 | + auto secondSep = singleB3Header.find('-', firstSep + 1); |
| 170 | + if (secondSep != nostd::string_view::npos) |
| 171 | + { // more than two fields - check also trace_flags |
| 172 | + span_id = singleB3Header.substr(firstSep + 1, secondSep - firstSep - 1); |
| 173 | + if (secondSep + 1 < singleB3Header.size()) |
| 174 | + { |
| 175 | + trace_flags = singleB3Header.substr(secondSep + 1, kTraceFlagHexStrLength); |
| 176 | + } |
| 177 | + } |
| 178 | + else |
| 179 | + { |
| 180 | + span_id = singleB3Header.substr(firstSep + 1); |
| 181 | + } |
| 182 | + } |
| 183 | + } |
| 184 | + else |
| 185 | + { |
| 186 | + trace_id = getter(carrier, kB3TraceIdHeader); |
| 187 | + span_id = getter(carrier, kB3SpanIdHeader); |
| 188 | + trace_flags = getter(carrier, kB3SampledHeader); |
| 189 | + } |
| 190 | + |
| 191 | + // now convert hex to objects |
| 192 | + TraceId trace_id_obj = GenerateTraceIdFromString(trace_id); |
| 193 | + SpanId span_id_obj = GenerateSpanIdFromString(span_id); |
| 194 | + if (!trace_id_obj.IsValid() || !span_id_obj.IsValid()) |
| 195 | + { |
| 196 | + return SpanContext(false, false); |
| 197 | + } |
| 198 | + TraceFlags trace_flags_obj = GenerateTraceFlagsFromString(trace_flags); |
| 199 | + return SpanContext(trace_id_obj, span_id_obj, trace_flags_obj, true); |
| 200 | + } |
| 201 | +}; |
| 202 | + |
| 203 | +// The B3Propagator class provides interface that enables extracting and injecting context into |
| 204 | +// single header of HTTP Request. |
| 205 | +template <typename T> |
| 206 | +class B3Propagator : public B3PropagatorExtractor<T> |
| 207 | +{ |
| 208 | +public: |
| 209 | + // Rules that manages how context will be injected to carrier. |
| 210 | + using Setter = void (*)(T &carrier, |
| 211 | + nostd::string_view trace_type, |
| 212 | + nostd::string_view trace_description); |
| 213 | + // Sets the context for a HTTP header carrier with self defined rules. |
| 214 | + void Inject(Setter setter, T &carrier, const context::Context &context) noexcept override |
| 215 | + { |
| 216 | + SpanContext span_context = B3PropagatorExtractor<T>::GetCurrentSpan(context); |
| 217 | + if (!span_context.IsValid()) |
| 218 | + { |
| 219 | + return; |
| 220 | + } |
| 221 | + char trace_id[kTraceIdHexStrLength]; |
| 222 | + TraceId(span_context.trace_id()).ToLowerBase16(trace_id); |
| 223 | + char span_id[kSpanIdHexStrLength]; |
| 224 | + SpanId(span_context.span_id()).ToLowerBase16(span_id); |
| 225 | + char trace_flags[2]; |
| 226 | + TraceFlags(span_context.trace_flags()).ToLowerBase16(trace_flags); |
| 227 | + // Note: This is only temporary replacement for appendable string |
| 228 | + std::string hex_string = ""; |
| 229 | + for (int i = 0; i < 32; i++) |
| 230 | + { |
| 231 | + hex_string.push_back(trace_id[i]); |
| 232 | + } |
| 233 | + hex_string.push_back('-'); |
| 234 | + for (int i = 0; i < 16; i++) |
| 235 | + { |
| 236 | + hex_string.push_back(span_id[i]); |
| 237 | + } |
| 238 | + hex_string.push_back('-'); |
| 239 | + hex_string.push_back(trace_flags[1]); |
| 240 | + setter(carrier, kB3CombinedHeader, hex_string); |
| 241 | + } |
| 242 | +}; |
| 243 | + |
| 244 | +template <typename T> |
| 245 | +class B3PropagatorMultiHeader : public B3PropagatorExtractor<T> |
| 246 | +{ |
| 247 | +public: |
| 248 | + // Rules that manages how context will be injected to carrier. |
| 249 | + using Setter = void (*)(T &carrier, |
| 250 | + nostd::string_view trace_type, |
| 251 | + nostd::string_view trace_description); |
| 252 | + void Inject(Setter setter, T &carrier, const context::Context &context) noexcept override |
| 253 | + { |
| 254 | + SpanContext span_context = B3PropagatorExtractor<T>::GetCurrentSpan(context); |
| 255 | + if (!span_context.IsValid()) |
| 256 | + { |
| 257 | + return; |
| 258 | + } |
| 259 | + char trace_id[32]; |
| 260 | + TraceId(span_context.trace_id()).ToLowerBase16(trace_id); |
| 261 | + char span_id[16]; |
| 262 | + SpanId(span_context.span_id()).ToLowerBase16(span_id); |
| 263 | + char trace_flags[2]; |
| 264 | + TraceFlags(span_context.trace_flags()).ToLowerBase16(trace_flags); |
| 265 | + setter(carrier, kB3TraceIdHeader, nostd::string_view(trace_id, sizeof(trace_id))); |
| 266 | + setter(carrier, kB3SpanIdHeader, nostd::string_view(span_id, sizeof(span_id))); |
| 267 | + setter(carrier, kB3SampledHeader, nostd::string_view(trace_flags + 1, 1)); |
| 268 | + } |
| 269 | +}; |
| 270 | + |
| 271 | +} // namespace propagation |
| 272 | +} // namespace trace |
| 273 | +OPENTELEMETRY_END_NAMESPACE |
0 commit comments