Skip to content

Commit

Permalink
move extraction code into its own component
Browse files Browse the repository at this point in the history
  • Loading branch information
dgoffredo committed Nov 16, 2023
1 parent 9c87ef6 commit 322326b
Show file tree
Hide file tree
Showing 5 changed files with 335 additions and 228 deletions.
2 changes: 2 additions & 0 deletions BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down Expand Up @@ -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",
Expand Down
2 changes: 2 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
240 changes: 240 additions & 0 deletions src/datadog/extraction_util.cpp
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
90 changes: 90 additions & 0 deletions src/datadog/extraction_util.h
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
Loading

0 comments on commit 322326b

Please sign in to comment.