Skip to content

Commit 9647240

Browse files
authored
feat: extract/inject datadog tracestate field p (#115)
Add support for W3C datadog tracestate field `p`.
1 parent e0c07e2 commit 9647240

File tree

12 files changed

+414
-193
lines changed

12 files changed

+414
-193
lines changed

examples/http-server/common/tracingutil.h

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,12 @@ class HeaderWriter final : public datadog::tracing::DictWriter {
1616
explicit HeaderWriter(httplib::Headers& headers) : headers_(headers) {}
1717

1818
void set(std::string_view key, std::string_view value) override {
19-
headers_.emplace(key, value);
19+
auto found = headers_.find(std::string(key));
20+
if (found == headers_.cend()) {
21+
headers_.emplace(key, value);
22+
} else {
23+
found->second = value;
24+
}
2025
}
2126
};
2227

examples/http-server/proxy/proxy.cpp

Lines changed: 13 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -49,37 +49,38 @@ int main() {
4949
auto forward_handler = [&tracer, &upstream_client](
5050
const httplib::Request& req,
5151
httplib::Response& res) {
52-
auto span = tracer.create_span();
53-
span.set_name("forward.request");
54-
span.set_resource_name(req.method + " " + req.path);
55-
span.set_tag("network.origin.ip", req.remote_addr);
56-
span.set_tag("network.origin.port", std::to_string(req.remote_port));
57-
span.set_tag("http.url_details.path", req.target);
58-
span.set_tag("http.route", req.path);
59-
span.set_tag("http.method", req.method);
52+
tracingutil::HeaderReader reader(req.headers);
53+
auto span = tracer.extract_or_create_span(reader);
54+
span->set_name("forward.request");
55+
span->set_resource_name(req.method + " " + req.path);
56+
span->set_tag("network.origin.ip", req.remote_addr);
57+
span->set_tag("network.origin.port", std::to_string(req.remote_port));
58+
span->set_tag("http.url_details.path", req.target);
59+
span->set_tag("http.route", req.path);
60+
span->set_tag("http.method", req.method);
6061

6162
httplib::Error er;
6263
httplib::Request forward_request(req);
6364
forward_request.path = req.target;
6465

6566
tracingutil::HeaderWriter writer(forward_request.headers);
66-
span.inject(writer);
67+
span->inject(writer);
6768

6869
upstream_client.send(forward_request, res, er);
6970
if (er != httplib::Error::Success) {
7071
res.status = 500;
71-
span.set_error_message(httplib::to_string(er));
72+
span->set_error_message(httplib::to_string(er));
7273
std::cerr << "Error occurred while proxying request: " << req.target
7374
<< "\n";
7475
} else {
7576
tracingutil::HeaderReader reader(res.headers);
76-
auto status = span.read_sampling_delegation_response(reader);
77+
auto status = span->read_sampling_delegation_response(reader);
7778
if (auto error = status.if_error()) {
7879
std::cerr << error << "\n";
7980
}
8081
}
8182

82-
span.set_tag("http.status_code", std::to_string(res.status));
83+
span->set_tag("http.status_code", std::to_string(res.status));
8384
};
8485

8586
httplib::Server server;

src/datadog/extracted_data.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,10 @@ struct ExtractedData {
2323
bool delegate_sampling_decision = false;
2424
Optional<int> sampling_priority;
2525
// If this `ExtractedData` was created on account of `PropagationStyle::W3C`,
26+
// then `datadog_w3c_parent_id` contains the parts of the "tracestate"
27+
// refering to the latest datadog parent ID.
28+
Optional<std::string> datadog_w3c_parent_id;
29+
// If this `ExtractedData` was created on account of `PropagationStyle::W3C`,
2630
// then `additional_w3c_tracestate` contains the parts of the "tracestate"
2731
// header that are not the "dd" (Datadog) entry. If there are no other parts,
2832
// then `additional_w3c_tracestate` is null.

src/datadog/extraction_util.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -295,6 +295,7 @@ ExtractedData merge(const std::vector<ExtractedData>& contexts) {
295295
});
296296

297297
if (other != contexts.end()) {
298+
result.datadog_w3c_parent_id = other->datadog_w3c_parent_id;
298299
result.additional_w3c_tracestate = other->additional_w3c_tracestate;
299300
result.additional_datadog_w3c_tracestate =
300301
other->additional_datadog_w3c_tracestate;

src/datadog/tags.cpp

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,5 @@
11
#include "tags.h"
22

3-
#include "string_util.h"
4-
53
namespace datadog {
64
namespace tracing {
75
namespace tags {
@@ -32,11 +30,10 @@ const std::string process_id = "process_id";
3230
const std::string language = "language";
3331
const std::string runtime_id = "runtime-id";
3432
const std::string sampling_decider = "_dd.is_sampling_decider";
33+
const std::string w3c_parent_id = "_dd.parent_id";
3534

3635
} // namespace internal
3736

38-
bool is_internal(StringView tag_name) { return starts_with(tag_name, "_dd."); }
39-
4037
} // namespace tags
4138
} // namespace tracing
4239
} // namespace datadog

src/datadog/tags.h

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55

66
#include <string>
77

8+
#include "string_util.h"
89
#include "string_view.h"
910

1011
namespace datadog {
@@ -36,11 +37,14 @@ extern const std::string process_id;
3637
extern const std::string language;
3738
extern const std::string runtime_id;
3839
extern const std::string sampling_decider;
40+
extern const std::string w3c_parent_id;
3941
} // namespace internal
4042

4143
// Return whether the specified `tag_name` is reserved for use internal to this
4244
// library.
43-
bool is_internal(StringView tag_name);
45+
inline bool is_internal(StringView tag_name) {
46+
return starts_with(tag_name, "_dd.");
47+
}
4448

4549
} // namespace tags
4650
} // namespace tracing

