Skip to content

Commit 0253eba

Browse files
authored
Add support for x-cloud-trace-context (#248)
* Add support for x-cloud-trace-context * needs debugging * Fixed one test, just need to implement the parsing and serialising of the x-cloud-trace-context header * Now supports long format trace_id/span_id;o=[0-1] - TODO support the trace_id & trace_id/span_id formats * Support medium and short form cloud-trace headers (TODO verify what we should do when no span ID is provided) * Revert CMakeLists.txt change * Revert CMakeLists.txt * Expect span ID to be 0 when none is given (need to confirm this is the correct behaviour) * Add cloud_trace propagator to CMakeLists.txt * span ID is an unsigned long, not a hex value * Fix the max length of the x-cloud-trace-context header * incorrect char array assignment * set the header length properly * Revert CMakeLists.txt * fix style error * fix linting issues * unused import * Add cloud_trace_propagator.cpp to build list * Fix portability error * use string_view and nullptr * Fix span_id short parsing * Simplify serialize method with snprintf * cover error handling with additional test cases * Test potential maximum ID values for propagators * Cover the bad trace flag case * Was adding a space at the end of the header * Extend header length (it looks like it does need it for the possible null terminator) * Review comments * Fix clang-tidy error * Review comments * Fix failing test
1 parent a5705f2 commit 0253eba

File tree

10 files changed

+457
-1
lines changed

10 files changed

+457
-1
lines changed

CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -230,6 +230,7 @@ set(LIGHTSTEP_SRCS src/common/utility.cpp
230230
src/tracer/propagation/trace_context_propagator.cpp
231231
src/tracer/propagation/lightstep_propagator.cpp
232232
src/tracer/propagation/multiheader_propagator.cpp
233+
src/tracer/propagation/cloud_trace_propagator.cpp
233234
src/tracer/propagation/propagation.cpp
234235
src/tracer/propagation/propagation_options.cpp
235236
src/tracer/immutable_span_context.cpp

include/lightstep/tracer.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,8 @@ enum class PropagationMode {
2626
lightstep = 1,
2727
b3 = 2,
2828
envoy = 3,
29-
trace_context = 4
29+
trace_context = 4,
30+
cloud_trace = 5
3031
};
3132

3233
// DynamicConfigurationValue is used for configuration values that can

src/tracer/json_options.cpp

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,9 @@ static PropagationMode GetPropagationMode(opentracing::string_view s) {
4747
if (s == "trace_context") {
4848
return PropagationMode::trace_context;
4949
}
50+
if (s == "cloud_trace") {
51+
return PropagationMode::cloud_trace;
52+
}
5053
std::ostringstream oss;
5154
oss << "invalid propagation mode " << s;
5255
throw std::runtime_error{oss.str()};

src/tracer/propagation/BUILD

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -167,6 +167,7 @@ lightstep_cc_library(
167167
":envoy_propagator_lib",
168168
":trace_context_propagator_lib",
169169
":baggage_propagator_lib",
170+
":cloud_trace_propagator_lib",
170171
],
171172
)
172173

@@ -184,3 +185,18 @@ lightstep_cc_library(
184185
":trace_context_lib",
185186
],
186187
)
188+
189+
lightstep_cc_library(
190+
name = "cloud_trace_propagator_lib",
191+
private_hdrs = [
192+
"cloud_trace_propagator.h",
193+
],
194+
srcs = [
195+
"cloud_trace_propagator.cpp",
196+
],
197+
deps = [
198+
"//3rd_party/base64:base64_lib",
199+
":binary_propagation_lib",
200+
":utility_lib",
201+
],
202+
)
Lines changed: 191 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,191 @@
1+
#include "tracer/propagation/cloud_trace_propagator.h"
2+
3+
#include "common/hex_conversion.h"
4+
5+
#include "tracer/propagation/binary_propagation.h"
6+
#include "tracer/propagation/utility.h"
7+
8+
const opentracing::string_view PropagationSingleKey = "x-cloud-trace-context";
9+
const opentracing::string_view PrefixBaggage = "ot-baggage-";
10+
11+
namespace lightstep {
12+
//--------------------------------------------------------------------------------------------------
13+
// InjectSpanContext
14+
//--------------------------------------------------------------------------------------------------
15+
opentracing::expected<void> CloudTracePropagator::InjectSpanContext(
16+
const opentracing::TextMapWriter& carrier,
17+
const TraceContext& trace_context, opentracing::string_view /*trace_state*/,
18+
const BaggageProtobufMap& /*baggage*/) const {
19+
return this->InjectSpanContextImpl(carrier, trace_context);
20+
}
21+
22+
opentracing::expected<void> CloudTracePropagator::InjectSpanContext(
23+
const opentracing::TextMapWriter& carrier,
24+
const TraceContext& trace_context, opentracing::string_view /*trace_state*/,
25+
const BaggageFlatMap& /*baggage*/) const {
26+
return this->InjectSpanContextImpl(carrier, trace_context);
27+
}
28+
//--------------------------------------------------------------------------------------------------
29+
// ExtractSpanContext
30+
//--------------------------------------------------------------------------------------------------
31+
opentracing::expected<bool> CloudTracePropagator::ExtractSpanContext(
32+
const opentracing::TextMapReader& carrier, bool case_sensitive,
33+
TraceContext& trace_context, std::string& /*trace_state*/,
34+
BaggageProtobufMap& baggage) const {
35+
auto iequals =
36+
[](opentracing::string_view lhs, opentracing::string_view rhs) noexcept {
37+
return lhs.length() == rhs.length() &&
38+
std::equal(std::begin(lhs), std::end(lhs), std::begin(rhs),
39+
[](char a, char b) {
40+
return std::tolower(a) == std::tolower(b);
41+
});
42+
};
43+
opentracing::expected<bool> result;
44+
if (case_sensitive) {
45+
result = this->ExtractSpanContextImpl(carrier, trace_context,
46+
std::equal_to<opentracing::string_view>{}, baggage);
47+
} else {
48+
result = this->ExtractSpanContextImpl(carrier, trace_context, iequals, baggage);
49+
}
50+
if (!result || !*result) {
51+
return result;
52+
}
53+
54+
return result;
55+
}
56+
57+
//--------------------------------------------------------------------------------------------------
58+
// InjectSpanContextImpl
59+
//--------------------------------------------------------------------------------------------------
60+
opentracing::expected<void> CloudTracePropagator::InjectSpanContextImpl(
61+
const opentracing::TextMapWriter& carrier,
62+
const TraceContext& trace_context) const {
63+
std::array<char, CloudContextLength> buffer;
64+
auto data_length = this->SerializeCloudTrace(trace_context, buffer.data());
65+
return carrier.Set(PropagationSingleKey,
66+
opentracing::string_view{buffer.data(), data_length});
67+
}
68+
69+
//--------------------------------------------------------------------------------------------------
70+
// ExtractSpanContextImpl
71+
//--------------------------------------------------------------------------------------------------
72+
template <class KeyCompare>
73+
opentracing::expected<bool> CloudTracePropagator::ExtractSpanContextImpl(
74+
const opentracing::TextMapReader& carrier, TraceContext& trace_context, const KeyCompare& key_compare,
75+
BaggageProtobufMap& baggage) const {
76+
bool parent_header_found = false;
77+
auto result =
78+
carrier.ForeachKey([&](opentracing::string_view key,
79+
opentracing::string_view
80+
value) noexcept->opentracing::expected<void> {
81+
if (key_compare(key, PropagationSingleKey)) {
82+
auto was_successful = this->ParseCloudTrace(value, trace_context);
83+
if (!was_successful) {
84+
return opentracing::make_unexpected(was_successful.error());
85+
}
86+
parent_header_found = true;
87+
} else if (key.length() > PrefixBaggage.size() &&
88+
key_compare(opentracing::string_view{key.data(),
89+
PrefixBaggage.size()},
90+
PrefixBaggage)) {
91+
baggage.insert(BaggageProtobufMap::value_type(
92+
ToLower(
93+
opentracing::string_view{key.data() + PrefixBaggage.size(),
94+
key.size() - PrefixBaggage.size()}),
95+
value));
96+
}
97+
return {};
98+
});
99+
if (!result) {
100+
return opentracing::make_unexpected(result.error());
101+
}
102+
return parent_header_found;
103+
}
104+
105+
opentracing::expected<void> CloudTracePropagator::ParseCloudTrace(
106+
opentracing::string_view s, TraceContext& trace_context) const noexcept {
107+
if (s.size() < Num128BitHexDigits) {
108+
return opentracing::make_unexpected(
109+
std::make_error_code(std::errc::invalid_argument));
110+
}
111+
size_t offset = 0;
112+
113+
// default sampled to on (this comes from the ;o=1 part of
114+
// x-cloud-trace-context; if it is not set we will default to sampling
115+
// this request)
116+
trace_context.trace_flags = SetTraceFlag<SampledFlagMask>(trace_context.trace_flags, true);
117+
118+
// trace-id
119+
auto error_maybe = NormalizedHexToUint128(
120+
opentracing::string_view{s.data() + offset, Num128BitHexDigits},
121+
trace_context.trace_id_high, trace_context.trace_id_low);
122+
if (!error_maybe) {
123+
return error_maybe;
124+
}
125+
126+
offset += Num128BitHexDigits;
127+
if (s.size() - offset < 2) {
128+
// only a short form trace ID has been given (not a "trace id/span id")
129+
return {};
130+
}
131+
132+
if (s[offset] != '/') {
133+
return opentracing::make_unexpected(
134+
std::make_error_code(std::errc::invalid_argument));
135+
}
136+
++offset;
137+
138+
std::array<char, Num64BitDecimalDigits + 1> parent_id;
139+
size_t i;
140+
for (i=0; i < Num64BitDecimalDigits; ++i) {
141+
if (offset == s.length() || std::isdigit(s[offset]) == 0) {
142+
break;
143+
}
144+
parent_id[i] = s[offset];
145+
++offset;
146+
}
147+
parent_id[i] = '\0';
148+
149+
// parent-id
150+
errno = 0;
151+
trace_context.parent_id = std::strtoull(parent_id.data(), nullptr, 10);
152+
if (errno == ERANGE) {
153+
return opentracing::make_unexpected(
154+
std::make_error_code(std::errc::result_out_of_range));
155+
}
156+
157+
if (s.size() - offset < 4) {
158+
// only a "trace ID/span ID" has been given (not a "trace id/span id;o=[0-1]")
159+
return {};
160+
}
161+
162+
if(opentracing::string_view(s.begin() + offset, 3) != opentracing::string_view(";o=")) {
163+
return opentracing::make_unexpected(
164+
std::make_error_code(std::errc::invalid_argument));
165+
}
166+
167+
offset += 3;
168+
169+
// trace-flags
170+
if (s[offset] == '0') {
171+
// don't sample
172+
trace_context.trace_flags = SetTraceFlag<SampledFlagMask>(trace_context.trace_flags, false);
173+
}
174+
175+
return {};
176+
}
177+
178+
size_t CloudTracePropagator::SerializeCloudTrace(const TraceContext& trace_context,
179+
char* s) const noexcept {
180+
size_t offset = 0;
181+
// trace-id
182+
Uint64ToHex(trace_context.trace_id_high, s);
183+
offset += Num64BitHexDigits;
184+
Uint64ToHex(trace_context.trace_id_low, s + offset);
185+
offset += Num64BitHexDigits;
186+
187+
offset += snprintf(s + offset, CloudContextLength - offset, "/%lu;o=%d", trace_context.parent_id, IsTraceFlagSet<SampledFlagMask>(trace_context.trace_flags) ? 1 : 0);
188+
189+
return offset;
190+
}
191+
} // namespace lightstep
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
#pragma once
2+
3+
#include "tracer/propagation/propagator.h"
4+
5+
namespace lightstep {
6+
7+
const size_t CloudContextLength = 58; // max x-cloud-trace-context header
8+
9+
const size_t Num64BitDecimalDigits = 20;
10+
11+
class CloudTracePropagator final : public Propagator {
12+
public:
13+
// Propagator
14+
opentracing::expected<void> InjectSpanContext(
15+
const opentracing::TextMapWriter& carrier,
16+
const TraceContext& trace_context, opentracing::string_view trace_state,
17+
const BaggageProtobufMap& baggage) const override;
18+
19+
opentracing::expected<void> InjectSpanContext(
20+
const opentracing::TextMapWriter& carrier,
21+
const TraceContext& trace_context, opentracing::string_view trace_state,
22+
const BaggageFlatMap& baggage) const override;
23+
24+
opentracing::expected<bool> ExtractSpanContext(
25+
const opentracing::TextMapReader& carrier, bool case_sensitive,
26+
TraceContext& trace_context, std::string& trace_state,
27+
BaggageProtobufMap& baggage) const override;
28+
29+
private:
30+
opentracing::expected<void> InjectSpanContextImpl(
31+
const opentracing::TextMapWriter& carrier,
32+
const TraceContext& trace_context) const;
33+
34+
template <class KeyCompare>
35+
opentracing::expected<bool> ExtractSpanContextImpl(
36+
const opentracing::TextMapReader& carrier, TraceContext& trace_context,
37+
const KeyCompare& key_compare, BaggageProtobufMap& baggage) const;
38+
39+
opentracing::expected<void> ParseCloudTrace(
40+
opentracing::string_view s, lightstep::TraceContext& trace_context) const noexcept;
41+
42+
size_t SerializeCloudTrace(const TraceContext& trace_context,
43+
char* s) const noexcept;
44+
};
45+
} // namespace lightstep

src/tracer/propagation/propagation_options.cpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
#include "tracer/propagation/envoy_propagator.h"
88
#include "tracer/propagation/lightstep_propagator.h"
99
#include "tracer/propagation/trace_context_propagator.h"
10+
#include "tracer/propagation/cloud_trace_propagator.h"
1011

1112
namespace lightstep {
1213
//--------------------------------------------------------------------------------------------------
@@ -76,6 +77,9 @@ static std::vector<std::unique_ptr<Propagator>> MakePropagators(
7677
case PropagationMode::trace_context:
7778
result.emplace_back(new TraceContextPropagator{});
7879
break;
80+
case PropagationMode::cloud_trace:
81+
result.emplace_back(new CloudTracePropagator{});
82+
break;
7983
}
8084
}
8185
return result;

test/tracer/propagation/BUILD

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -141,3 +141,17 @@ lightstep_catch_test(
141141
"//src/tracer/propagation:propagation_lib",
142142
],
143143
)
144+
145+
lightstep_catch_test(
146+
name = "cloud_trace_propagation_test",
147+
srcs = [
148+
"cloud_trace_propagation_test.cpp",
149+
],
150+
deps = [
151+
"//:manual_tracer_lib",
152+
"//test/recorder:in_memory_recorder_lib",
153+
":text_map_carrier_lib",
154+
":http_headers_carrier_lib",
155+
":utility_lib",
156+
],
157+
)

0 commit comments

Comments
 (0)