Skip to content

Commit

Permalink
feat: extract/inject datadog tracestate field p (#115)
Browse files Browse the repository at this point in the history
Add support for W3C datadog tracestate field `p`.
  • Loading branch information
dmehala committed May 15, 2024
1 parent e0c07e2 commit 9647240
Show file tree
Hide file tree
Showing 12 changed files with 414 additions and 193 deletions.
7 changes: 6 additions & 1 deletion examples/http-server/common/tracingutil.h
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,12 @@ class HeaderWriter final : public datadog::tracing::DictWriter {
explicit HeaderWriter(httplib::Headers& headers) : headers_(headers) {}

void set(std::string_view key, std::string_view value) override {
headers_.emplace(key, value);
auto found = headers_.find(std::string(key));
if (found == headers_.cend()) {
headers_.emplace(key, value);
} else {
found->second = value;
}
}
};

Expand Down
25 changes: 13 additions & 12 deletions examples/http-server/proxy/proxy.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -49,37 +49,38 @@ int main() {
auto forward_handler = [&tracer, &upstream_client](
const httplib::Request& req,
httplib::Response& res) {
auto span = tracer.create_span();
span.set_name("forward.request");
span.set_resource_name(req.method + " " + req.path);
span.set_tag("network.origin.ip", req.remote_addr);
span.set_tag("network.origin.port", std::to_string(req.remote_port));
span.set_tag("http.url_details.path", req.target);
span.set_tag("http.route", req.path);
span.set_tag("http.method", req.method);
tracingutil::HeaderReader reader(req.headers);
auto span = tracer.extract_or_create_span(reader);
span->set_name("forward.request");
span->set_resource_name(req.method + " " + req.path);
span->set_tag("network.origin.ip", req.remote_addr);
span->set_tag("network.origin.port", std::to_string(req.remote_port));
span->set_tag("http.url_details.path", req.target);
span->set_tag("http.route", req.path);
span->set_tag("http.method", req.method);

httplib::Error er;
httplib::Request forward_request(req);
forward_request.path = req.target;

tracingutil::HeaderWriter writer(forward_request.headers);
span.inject(writer);
span->inject(writer);

upstream_client.send(forward_request, res, er);
if (er != httplib::Error::Success) {
res.status = 500;
span.set_error_message(httplib::to_string(er));
span->set_error_message(httplib::to_string(er));
std::cerr << "Error occurred while proxying request: " << req.target
<< "\n";
} else {
tracingutil::HeaderReader reader(res.headers);
auto status = span.read_sampling_delegation_response(reader);
auto status = span->read_sampling_delegation_response(reader);
if (auto error = status.if_error()) {
std::cerr << error << "\n";
}
}

span.set_tag("http.status_code", std::to_string(res.status));
span->set_tag("http.status_code", std::to_string(res.status));
};

httplib::Server server;
Expand Down
4 changes: 4 additions & 0 deletions src/datadog/extracted_data.h
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,10 @@ struct ExtractedData {
bool delegate_sampling_decision = false;
Optional<int> sampling_priority;
// If this `ExtractedData` was created on account of `PropagationStyle::W3C`,
// then `datadog_w3c_parent_id` contains the parts of the "tracestate"
// refering to the latest datadog parent ID.
Optional<std::string> datadog_w3c_parent_id;
// If this `ExtractedData` was created on account of `PropagationStyle::W3C`,
// then `additional_w3c_tracestate` contains the parts of the "tracestate"
// header that are not the "dd" (Datadog) entry. If there are no other parts,
// then `additional_w3c_tracestate` is null.
Expand Down
1 change: 1 addition & 0 deletions src/datadog/extraction_util.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -295,6 +295,7 @@ ExtractedData merge(const std::vector<ExtractedData>& contexts) {
});

if (other != contexts.end()) {
result.datadog_w3c_parent_id = other->datadog_w3c_parent_id;
result.additional_w3c_tracestate = other->additional_w3c_tracestate;
result.additional_datadog_w3c_tracestate =
other->additional_datadog_w3c_tracestate;
Expand Down
5 changes: 1 addition & 4 deletions src/datadog/tags.cpp
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
#include "tags.h"

#include "string_util.h"

namespace datadog {
namespace tracing {
namespace tags {
Expand Down Expand Up @@ -32,11 +30,10 @@ const std::string process_id = "process_id";
const std::string language = "language";
const std::string runtime_id = "runtime-id";
const std::string sampling_decider = "_dd.is_sampling_decider";
const std::string w3c_parent_id = "_dd.parent_id";

} // namespace internal

bool is_internal(StringView tag_name) { return starts_with(tag_name, "_dd."); }

} // namespace tags
} // namespace tracing
} // namespace datadog
6 changes: 5 additions & 1 deletion src/datadog/tags.h
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

#include <string>

#include "string_util.h"
#include "string_view.h"

namespace datadog {
Expand Down Expand Up @@ -36,11 +37,14 @@ extern const std::string process_id;
extern const std::string language;
extern const std::string runtime_id;
extern const std::string sampling_decider;
extern const std::string w3c_parent_id;
} // namespace internal

// Return whether the specified `tag_name` is reserved for use internal to this
// library.
bool is_internal(StringView tag_name);
inline bool is_internal(StringView tag_name) {
return starts_with(tag_name, "_dd.");
}

} // namespace tags
} // namespace tracing
Expand Down
9 changes: 5 additions & 4 deletions src/datadog/trace_segment.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -418,10 +418,11 @@ bool TraceSegment::inject(DictWriter& writer, const SpanData& span,
writer.set(
"traceparent",
encode_traceparent(span.trace_id, span.span_id, sampling_priority));
writer.set("tracestate",
encode_tracestate(sampling_priority, origin_, trace_tags,
additional_datadog_w3c_tracestate_,
additional_w3c_tracestate_));
writer.set(
"tracestate",
encode_tracestate(span.span_id, sampling_priority, origin_,
trace_tags, additional_datadog_w3c_tracestate_,
additional_w3c_tracestate_));
break;
default:
assert(style == PropagationStyle::NONE);
Expand Down
6 changes: 5 additions & 1 deletion src/datadog/tracer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -175,7 +175,7 @@ Expected<Span> Tracer::extract_span(const DictReader& reader,
}

auto [trace_id, parent_id, origin, trace_tags, delegate_sampling_decision,
sampling_priority, additional_w3c_tracestate,
sampling_priority, datadog_w3c_parent_id, additional_w3c_tracestate,
additional_datadog_w3c_tracestate, style, headers_examined] =
merge(extracted_contexts);

Expand Down Expand Up @@ -277,6 +277,10 @@ Expected<Span> Tracer::extract_span(const DictReader& reader,
}
}