src/datadog/trace_segment.cpp

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -418,10 +418,11 @@ bool TraceSegment::inject(DictWriter& writer, const SpanData& span,
418418
writer.set(
419419
"traceparent",
420420
encode_traceparent(span.trace_id, span.span_id, sampling_priority));
421-
writer.set("tracestate",
422-
encode_tracestate(sampling_priority, origin_, trace_tags,
423-
additional_datadog_w3c_tracestate_,
424-
additional_w3c_tracestate_));
421+
writer.set(
422+
"tracestate",
423+
encode_tracestate(span.span_id, sampling_priority, origin_,
424+
trace_tags, additional_datadog_w3c_tracestate_,
425+
additional_w3c_tracestate_));
425426
break;
426427
default:
427428
assert(style == PropagationStyle::NONE);

src/datadog/tracer.cpp

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -175,7 +175,7 @@ Expected<Span> Tracer::extract_span(const DictReader& reader,
175175
}
176176

177177
auto [trace_id, parent_id, origin, trace_tags, delegate_sampling_decision,
178-
sampling_priority, additional_w3c_tracestate,
178+
sampling_priority, datadog_w3c_parent_id, additional_w3c_tracestate,
179179
additional_datadog_w3c_tracestate, style, headers_examined] =
180180
merge(extracted_contexts);
181181

@@ -277,6 +277,10 @@ Expected<Span> Tracer::extract_span(const DictReader& reader,
277277
}
278278
}
279279

