-
Notifications
You must be signed in to change notification settings - Fork 19
enforce fixed length of 16 for _dd.p.tid #42
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
Changes from all commits
93c25b3
e45b0c1
680bcac
69db7d9
20f8dd0
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -27,6 +27,22 @@ namespace datadog { | |
| namespace tracing { | ||
| namespace { | ||
|
|
||
| // Parse the high 64 bits of a trace ID from the specified `value`. If `value` | ||
| // is correctly formatted, then return the resulting bits. If `value` is | ||
| // incorrectly formatted then return `nullopt`. | ||
| Optional<std::uint64_t> parse_trace_id_high(const std::string& value) { | ||
| if (value.size() != 16) { | ||
| return nullopt; | ||
| } | ||
|
|
||
| auto high = parse_uint64(value, 16); | ||
| if (high) { | ||
| return *high; | ||
| } | ||
|
|
||
| return nullopt; | ||
| } | ||
|
|
||
| // Decode the specified `trace_tags` and integrate them into the specified | ||
| // `result`. If an error occurs, add a `tags::internal::propagation_error` tag | ||
| // to the specified `span_tags` and log a diagnostic using the specified | ||
|
|
@@ -48,12 +64,8 @@ void handle_trace_tags(StringView trace_tags, ExtractedData& result, | |
|
|
||
| if (key == tags::internal::trace_id_high) { | ||
| // _dd.p.tid contains the high 64 bits of the trace ID. | ||
| auto high = parse_uint64(value, 16); | ||
| if (auto* error = high.if_error()) { | ||
| logger.log_error( | ||
| error->with_prefix("Unable to parse high bits of the trace ID in " | ||
| "Datadog style from the " | ||
| "\"_dd.p.tid\" trace tag: ")); | ||
| const Optional<std::uint64_t> high = parse_trace_id_high(value); | ||
| if (!high) { | ||
| span_tags[tags::internal::propagation_error] = "malformed_tid " + value; | ||
| continue; | ||
| } | ||
|
|
@@ -274,7 +286,7 @@ Span Tracer::create_span(const SpanConfig& config) { | |
| span_data->trace_id = generator_->trace_id(span_data->start); | ||
| if (span_data->trace_id.high) { | ||
| trace_tags.emplace_back(tags::internal::trace_id_high, | ||
| hex(span_data->trace_id.high)); | ||
| hex_padded(span_data->trace_id.high)); | ||
| } | ||
| span_data->span_id = span_data->trace_id.low; | ||
| span_data->parent_id = 0; | ||
|
|
@@ -401,21 +413,27 @@ Expected<Span> Tracer::extract_span(const DictReader& reader, | |
| // | ||
| // First, though, if the `trace_id_high` tag is already set and has a bogus | ||
| // value or a value inconsistent with the trace ID, tag an error. | ||
| const std::string hex_high = hex(span_data->trace_id.high); | ||
| const auto hex_high = hex_padded(span_data->trace_id.high); | ||
| const auto extant = std::find_if( | ||
| trace_tags.begin(), trace_tags.end(), [&](const auto& pair) { | ||
| return pair.first == tags::internal::trace_id_high; | ||
| }); | ||
| if (extant == trace_tags.end()) { | ||
| trace_tags.emplace_back(tags::internal::trace_id_high, hex_high); | ||
| } else if (!parse_uint64(extant->second, 16)) { | ||
| span_data->tags[tags::internal::propagation_error] = | ||
| "malformed_tid " + extant->second; | ||
| extant->second = hex_high; | ||
| } else if (extant->second != hex_high) { | ||
| span_data->tags[tags::internal::propagation_error] = | ||
| "inconsistent_tid " + extant->second; | ||
| extant->second = hex_high; | ||
| } else { | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Maybe add a comment here, supporting the comment above, describing what's going on. For example, the appearance of |
||
| // There is already a `trace_id_high` tag. `hex_high` is its proper value. | ||
| // Check if the extant value is malformed or different from `hex_high`. In | ||
| // either case, tag an error and overwrite the tag with `hex_high`. | ||
| const Optional<std::uint64_t> high = parse_trace_id_high(extant->second); | ||
| if (!high) { | ||
| span_data->tags[tags::internal::propagation_error] = | ||
| "malformed_tid " + extant->second; | ||
| extant->second = hex_high; | ||
| } else if (*high != span_data->trace_id.high) { | ||
| span_data->tags[tags::internal::propagation_error] = | ||
| "inconsistent_tid " + extant->second; | ||
| extant->second = hex_high; | ||
| } | ||
| } | ||
| } | ||
|
|
||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,135 @@ | ||
| #include <datadog/error.h> | ||
| #include <datadog/parse_util.h> | ||
|
|
||
| #include <cassert> | ||
| #include <cstdint> | ||
| #include <limits> | ||
| #include <string> | ||
| #include <variant> | ||
|
|
||
| #include "test.h" | ||
|
|
||
| using namespace datadog::tracing; | ||
|
|
||
| TEST_CASE("parse_int") { | ||
| struct TestCase { | ||
| int line; | ||
| std::string name; | ||
| std::string argument; | ||
| int base; | ||
| std::variant<int, Error::Code> expected; | ||
| }; | ||
|
|
||
| // clang-format off | ||
| auto test_case = GENERATE(values<TestCase>({ | ||
| {__LINE__, "zero (dec)", "0", 10, 0}, | ||
| {__LINE__, "zeros (dec)", "000", 10, 0}, | ||
| {__LINE__, "zero (hex)", "0", 16, 0}, | ||
| {__LINE__, "zeros (hex)", "000", 16, 0}, | ||
| {__LINE__, "leading whitespace (dec 1)", " 42", 10, Error::INVALID_INTEGER}, | ||
| {__LINE__, "leading whitespace (dec 2)", "\t42", 10, Error::INVALID_INTEGER}, | ||
| {__LINE__, "leading whitespace (dec 3)", "\n42", 10, Error::INVALID_INTEGER}, | ||
| {__LINE__, "trailing whitespace (dec 1)", "42 ", 10, Error::INVALID_INTEGER}, | ||
| {__LINE__, "trailing whitespace (dec 2)", "42\t", 10, Error::INVALID_INTEGER}, | ||
| {__LINE__, "trailing whitespace (dec 3)", "42\n", 10, Error::INVALID_INTEGER}, | ||
| {__LINE__, "leading whitespace (hex 1)", " 42", 16, Error::INVALID_INTEGER}, | ||
| {__LINE__, "leading whitespace (hex 2)", "\t42", 16, Error::INVALID_INTEGER}, | ||
| {__LINE__, "leading whitespace (hex 3)", "\n42", 16, Error::INVALID_INTEGER}, | ||
| {__LINE__, "trailing whitespace (hex 1)", "42 ", 16, Error::INVALID_INTEGER}, | ||
| {__LINE__, "trailing whitespace (hex 2)", "42\t", 16, Error::INVALID_INTEGER}, | ||
| {__LINE__, "trailing whitespace (hex 3)", "42\n", 16, Error::INVALID_INTEGER}, | ||
| {__LINE__, "no hex prefix", "0xbeef", 16, Error::INVALID_INTEGER}, | ||
| {__LINE__, "dec rejects hex", "42beef", 10, Error::INVALID_INTEGER}, | ||
| {__LINE__, "hex accepts hex", "42beef", 16, 0x42beef}, | ||
| {__LINE__, "no trailing nonsense (dec)", "42xyz", 10, Error::INVALID_INTEGER}, | ||
| {__LINE__, "no trailing nonsense (hex)", "42xyz", 16, Error::INVALID_INTEGER}, | ||
| {__LINE__, "no leading nonsense (dec)", "xyz42", 10, Error::INVALID_INTEGER}, | ||
| {__LINE__, "no leading nonsense (hex)", "xyz42", 16, Error::INVALID_INTEGER}, | ||
| {__LINE__, "overflow", std::to_string(std::numeric_limits<int>::max()) + "0", 10, Error::OUT_OF_RANGE_INTEGER}, | ||
| {__LINE__, "negative (dec)", "-10", 10, -10}, | ||
| {__LINE__, "negative (hex)", "-a", 16, -10}, | ||
| {__LINE__, "lower case", "a", 16, 10}, | ||
| {__LINE__, "upper case", "A", 16, 10}, | ||
| {__LINE__, "underflow", std::to_string(std::numeric_limits<int>::min()) + "0", 10, Error::OUT_OF_RANGE_INTEGER}, | ||
| })); | ||
| // clang-format on | ||
|
|
||
| CAPTURE(test_case.line); | ||
| CAPTURE(test_case.name); | ||
| CAPTURE(test_case.argument); | ||
| CAPTURE(test_case.base); | ||
|
|
||
| const auto result = parse_int(test_case.argument, test_case.base); | ||
| if (std::holds_alternative<int>(test_case.expected)) { | ||
| const int& expected = std::get<int>(test_case.expected); | ||
| REQUIRE(result); | ||
| REQUIRE(*result == expected); | ||
| } else { | ||
| assert(std::holds_alternative<Error::Code>(test_case.expected)); | ||
| const Error::Code& expected = std::get<Error::Code>(test_case.expected); | ||
| REQUIRE(!result); | ||
| REQUIRE(result.error().code == expected); | ||
| } | ||
| } | ||
|
|
||
| // This test case is similar to the one above, except that negative numbers are | ||
| // not supported, and the underflow and overflow values are different. | ||
| TEST_CASE("parse_uint64") { | ||
| struct TestCase { | ||
| int line; | ||
| std::string name; | ||
| std::string argument; | ||
| int base; | ||
| std::variant<std::uint64_t, Error::Code> expected; | ||
| }; | ||
|
|
||
| // clang-format off | ||
| auto test_case = GENERATE(values<TestCase>({ | ||
| {__LINE__, "zero (dec)", "0", 10, UINT64_C(0)}, | ||
| {__LINE__, "zeros (dec)", "000", 10, UINT64_C(0)}, | ||
| {__LINE__, "zero (hex)", "0", 16, UINT64_C(0)}, | ||
| {__LINE__, "zeros (hex)", "000", 16, UINT64_C(0)}, | ||
| {__LINE__, "leading whitespace (dec 1)", " 42", 10, Error::INVALID_INTEGER}, | ||
| {__LINE__, "leading whitespace (dec 2)", "\t42", 10, Error::INVALID_INTEGER}, | ||
| {__LINE__, "leading whitespace (dec 3)", "\n42", 10, Error::INVALID_INTEGER}, | ||
| {__LINE__, "trailing whitespace (dec 1)", "42 ", 10, Error::INVALID_INTEGER}, | ||
| {__LINE__, "trailing whitespace (dec 2)", "42\t", 10, Error::INVALID_INTEGER}, | ||
| {__LINE__, "trailing whitespace (dec 3)", "42\n", 10, Error::INVALID_INTEGER}, | ||
| {__LINE__, "leading whitespace (hex 1)", " 42", 16, Error::INVALID_INTEGER}, | ||
| {__LINE__, "leading whitespace (hex 2)", "\t42", 16, Error::INVALID_INTEGER}, | ||
| {__LINE__, "leading whitespace (hex 3)", "\n42", 16, Error::INVALID_INTEGER}, | ||
| {__LINE__, "trailing whitespace (hex 1)", "42 ", 16, Error::INVALID_INTEGER}, | ||
| {__LINE__, "trailing whitespace (hex 2)", "42\t", 16, Error::INVALID_INTEGER}, | ||
| {__LINE__, "trailing whitespace (hex 3)", "42\n", 16, Error::INVALID_INTEGER}, | ||
| {__LINE__, "no hex prefix", "0xbeef", 16, Error::INVALID_INTEGER}, | ||
| {__LINE__, "dec rejects hex", "42beef", 10, Error::INVALID_INTEGER}, | ||
| {__LINE__, "hex accepts hex", "42beef", 16, UINT64_C(0x42beef)}, | ||
| {__LINE__, "no trailing nonsense (dec)", "42xyz", 10, Error::INVALID_INTEGER}, | ||
| {__LINE__, "no trailing nonsense (hex)", "42xyz", 16, Error::INVALID_INTEGER}, | ||
| {__LINE__, "no leading nonsense (dec)", "xyz42", 10, Error::INVALID_INTEGER}, | ||
| {__LINE__, "no leading nonsense (hex)", "xyz42", 16, Error::INVALID_INTEGER}, | ||
| {__LINE__, "overflow", std::to_string(std::numeric_limits<std::uint64_t>::max()) + "0", 10, Error::OUT_OF_RANGE_INTEGER}, | ||
| {__LINE__, "negative (dec)", "-10", 10, Error::INVALID_INTEGER}, | ||
| {__LINE__, "negative (hex)", "-a", 16, Error::INVALID_INTEGER}, | ||
| {__LINE__, "lower case", "a", 16, UINT64_C(10)}, | ||
| {__LINE__, "upper case", "A", 16, UINT64_C(10)}, | ||
| })); | ||
| // clang-format on | ||
|
|
||
| CAPTURE(test_case.line); | ||
| CAPTURE(test_case.name); | ||
| CAPTURE(test_case.argument); | ||
| CAPTURE(test_case.base); | ||
|
|
||
| const auto result = parse_uint64(test_case.argument, test_case.base); | ||
| if (std::holds_alternative<std::uint64_t>(test_case.expected)) { | ||
| const std::uint64_t& expected = std::get<std::uint64_t>(test_case.expected); | ||
| REQUIRE(result); | ||
| REQUIRE(*result == expected); | ||
| } else { | ||
| assert(std::holds_alternative<Error::Code>(test_case.expected)); | ||
| const Error::Code& expected = std::get<Error::Code>(test_case.expected); | ||
| REQUIRE(!result); | ||
| REQUIRE(result.error().code == expected); | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -1042,11 +1042,30 @@ TEST_CASE("128-bit trace IDs") { | |
| trace_id = span->trace_id(); | ||
| } | ||
|
|
||
| SECTION("are, for W3C, extracted preferentially from traceparent") { | ||
| auto tid = GENERATE(values<std::string>({"decade", "deadbeefdeadbeed"})); | ||
| std::unordered_map<std::string, std::string> headers; | ||
| headers["traceparent"] = | ||
| "00-deadbeefdeadbeefcafebabecafebabe-0000000000000001-01"; | ||
| // The _dd.p.tid value below is either malformed or inconsistent with the | ||
| // trace ID in the traceparent. | ||
| // It will be ignored, and the resulting _dd.p.tid value will be consistent | ||
| // with the higher part of the trace ID in traceparent: "deadbeefdeadbeef". | ||
| headers["tracestate"] = "dd=t.tid:" + tid; | ||
| MockDictReader reader{headers}; | ||
| const auto span = tracer.extract_span(reader); | ||
| CAPTURE(logger->entries); | ||
| REQUIRE(logger->error_count() == 0); | ||
| REQUIRE(span); | ||
| REQUIRE(hex(span->trace_id().high) == "deadbeefdeadbeef"); | ||
| trace_id = span->trace_id(); | ||
| } | ||
|
|
||
| SECTION("are extracted from Datadog (_dd.p.tid)") { | ||
| std::unordered_map<std::string, std::string> headers; | ||
| headers["x-datadog-trace-id"] = "4"; | ||
| headers["x-datadog-parent-id"] = "42"; | ||
| headers["x-datadog-tags"] = "_dd.p.tid=beef"; | ||
| headers["x-datadog-tags"] = "_dd.p.tid=000000000000beef"; | ||
| MockDictReader reader{headers}; | ||
| const auto span = tracer.extract_span(reader); | ||
| CAPTURE(logger->entries); | ||
|
|
@@ -1094,7 +1113,9 @@ TEST_CASE( | |
|
|
||
| auto test_case = GENERATE(values<TestCase>( | ||
| {{__LINE__, "invalid _dd.p.tid", "noodle", "malformed_tid "}, | ||
| {__LINE__, "_dd.p.tid inconsistent with trace ID", "adfeed", | ||
| {__LINE__, "short _dd.p.tid", "beef", "malformed_tid "}, | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'd probably test with values of length 0, 1, 15, and 17.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It is arbitrary. For the long tests I held down 1, 15, and 17, to be just below and above 16? Yeah, I see your point. I'll leave this, though. |
||
| {__LINE__, "long _dd.p.tid", "000000000000000000beef", "malformed_tid "}, | ||
| {__LINE__, "_dd.p.tid inconsistent with trace ID", "0000000000adfeed", | ||
| "inconsistent_tid "}})); | ||
|
|
||
| CAPTURE(test_case.line); | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd consider tagging things in this block instead. It's the point where the error is being handled
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I could remove the
span_tagsargument fromparse_trace_id_high, and duplicate the setting of"malformed_tid " ...in the two places where it is used. Yeah, I like that better.