-
Notifications
You must be signed in to change notification settings - Fork 11
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
move extraction code into its own component
- Loading branch information
Showing
5 changed files
with
335 additions
and
228 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,240 @@ | ||
#include "extraction_util.h" | ||
|
||
#include <cstdint> | ||
#include <sstream> | ||
#include <string> | ||
#include <unordered_map> | ||
|
||
#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<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 | ||
// `logger`. | ||
void handle_trace_tags(StringView trace_tags, ExtractedData& result, | ||
std::unordered_map<std::string, std::string>& 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<std::uint64_t> 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<Optional<std::uint64_t>> 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<ExtractedData> extract_datadog( | ||
const DictReader& headers, | ||
std::unordered_map<std::string, std::string>& 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<ExtractedData> extract_b3( | ||
const DictReader& headers, std::unordered_map<std::string, std::string>&, | ||
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<ExtractedData> extract_none( | ||
const DictReader&, std::unordered_map<std::string, std::string>&, Logger&) { | ||
ExtractedData result; | ||
result.style = PropagationStyle::NONE; | ||
return result; | ||
} | ||
|
||
std::string extraction_error_prefix( | ||
const Optional<PropagationStyle>& style, | ||
const std::vector<std::pair<std::string, std::string>>& 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<StringView> 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<void(StringView key, StringView value)>& visitor) | ||
const { | ||
underlying.visit([&, this](StringView key, StringView value) { | ||
entries_found.emplace_back(key, value); | ||
visitor(key, value); | ||
}); | ||
} | ||
|
||
} // namespace tracing | ||
} // namespace datadog |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 <cstdint> | ||
#include <string> | ||
#include <unordered_map> | ||
#include <utility> | ||
#include <vector> | ||
|
||
#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<std::uint64_t> 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<std::string, std::string>& 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<Optional<std::uint64_t>> 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<ExtractedData> extract_datadog( | ||
const DictReader& headers, | ||
std::unordered_map<std::string, std::string>& 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<ExtractedData> extract_b3( | ||
const DictReader& headers, std::unordered_map<std::string, std::string>&, | ||
Logger&); | ||
|
||
// Return a default constructed `ExtractedData`, which indicates the absence of | ||
// extracted trace information. | ||
Expected<ExtractedData> extract_none( | ||
const DictReader&, std::unordered_map<std::string, std::string>&, 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<PropagationStyle>& style, | ||
const std::vector<std::pair<std::string, std::string>>& 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<std::pair<std::string, std::string>> entries_found; | ||
|
||
explicit AuditedReader(const DictReader& underlying); | ||
|
||
Optional<StringView> lookup(StringView key) const override; | ||
|
||
void visit(const std::function<void(StringView key, StringView value)>& | ||
visitor) const override; | ||
}; | ||
|
||
} // namespace tracing | ||
} // namespace datadog |
Oops, something went wrong.