280+
if (datadog_w3c_parent_id) {
281+
span_data->tags[tags::internal::w3c_parent_id] = *datadog_w3c_parent_id;
282+
}
283+
280284
Optional<SamplingDecision> sampling_decision;
281285
if (sampling_priority) {
282286
SamplingDecision decision;

src/datadog/w3c_propagation.cpp

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -221,6 +221,14 @@ void parse_datadog_tracestate(ExtractedData& result, StringView datadog_value) {
221221
(*result.sampling_priority > 0) == (priority > 0)) {
222222
result.sampling_priority = priority;
223223
}
224+
} else if (key == "p") {
225+
if (value.size() != 16) {
226+
// chaff!
227+
pair_begin = pair_end == end ? end : pair_end + 1;
228+
continue;
229+
}
230+
231+
result.datadog_w3c_parent_id = std::string(value);
224232
} else if (starts_with(key, "t.")) {
225233
// The part of the key that follows "t." is the name of a trace tag,
226234
// except without the "_dd.p." prefix.
@@ -300,6 +308,7 @@ Expected<ExtractedData> extract_w3c(
300308
return result;
301309
}
302310

311+
result.datadog_w3c_parent_id = "0000000000000000";
303312
extract_tracestate(result, headers);
304313

305314
return result;
@@ -326,11 +335,14 @@ std::string encode_traceparent(TraceID trace_id, std::uint64_t span_id,
326335
}
327336

328337
std::string encode_datadog_tracestate(
329-
int sampling_priority, const Optional<std::string>& origin,
338+
uint64_t span_id, int sampling_priority,
339+
const Optional<std::string>& origin,
330340
const std::vector<std::pair<std::string, std::string>>& trace_tags,
331341
const Optional<std::string>& additional_datadog_w3c_tracestate) {
332342
std::string result = "dd=s:";
333343
result += std::to_string(sampling_priority);
344+
result += ";p:";
345+
result += hex_padded(span_id);
334346

335347
if (origin) {
336348
result += ";o:";
@@ -382,12 +394,14 @@ std::string encode_datadog_tracestate(
382394
}
383395

384396
std::string encode_tracestate(
385-
int sampling_priority, const Optional<std::string>& origin,
397+
uint64_t span_id, int sampling_priority,
398+
const Optional<std::string>& origin,
386399
const std::vector<std::pair<std::string, std::string>>& trace_tags,
387400
const Optional<std::string>& additional_datadog_w3c_tracestate,
388401
const Optional<std::string>& additional_w3c_tracestate) {
389-
std::string result = encode_datadog_tracestate(
390-
sampling_priority, origin, trace_tags, additional_datadog_w3c_tracestate);
402+
std::string result =
403+
encode_datadog_tracestate(span_id, sampling_priority, origin, trace_tags,
404+
additional_datadog_w3c_tracestate);
391405

392406
if (additional_w3c_tracestate) {
393407
result += ',';

src/datadog/w3c_propagation.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,8 @@ std::string encode_traceparent(TraceID trace_id, std::uint64_t span_id,
3737

3838
// Return a value for the "tracestate" header containing the specified fields.
3939
std::string encode_tracestate(
40-
int sampling_priority, const Optional<std::string>& origin,
40+
uint64_t span_id, int sampling_priority,
41+
const Optional<std::string>& origin,
4142
const std::vector<std::pair<std::string, std::string>>& trace_tags,
4243
const Optional<std::string>& additional_datadog_w3c_tracestate,
4344
const Optional<std::string>& additional_w3c_tracestate);

test/test_span.cpp

Lines changed: 17 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -660,6 +660,7 @@ TEST_CASE("injecting W3C tracestate header") {
660660
// - sampling priority
661661
// - origin
662662
// - trace tags
663+
// - parent id
663664
// - extra fields (extracted from W3C)
664665
// - all of the above
665666
// - character substitutions:
@@ -704,79 +705,79 @@ TEST_CASE("injecting W3C tracestate header") {
704705
{__LINE__, "sampling priority",
705706
{{"x-datadog-trace-id", "1"}, {"x-datadog-parent-id", "1"},
706707
{"x-datadog-sampling-priority", "2"}},
707-
"dd=s:2"},
708+
"dd=s:2;p:$parent_id"},
708709

709710
{__LINE__, "origin",
710711
{{"x-datadog-trace-id", "1"}, {"x-datadog-parent-id", "1"},
711712
{"x-datadog-origin", "France"}},
712713
// The "s:-1" comes from the 0% sample rate.
713-
"dd=s:-1;o:France"},
714+
"dd=s:-1;p:$parent_id;o:France"},
714715

715716
{__LINE__, "trace tags",
716717
{{"x-datadog-trace-id", "1"}, {"x-datadog-parent-id", "1"},
717718
{"x-datadog-tags", "_dd.p.foo=x,_dd.p.bar=y,ignored=wrong_prefix"}},
718719
// The "s:-1" comes from the 0% sample rate.
719-
"dd=s:-1;t.foo:x;t.bar:y"},
720+
"dd=s:-1;p:$parent_id;t.foo:x;t.bar:y"},
720721

721722
{__LINE__, "extra fields",
722723
{{"traceparent", traceparent_drop}, {"tracestate", "dd=foo:bar;boing:boing"}},
723724
// The "s:0" comes from the sampling decision in `traceparent_drop`.
724-
"dd=s:0;foo:bar;boing:boing"},
725+
"dd=s:0;p:$parent_id;foo:bar;boing:boing"},
725726

726727
{__LINE__, "all of the above",
727728
{{"traceparent", traceparent_drop},
728729
{"tracestate", "dd=o:France;t.foo:x;t.bar:y;foo:bar;boing:boing"}},
729730
// The "s:0" comes from the sampling decision in `traceparent_drop`.
730-
"dd=s:0;o:France;t.foo:x;t.bar:y;foo:bar;boing:boing"},
731+
"dd=s:0;p:$parent_id;o:France;t.foo:x;t.bar:y;foo:bar;boing:boing"},
731732

732733
{__LINE__, "replace invalid characters in origin",
733734
{{"x-datadog-trace-id", "1"}, {"x-datadog-parent-id", "1"},
734735
{"x-datadog-origin", "France, is a country=nation; so is 台北."}},
735736
// The "s:-1" comes from the 0% sample rate.
736-
"dd=s:-1;o:France_ is a country~nation_ so is ______."},
737+
"dd=s:-1;p:$parent_id;o:France_ is a country~nation_ so is ______."},
737738

738739
{__LINE__, "replace invalid characters in trace tag key",
739740
{{"x-datadog-trace-id", "1"}, {"x-datadog-parent-id", "1"},
740741
{"x-datadog-tags", "_dd.p.a;d台北x =foo,_dd.p.ok=bar"}},
741742
// The "s:-1" comes from the 0% sample rate.
742-
"dd=s:-1;t.a_d______x_:foo;t.ok:bar"},
743+
"dd=s:-1;p:$parent_id;t.a_d______x_:foo;t.ok:bar"},
743744

744745
{__LINE__, "replace invalid characters in trace tag value",
745746
{{"x-datadog-trace-id", "1"}, {"x-datadog-parent-id", "1"},
746747
{"x-datadog-tags", "_dd.p.wacky=hello fr~d; how are คุณ?"}},
747748
// The "s:-1" comes from the 0% sample rate.
748-
"dd=s:-1;t.wacky:hello fr_d_ how are _________?"},
749+
"dd=s:-1;p:$parent_id;t.wacky:hello fr_d_ how are _________?"},
749750

