diff --git a/BUILD.bazel b/BUILD.bazel index 6f039007..a91d7ec6 100644 --- a/BUILD.bazel +++ b/BUILD.bazel @@ -11,6 +11,7 @@ cc_library( "src/datadog/default_http_client_null.cpp", "src/datadog/environment.cpp", "src/datadog/error.cpp", + "src/datadog/extraction_util.cpp", "src/datadog/glob.cpp", "src/datadog/id_generator.cpp", "src/datadog/limiter.cpp", @@ -59,6 +60,7 @@ cc_library( "src/datadog/event_scheduler.h", "src/datadog/expected.h", "src/datadog/extracted_data.h", + "src/datadog/extraction_util.h", "src/datadog/glob.h", "src/datadog/hex.h", "src/datadog/http_client.h", diff --git a/CMakeLists.txt b/CMakeLists.txt index a0336c13..3dbcd309 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -106,6 +106,7 @@ target_sources(dd_trace_cpp-objects PRIVATE # src/datadog/default_http_client_null.cpp use libcurl src/datadog/environment.cpp src/datadog/error.cpp + src/datadog/extraction_util.cpp src/datadog/glob.cpp src/datadog/id_generator.cpp src/datadog/limiter.cpp @@ -160,6 +161,7 @@ target_sources(dd_trace_cpp-objects PUBLIC src/datadog/event_scheduler.h src/datadog/expected.h src/datadog/extracted_data.h + src/datadog/extraction_util.h src/datadog/glob.h src/datadog/hex.h src/datadog/http_client.h diff --git a/src/datadog/extraction_util.cpp b/src/datadog/extraction_util.cpp new file mode 100644 index 00000000..c96913a1 --- /dev/null +++ b/src/datadog/extraction_util.cpp @@ -0,0 +1,240 @@ +#include "extraction_util.h" + +#include +#include +#include +#include + +#include "extracted_data.h" +#include "json.hpp" +#include "logger.h" +#include "parse_util.h" +#include "tag_propagation.h" +#include "tags.h" + +namespace datadog { +namespace tracing { + +// 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 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 +// `logger`. +void handle_trace_tags(StringView trace_tags, ExtractedData& result, + std::unordered_map& span_tags, + Logger& logger) { + auto maybe_trace_tags = decode_tags(trace_tags); + if (auto* error = maybe_trace_tags.if_error()) { + logger.log_error(*error); + span_tags[tags::internal::propagation_error] = "decoding_error"; + return; + } + + for (auto& [key, value] : *maybe_trace_tags) { + if (!starts_with(key, "_dd.p.")) { + continue; + } + + if (key == tags::internal::trace_id_high) { + // _dd.p.tid contains the high 64 bits of the trace ID. + const Optional high = parse_trace_id_high(value); + if (!high) { + span_tags[tags::internal::propagation_error] = "malformed_tid " + value; + continue; + } + + if (result.trace_id) { + // Note that this assumes the lower 64 bits of the trace ID have already + // been extracted (i.e. we look for X-Datadog-Trace-ID first). + result.trace_id->high = *high; + } + } + + result.trace_tags.emplace_back(std::move(key), std::move(value)); + } +} + +Expected> extract_id_header(const DictReader& headers, + StringView header, + StringView header_kind, + StringView style_name, + int base) { + auto found = headers.lookup(header); + if (!found) { + return nullopt; + } + auto result = parse_uint64(*found, base); + if (auto* error = result.if_error()) { + std::string prefix; + prefix += "Could not extract "; + append(prefix, style_name); + prefix += "-style "; + append(prefix, header_kind); + prefix += "ID from "; + append(prefix, header); + prefix += ": "; + append(prefix, *found); + prefix += ' '; + return error->with_prefix(prefix); + } + return *result; +} + +Expected extract_datadog( + const DictReader& headers, + std::unordered_map& span_tags, Logger& logger) { + ExtractedData result; + result.style = PropagationStyle::DATADOG; + + auto trace_id = + extract_id_header(headers, "x-datadog-trace-id", "trace", "Datadog", 10); + if (auto* error = trace_id.if_error()) { + return std::move(*error); + } + if (*trace_id) { + result.trace_id = TraceID(**trace_id); + } + + auto parent_id = extract_id_header(headers, "x-datadog-parent-id", + "parent span", "Datadog", 10); + if (auto* error = parent_id.if_error()) { + return std::move(*error); + } + result.parent_id = *parent_id; + + const StringView sampling_priority_header = "x-datadog-sampling-priority"; + if (auto found = headers.lookup(sampling_priority_header)) { + auto sampling_priority = parse_int(*found, 10); + if (auto* error = sampling_priority.if_error()) { + std::string prefix; + prefix += "Could not extract Datadog-style sampling priority from "; + append(prefix, sampling_priority_header); + prefix += ": "; + append(prefix, *found); + prefix += ' '; + return error->with_prefix(prefix); + } + result.sampling_priority = *sampling_priority; + } + + auto origin = headers.lookup("x-datadog-origin"); + if (origin) { + result.origin = std::string(*origin); + } + + auto trace_tags = headers.lookup("x-datadog-tags"); + if (trace_tags) { + handle_trace_tags(*trace_tags, result, span_tags, logger); + } + + return result; +} + +Expected extract_b3( + const DictReader& headers, std::unordered_map&, + Logger&) { + ExtractedData result; + result.style = PropagationStyle::B3; + + if (auto found = headers.lookup("x-b3-traceid")) { + auto parsed = TraceID::parse_hex(*found); + if (auto* error = parsed.if_error()) { + std::string prefix = "Could not extract B3-style trace ID from \""; + append(prefix, *found); + prefix += "\": "; + return error->with_prefix(prefix); + } + result.trace_id = *parsed; + } + + auto parent_id = + extract_id_header(headers, "x-b3-spanid", "parent span", "B3", 16); + if (auto* error = parent_id.if_error()) { + return std::move(*error); + } + result.parent_id = *parent_id; + + const StringView sampling_priority_header = "x-b3-sampled"; + if (auto found = headers.lookup(sampling_priority_header)) { + auto sampling_priority = parse_int(*found, 10); + if (auto* error = sampling_priority.if_error()) { + std::string prefix; + prefix += "Could not extract B3-style sampling priority from "; + append(prefix, sampling_priority_header); + prefix += ": "; + append(prefix, *found); + prefix += ' '; + return error->with_prefix(prefix); + } + result.sampling_priority = *sampling_priority; + } + + return result; +} + +Expected extract_none( + const DictReader&, std::unordered_map&, Logger&) { + ExtractedData result; + result.style = PropagationStyle::NONE; + return result; +} + +std::string extraction_error_prefix( + const Optional& style, + const std::vector>& headers_examined) { + std::ostringstream stream; + stream << "While extracting trace context"; + if (style) { + stream << " in the " << to_json(*style) << " propagation style"; + } + auto it = headers_examined.begin(); + if (it != headers_examined.end()) { + stream << " from the following headers: ["; + stream << nlohmann::json(it->first + ": " + it->second); + for (++it; it != headers_examined.end(); ++it) { + stream << ", "; + stream << nlohmann::json(it->first + ": " + it->second); + } + stream << "]"; + } + stream << ", an error occurred: "; + return stream.str(); +} + +AuditedReader::AuditedReader(const DictReader& underlying) + : underlying(underlying) {} + +Optional AuditedReader::lookup(StringView key) const { + auto value = underlying.lookup(key); + if (value) { + entries_found.emplace_back(key, *value); + } + return value; +} + +void AuditedReader::visit( + const std::function& visitor) + const { + underlying.visit([&, this](StringView key, StringView value) { + entries_found.emplace_back(key, value); + visitor(key, value); + }); +} + +} // namespace tracing +} // namespace datadog diff --git a/src/datadog/extraction_util.h b/src/datadog/extraction_util.h new file mode 100644 index 00000000..cb148494 --- /dev/null +++ b/src/datadog/extraction_util.h @@ -0,0 +1,90 @@ +#pragma once + +// This component provides facilities for extracting trace context from a +// `DictReader`. It is used by `Tracer::extract_trace`. See `tracer.cpp`. + +#include +#include +#include +#include +#include + +#include "dict_reader.h" +#include "expected.h" +#include "optional.h" +#include "propagation_style.h" + +namespace datadog { +namespace tracing { + +struct ExtractedData; +class Logger; + +// 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 parse_trace_id_high(const std::string& value); + +// 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 +// `logger`. +void handle_trace_tags(StringView trace_tags, ExtractedData& result, + std::unordered_map& span_tags, + Logger& logger); + +// Extract an ID from the specified `header`, which might be present in the +// specified `headers`, and return the ID. If `header` is not present in +// `headers`, then return `nullopt`. If an error occurs, return an `Error`. +// Parse the ID with respect to the specified numeric `base`, e.g. `10` or `16`. +// The specified `header_kind` and `style_name` are used in diagnostic messages +// should an error occur. +Expected> extract_id_header(const DictReader& headers, + StringView header, + StringView header_kind, + StringView style_name, + int base); + +// Return trace information parsed from the specified `headers` in the Datadog +// propagation style. Use the specified `span_tags` and `logger` to report +// warnings. If an error occurs, return an `Error`. +Expected extract_datadog( + const DictReader& headers, + std::unordered_map& span_tags, Logger& logger); + +// Return trace information parsed from the specified `headers` in the B3 +// multi-header propagation style. If an error occurs, return an `Error`. +Expected extract_b3( + const DictReader& headers, std::unordered_map&, + Logger&); + +// Return a default constructed `ExtractedData`, which indicates the absence of +// extracted trace information. +Expected extract_none( + const DictReader&, std::unordered_map&, Logger&); + +// Return a string that can be used as the argument to `Error::with_prefix` for +// errors occurring while extracting trace information in the specified `style` +// from the specified `headers_examined`. +std::string extraction_error_prefix( + const Optional& style, + const std::vector>& headers_examined); + +// `AuditedReader` is a `DictReader` that remembers all key/value pairs looked +// up or visited through it. It remembers a lookup only if it yielded a non-null +// value. This is used for error diagnostic messages in trace extraction (i.e. +// an error occurred, but which HTTP request headers were we looking at?). +struct AuditedReader : public DictReader { + const DictReader& underlying; + mutable std::vector> entries_found; + + explicit AuditedReader(const DictReader& underlying); + + Optional lookup(StringView key) const override; + + void visit(const std::function& + visitor) const override; +}; + +} // namespace tracing +} // namespace datadog diff --git a/src/datadog/tracer.cpp b/src/datadog/tracer.cpp index 96b0daf8..78fb5905 100644 --- a/src/datadog/tracer.cpp +++ b/src/datadog/tracer.cpp @@ -8,6 +8,7 @@ #include "dict_reader.h" #include "environment.h" #include "extracted_data.h" +#include "extraction_util.h" #include "hex.h" #include "json.hpp" #include "logger.h" @@ -26,234 +27,6 @@ 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 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 -// `logger`. -void handle_trace_tags(StringView trace_tags, ExtractedData& result, - std::unordered_map& span_tags, - Logger& logger) { - auto maybe_trace_tags = decode_tags(trace_tags); - if (auto* error = maybe_trace_tags.if_error()) { - logger.log_error(*error); - span_tags[tags::internal::propagation_error] = "decoding_error"; - return; - } - - for (auto& [key, value] : *maybe_trace_tags) { - if (!starts_with(key, "_dd.p.")) { - continue; - } - - if (key == tags::internal::trace_id_high) { - // _dd.p.tid contains the high 64 bits of the trace ID. - const Optional high = parse_trace_id_high(value); - if (!high) { - span_tags[tags::internal::propagation_error] = "malformed_tid " + value; - continue; - } - - if (result.trace_id) { - // Note that this assumes the lower 64 bits of the trace ID have already - // been extracted (i.e. we look for X-Datadog-Trace-ID first). - result.trace_id->high = *high; - } - } - - result.trace_tags.emplace_back(std::move(key), std::move(value)); - } -} - -Expected> extract_id_header(const DictReader& headers, - StringView header, - StringView header_kind, - StringView style_name, - int base) { - auto found = headers.lookup(header); - if (!found) { - return nullopt; - } - auto result = parse_uint64(*found, base); - if (auto* error = result.if_error()) { - std::string prefix; - prefix += "Could not extract "; - append(prefix, style_name); - prefix += "-style "; - append(prefix, header_kind); - prefix += "ID from "; - append(prefix, header); - prefix += ": "; - append(prefix, *found); - prefix += ' '; - return error->with_prefix(prefix); - } - return *result; -} - -Expected extract_datadog( - const DictReader& headers, - std::unordered_map& span_tags, Logger& logger) { - ExtractedData result; - result.style = PropagationStyle::DATADOG; - - auto trace_id = - extract_id_header(headers, "x-datadog-trace-id", "trace", "Datadog", 10); - if (auto* error = trace_id.if_error()) { - return std::move(*error); - } - if (*trace_id) { - result.trace_id = TraceID(**trace_id); - } - - auto parent_id = extract_id_header(headers, "x-datadog-parent-id", - "parent span", "Datadog", 10); - if (auto* error = parent_id.if_error()) { - return std::move(*error); - } - result.parent_id = *parent_id; - - const StringView sampling_priority_header = "x-datadog-sampling-priority"; - if (auto found = headers.lookup(sampling_priority_header)) { - auto sampling_priority = parse_int(*found, 10); - if (auto* error = sampling_priority.if_error()) { - std::string prefix; - prefix += "Could not extract Datadog-style sampling priority from "; - append(prefix, sampling_priority_header); - prefix += ": "; - append(prefix, *found); - prefix += ' '; - return error->with_prefix(prefix); - } - result.sampling_priority = *sampling_priority; - } - - auto origin = headers.lookup("x-datadog-origin"); - if (origin) { - result.origin = std::string(*origin); - } - - auto trace_tags = headers.lookup("x-datadog-tags"); - if (trace_tags) { - handle_trace_tags(*trace_tags, result, span_tags, logger); - } - - return result; -} - -Expected extract_b3( - const DictReader& headers, std::unordered_map&, - Logger&) { - ExtractedData result; - result.style = PropagationStyle::B3; - - if (auto found = headers.lookup("x-b3-traceid")) { - auto parsed = TraceID::parse_hex(*found); - if (auto* error = parsed.if_error()) { - std::string prefix = "Could not extract B3-style trace ID from \""; - append(prefix, *found); - prefix += "\": "; - return error->with_prefix(prefix); - } - result.trace_id = *parsed; - } - - auto parent_id = - extract_id_header(headers, "x-b3-spanid", "parent span", "B3", 16); - if (auto* error = parent_id.if_error()) { - return std::move(*error); - } - result.parent_id = *parent_id; - - const StringView sampling_priority_header = "x-b3-sampled"; - if (auto found = headers.lookup(sampling_priority_header)) { - auto sampling_priority = parse_int(*found, 10); - if (auto* error = sampling_priority.if_error()) { - std::string prefix; - prefix += "Could not extract B3-style sampling priority from "; - append(prefix, sampling_priority_header); - prefix += ": "; - append(prefix, *found); - prefix += ' '; - return error->with_prefix(prefix); - } - result.sampling_priority = *sampling_priority; - } - - return result; -} - -Expected extract_none( - const DictReader&, std::unordered_map&, Logger&) { - ExtractedData result; - result.style = PropagationStyle::NONE; - return result; -} - -std::string extraction_error_prefix( - const Optional& style, - const std::vector>& headers_examined) { - std::ostringstream stream; - stream << "While extracting trace context"; - if (style) { - stream << " in the " << to_json(*style) << " propagation style"; - } - auto it = headers_examined.begin(); - if (it != headers_examined.end()) { - stream << " from the following headers: ["; - stream << nlohmann::json(it->first + ": " + it->second); - for (++it; it != headers_examined.end(); ++it) { - stream << ", "; - stream << nlohmann::json(it->first + ": " + it->second); - } - stream << "]"; - } - stream << ", an error occurred: "; - return stream.str(); -} - -struct AuditedReader : public DictReader { - const DictReader& underlying; - mutable std::vector> entries_found; - - explicit AuditedReader(const DictReader& underlying) - : underlying(underlying) {} - - Optional lookup(StringView key) const override { - auto value = underlying.lookup(key); - if (value) { - entries_found.emplace_back(key, *value); - } - return value; - } - - void visit(const std::function& - visitor) const override { - underlying.visit([&, this](StringView key, StringView value) { - entries_found.emplace_back(key, value); - visitor(key, value); - }); - } -}; - -} // namespace Tracer::Tracer(const FinalizedTracerConfig& config) : Tracer(config, default_id_generator(config.trace_id_128_bit)) {}