if (datadog_w3c_parent_id) {
span_data->tags[tags::internal::w3c_parent_id] = *datadog_w3c_parent_id;
}

Optional<SamplingDecision> sampling_decision;
if (sampling_priority) {
SamplingDecision decision;
Expand Down
22 changes: 18 additions & 4 deletions src/datadog/w3c_propagation.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -221,6 +221,14 @@ void parse_datadog_tracestate(ExtractedData& result, StringView datadog_value) {
(*result.sampling_priority > 0) == (priority > 0)) {
result.sampling_priority = priority;
}
} else if (key == "p") {
if (value.size() != 16) {
// chaff!
pair_begin = pair_end == end ? end : pair_end + 1;
continue;
}

result.datadog_w3c_parent_id = std::string(value);
} else if (starts_with(key, "t.")) {
// The part of the key that follows "t." is the name of a trace tag,
// except without the "_dd.p." prefix.
Expand Down Expand Up @@ -300,6 +308,7 @@ Expected<ExtractedData> extract_w3c(
return result;
}

result.datadog_w3c_parent_id = "0000000000000000";
extract_tracestate(result, headers);

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

std::string encode_datadog_tracestate(
int sampling_priority, const Optional<std::string>& origin,
uint64_t span_id, int sampling_priority,
const Optional<std::string>& origin,
const std::vector<std::pair<std::string, std::string>>& trace_tags,
const Optional<std::string>& additional_datadog_w3c_tracestate) {
std::string result = "dd=s:";
result += std::to_string(sampling_priority);
result += ";p:";
result += hex_padded(span_id);

if (origin) {
result += ";o:";
Expand Down Expand Up @@ -382,12 +394,14 @@ std::string encode_datadog_tracestate(
}

std::string encode_tracestate(
int sampling_priority, const Optional<std::string>& origin,
uint64_t span_id, int sampling_priority,
const Optional<std::string>& origin,
const std::vector<std::pair<std::string, std::string>>& trace_tags,
const Optional<std::string>& additional_datadog_w3c_tracestate,
const Optional<std::string>& additional_w3c_tracestate) {
std::string result = encode_datadog_tracestate(
sampling_priority, origin, trace_tags, additional_datadog_w3c_tracestate);
std::string result =
encode_datadog_tracestate(span_id, sampling_priority, origin, trace_tags,
additional_datadog_w3c_tracestate);

if (additional_w3c_tracestate) {
result += ',';
Expand Down
3 changes: 2 additions & 1 deletion src/datadog/w3c_propagation.h
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,8 @@ std::string encode_traceparent(TraceID trace_id, std::uint64_t span_id,

// Return a value for the "tracestate" header containing the specified fields.
std::string encode_tracestate(
int sampling_priority, const Optional<std::string>& origin,
uint64_t span_id, int sampling_priority,
const Optional<std::string>& origin,
const std::vector<std::pair<std::string, std::string>>& trace_tags,
const Optional<std::string>& additional_datadog_w3c_tracestate,
const Optional<std::string>& additional_w3c_tracestate);
Expand Down
30 changes: 17 additions & 13 deletions test/test_span.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -660,6 +660,7 @@ TEST_CASE("injecting W3C tracestate header") {
// - sampling priority
// - origin
// - trace tags
// - parent id
// - extra fields (extracted from W3C)
// - all of the above
// - character substitutions:
Expand Down Expand Up @@ -704,79 +705,79 @@ TEST_CASE("injecting W3C tracestate header") {
{__LINE__, "sampling priority",
{{"x-datadog-trace-id", "1"}, {"x-datadog-parent-id", "1"},
{"x-datadog-sampling-priority", "2"}},
"dd=s:2"},
"dd=s:2;p:$parent_id"},

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

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

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

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

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

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

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

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

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

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

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

{__LINE__, "non-Datadog tracestate",
{{"traceparent", traceparent_drop},
{"tracestate", "foo=bar,boing=boing"}},
// The "s:0" comes from the sampling decision in `traceparent_drop`.
"dd=s:0,foo=bar,boing=boing"},
"dd=s:0;p:$parent_id,foo=bar,boing=boing"},
}));
// clang-format on

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

test_case.expected_tracestate.replace(
test_case.expected_tracestate.find("$parent_id"),
sizeof("$parent_id") - 1, hex_padded(span->id()));
REQUIRE(found->second == test_case.expected_tracestate);

REQUIRE(logger->error_count() == 0);
Expand Down

0 comments on commit 9647240

Please sign in to comment.