750751
{__LINE__, "replace equal signs with tildes in trace tag value",
751752
{{"x-datadog-trace-id", "1"}, {"x-datadog-parent-id", "1"},
752753
{"x-datadog-tags", "_dd.p.base64_thingy=d2Fra2EhIHdhaw=="}},
753754
// The "s:-1" comes from the 0% sample rate.
754-
"dd=s:-1;t.base64_thingy:d2Fra2EhIHdhaw~~"},
755+
"dd=s:-1;p:$parent_id;t.base64_thingy:d2Fra2EhIHdhaw~~"},
755756

756757
{__LINE__, "oversized origin truncates it and subsequent fields",
757758
{{"x-datadog-trace-id", "1"}, {"x-datadog-parent-id", "1"},
758759
{"x-datadog-origin", "long cat is looooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooong"},
759760
{"x-datadog-tags", "_dd.p.foo=bar,_dd.p.honk=honk"}},
760761
// The "s:-1" comes from the 0% sample rate.
761-
"dd=s:-1"},
762+
"dd=s:-1;p:$parent_id"},
762763

763764
{__LINE__, "oversized trace tag truncates it and subsequent fields",
764765
{{"x-datadog-trace-id", "1"}, {"x-datadog-parent-id", "1"},
765766
{"x-datadog-tags", "_dd.p.foo=bar,_dd.p.long_cat_is=looooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooong,_dd.p.lost=forever"}},
766767
// The "s:-1" comes from the 0% sample rate.
767-
"dd=s:-1;t.foo:bar"},
768+
"dd=s:-1;p:$parent_id;t.foo:bar"},
768769

769770
{__LINE__, "oversized extra field truncates itself and subsequent fields",
770771
{{"traceparent", traceparent_drop},
771772
{"tracestate", "dd=foo:bar;long_cat_is:looooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooong;lost:forever"}},
772773
// The "s:0" comes from the sampling decision in `traceparent_drop`.
773-
"dd=s:0;foo:bar"},
774+
"dd=s:0;p:$parent_id;foo:bar"},
774775

775776
{__LINE__, "non-Datadog tracestate",
776777
{{"traceparent", traceparent_drop},
777778
{"tracestate", "foo=bar,boing=boing"}},
778779
// The "s:0" comes from the sampling decision in `traceparent_drop`.
779-
"dd=s:0,foo=bar,boing=boing"},
780+
"dd=s:0;p:$parent_id,foo=bar,boing=boing"},
780781
}));
781782
// clang-format on
782783

@@ -797,6 +798,9 @@ TEST_CASE("injecting W3C tracestate header") {
797798
const auto found = writer.items.find("tracestate");
798799
REQUIRE(found != writer.items.end());
799800

801+
test_case.expected_tracestate.replace(
802+
test_case.expected_tracestate.find("$parent_id"),
803+
sizeof("$parent_id") - 1, hex_padded(span->id()));
800804
REQUIRE(found->second == test_case.expected_tracestate);
801805

802806
REQUIRE(logger->error_count() == 0);

0 commit comments

Comments
 (0)