Skip to content

Commit eccafd5

Browse files
authored
Adding B3 propagator (#523)
1 parent d94a368 commit eccafd5

File tree

5 files changed

+524
-1
lines changed

5 files changed

+524
-1
lines changed

api/include/opentelemetry/nostd/string_view.h

+14
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,20 @@ class string_view
120120
return substr(pos1, count1).compare(string_view(s, count2));
121121
};
122122

123+
size_type find(char ch, size_type pos = 0) const noexcept
124+
{
125+
size_type res = npos;
126+
if (pos < length())
127+
{
128+
auto found = Traits::find(data() + pos, length() - pos, ch);
129+
if (found)
130+
{
131+
res = found - data();
132+
}
133+
}
134+
return res;
135+
}
136+
123137
bool operator<(const string_view v) const noexcept { return compare(v) < 0; }
124138

125139
bool operator>(const string_view v) const noexcept { return compare(v) > 0; }
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,273 @@
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

api/test/nostd/string_view_test.cc

+18
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,24 @@ TEST(StringViewTest, SubstrOutOfRange)
7575
#endif
7676
}
7777

78+
TEST(StringViewTest, FindSingleCharacter)
79+
{
80+
string_view s = "abc";
81+
82+
// starting from 0-th position (default)
83+
EXPECT_EQ(s.find('a'), 0);
84+
EXPECT_EQ(s.find('b'), 1);
85+
EXPECT_EQ(s.find('c'), 2);
86+
EXPECT_EQ(s.find('d'), -1); // FIXME: string_view:npos - problem with linker
87+
88+
// starting from given index
89+
EXPECT_EQ(s.find('a', 1), -1);
90+
EXPECT_EQ(s.find('b', 1), 1);
91+
92+
// out of index
93+
EXPECT_EQ(s.find('a', 10), -1);
94+
}
95+
7896
TEST(StringViewTest, Compare)
7997
{
8098
string_view s1 = "aaa";

api/test/trace/propagation/CMakeLists.txt

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
foreach(testname http_text_format_test)
1+
foreach(testname http_text_format_test b3_propagation_test)
22
add_executable(${testname} "${testname}.cc")
33
target_link_libraries(
44
${testname} ${GTEST_BOTH_LIBRARIES} ${CORE_RUNTIME_LIBS}

0 commit comments

Comments
 (0)