Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: extract/inject datadog tracestate field p #115

Merged
merged 5 commits into from
May 15, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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";

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Curious to know: Is there a reason to use 0s id instead of an empty field somehow? Is the all 0s value a special value somewhere else?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's from the RFC. I think it's an optimization for the backend.

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
Loading
Loading