LCOV - code coverage report
Current view: top level - datadog - extraction_util.cpp (source / functions) Hit Total Coverage
Test: filtered.info Lines: 163 171 95.3 %
Date: 2024-01-03 20:30:12 Functions: 13 15 86.7 %

          Line data    Source code
       1             : #include "extraction_util.h"
       2             : 
       3             : #include <algorithm>
       4             : #include <cstdint>
       5             : #include <sstream>
       6             : #include <string>
       7             : #include <unordered_map>
       8             : 
       9             : #include "extracted_data.h"
      10             : #include "json.hpp"
      11             : #include "logger.h"
      12             : #include "parse_util.h"
      13             : #include "tag_propagation.h"
      14             : #include "tags.h"
      15             : 
      16             : namespace datadog {
      17             : namespace tracing {
      18             : 
      19          10 : Optional<std::uint64_t> parse_trace_id_high(const std::string& value) {
      20          10 :   if (value.size() != 16) {
      21           6 :     return nullopt;
      22             :   }
      23             : 
      24           4 :   auto high = parse_uint64(value, 16);
      25           4 :   if (high) {
      26           4 :     return *high;
      27             :   }
      28             : 
      29           0 :   return nullopt;
      30           4 : }
      31             : 
      32          16 : void handle_trace_tags(StringView trace_tags, ExtractedData& result,
      33             :                        std::unordered_map<std::string, std::string>& span_tags,
      34             :                        Logger& logger) {
      35          16 :   auto maybe_trace_tags = decode_tags(trace_tags);
      36          16 :   if (auto* error = maybe_trace_tags.if_error()) {
      37           1 :     logger.log_error(*error);
      38           1 :     span_tags[tags::internal::propagation_error] = "decoding_error";
      39           1 :     return;
      40             :   }
      41             : 
      42       10042 :   for (auto& [key, value] : *maybe_trace_tags) {
      43       10027 :     if (!starts_with(key, "_dd.p.")) {
      44           8 :       continue;
      45             :     }
      46             : 
      47       10019 :     if (key == tags::internal::trace_id_high) {
      48             :       // _dd.p.tid contains the high 64 bits of the trace ID.
      49           3 :       const Optional<std::uint64_t> high = parse_trace_id_high(value);
      50           3 :       if (!high) {
      51           2 :         span_tags[tags::internal::propagation_error] = "malformed_tid " + value;
      52           2 :         continue;
      53             :       }
      54             : 
      55           1 :       if (result.trace_id) {
      56             :         // Note that this assumes the lower 64 bits of the trace ID have already
      57             :         // been extracted (i.e. we look for X-Datadog-Trace-ID first).
      58           1 :         result.trace_id->high = *high;
      59             :       }
      60             :     }
      61             : 
      62       10017 :     result.trace_tags.emplace_back(std::move(key), std::move(value));
      63             :   }
      64          16 : }
      65             : 
      66         203 : Expected<Optional<std::uint64_t>> extract_id_header(const DictReader& headers,
      67             :                                                     StringView header,
      68             :                                                     StringView header_kind,
      69             :                                                     StringView style_name,
      70             :                                                     int base) {
      71         203 :   auto found = headers.lookup(header);
      72         203 :   if (!found) {
      73          46 :     return nullopt;
      74             :   }
      75         157 :   auto result = parse_uint64(*found, base);
      76         157 :   if (auto* error = result.if_error()) {
      77          12 :     std::string prefix;
      78          12 :     prefix += "Could not extract ";
      79          12 :     append(prefix, style_name);
      80          12 :     prefix += "-style ";
      81          12 :     append(prefix, header_kind);
      82          12 :     prefix += "ID from ";
      83          12 :     append(prefix, header);
      84          12 :     prefix += ": ";
      85          12 :     append(prefix, *found);
      86          12 :     prefix += ' ';
      87          12 :     return error->with_prefix(prefix);
      88          12 :   }
      89         145 :   return *result;
      90         157 : }
      91             : 
      92          89 : Expected<ExtractedData> extract_datadog(
      93             :     const DictReader& headers,
      94             :     std::unordered_map<std::string, std::string>& span_tags, Logger& logger) {
      95          89 :   ExtractedData result;
      96          89 :   result.style = PropagationStyle::DATADOG;
      97             : 
      98             :   auto trace_id =
      99          89 :       extract_id_header(headers, "x-datadog-trace-id", "trace", "Datadog", 10);
     100          89 :   if (auto* error = trace_id.if_error()) {
     101           4 :     return std::move(*error);
     102             :   }
     103          85 :   if (*trace_id) {
     104          68 :     result.trace_id = TraceID(**trace_id);
     105             :   }
     106             : 
     107             :   auto parent_id = extract_id_header(headers, "x-datadog-parent-id",
     108          85 :                                      "parent span", "Datadog", 10);
     109          85 :   if (auto* error = parent_id.if_error()) {
     110           4 :     return std::move(*error);
     111             :   }
     112          81 :   result.parent_id = *parent_id;
     113             : 
     114          81 :   const StringView sampling_priority_header = "x-datadog-sampling-priority";
     115          81 :   if (auto found = headers.lookup(sampling_priority_header)) {
     116          34 :     auto sampling_priority = parse_int(*found, 10);
     117          34 :     if (auto* error = sampling_priority.if_error()) {
     118           4 :       std::string prefix;
     119           4 :       prefix += "Could not extract Datadog-style sampling priority from ";
     120           4 :       append(prefix, sampling_priority_header);
     121           4 :       prefix += ": ";
     122           4 :       append(prefix, *found);
     123           4 :       prefix += ' ';
     124           4 :       return error->with_prefix(prefix);
     125           4 :     }
     126          30 :     result.sampling_priority = *sampling_priority;
     127          34 :   }
     128             : 
     129          77 :   auto origin = headers.lookup("x-datadog-origin");
     130          77 :   if (origin) {
     131          15 :     result.origin = std::string(*origin);
     132             :   }
     133             : 
     134          77 :   auto trace_tags = headers.lookup("x-datadog-tags");
     135          77 :   if (trace_tags) {
     136          16 :     handle_trace_tags(*trace_tags, result, span_tags, logger);
     137             :   }
     138             : 
     139          77 :   return result;
     140          89 : }
     141             : 
     142          33 : Expected<ExtractedData> extract_b3(
     143             :     const DictReader& headers, std::unordered_map<std::string, std::string>&,
     144             :     Logger&) {
     145          33 :   ExtractedData result;
     146          33 :   result.style = PropagationStyle::B3;
     147             : 
     148          33 :   if (auto found = headers.lookup("x-b3-traceid")) {
     149          28 :     auto parsed = TraceID::parse_hex(*found);
     150          28 :     if (auto* error = parsed.if_error()) {
     151           4 :       std::string prefix = "Could not extract B3-style trace ID from \"";
     152           4 :       append(prefix, *found);
     153           4 :       prefix += "\": ";
     154           4 :       return error->with_prefix(prefix);
     155           4 :     }
     156          24 :     result.trace_id = *parsed;
     157          28 :   }
     158             : 
     159             :   auto parent_id =
     160          29 :       extract_id_header(headers, "x-b3-spanid", "parent span", "B3", 16);
     161          29 :   if (auto* error = parent_id.if_error()) {
     162           4 :     return std::move(*error);
     163             :   }
     164          25 :   result.parent_id = *parent_id;
     165             : 
     166          25 :   const StringView sampling_priority_header = "x-b3-sampled";
     167          25 :   if (auto found = headers.lookup(sampling_priority_header)) {
     168          11 :     auto sampling_priority = parse_int(*found, 10);
     169          11 :     if (auto* error = sampling_priority.if_error()) {
     170           4 :       std::string prefix;
     171           4 :       prefix += "Could not extract B3-style sampling priority from ";
     172           4 :       append(prefix, sampling_priority_header);
     173           4 :       prefix += ": ";
     174           4 :       append(prefix, *found);
     175           4 :       prefix += ' ';
     176           4 :       return error->with_prefix(prefix);
     177           4 :     }
     178           7 :     result.sampling_priority = *sampling_priority;
     179          11 :   }
     180             : 
     181          21 :   return result;
     182          33 : }
     183             : 
     184           1 : Expected<ExtractedData> extract_none(
     185             :     const DictReader&, std::unordered_map<std::string, std::string>&, Logger&) {
     186           1 :   ExtractedData result;
     187           1 :   result.style = PropagationStyle::NONE;
     188           1 :   return result;
     189           1 : }
     190             : 
     191          35 : std::string extraction_error_prefix(
     192             :     const Optional<PropagationStyle>& style,
     193             :     const std::vector<std::pair<std::string, std::string>>& headers_examined) {
     194          35 :   std::ostringstream stream;
     195          35 :   stream << "While extracting trace context";
     196          35 :   if (style) {
     197          32 :     stream << " in the " << to_json(*style) << " propagation style";
     198             :   }
     199          35 :   auto it = headers_examined.begin();
     200          35 :   if (it != headers_examined.end()) {
     201          32 :     stream << " from the following headers: [";
     202          32 :     stream << nlohmann::json(it->first + ": " + it->second);
     203          64 :     for (++it; it != headers_examined.end(); ++it) {
     204          32 :       stream << ", ";
     205          32 :       stream << nlohmann::json(it->first + ": " + it->second);
     206             :     }
     207          32 :     stream << "]";
     208             :   }
     209          35 :   stream << ", an error occurred: ";
     210          70 :   return stream.str();
     211          35 : }
     212             : 
     213         113 : AuditedReader::AuditedReader(const DictReader& underlying)
     214         113 :     : underlying(underlying) {}
     215             : 
     216         574 : Optional<StringView> AuditedReader::lookup(StringView key) const {
     217         574 :   auto value = underlying.lookup(key);
     218         574 :   if (value) {
     219         302 :     entries_found.emplace_back(key, *value);
     220             :   }
     221         574 :   return value;
     222             : }
     223             : 
     224           0 : void AuditedReader::visit(
     225             :     const std::function<void(StringView key, StringView value)>& visitor)
     226             :     const {
     227           0 :   underlying.visit([&, this](StringView key, StringView value) {
     228           0 :     entries_found.emplace_back(key, value);
     229           0 :     visitor(key, value);
     230           0 :   });
     231           0 : }
     232             : 
     233          89 : ExtractedData merge(const std::vector<ExtractedData>& contexts) {
     234          89 :   ExtractedData result;
     235             : 
     236          89 :   const auto found = std::find_if(
     237             :       contexts.begin(), contexts.end(),
     238         107 :       [](const ExtractedData& data) { return data.trace_id.has_value(); });
     239             : 
     240          89 :   if (found == contexts.end()) {
     241             :     // Nothing extracted a trace ID. Return the first context that includes a
     242             :     // parent ID, if any, or otherwise just return an empty `ExtractedData`.
     243             :     // The purpose of looking for a parent ID is to allow for the error
     244             :     // "extracted a parent ID without a trace ID," if that's what happened.
     245           5 :     const auto other = std::find_if(
     246             :         contexts.begin(), contexts.end(),
     247           6 :         [](const ExtractedData& data) { return data.parent_id.has_value(); });
     248           5 :     if (other != contexts.end()) {
     249           2 :       result = *other;
     250             :     }
     251           5 :     return result;
     252             :   }
     253             : 
     254             :   // `found` refers to the first extracted context that yielded a trace ID.
     255             :   // This will be our main context.
     256             :   //
     257             :   // If the style of `found` is not W3C, then examine the remaining contexts
     258             :   // for W3C-style tracestate that we might want to include in `result`.
     259          84 :   result = *found;
     260          84 :   if (result.style == PropagationStyle::W3C) {
     261          17 :     return result;
     262             :   }
     263             : 
     264             :   const auto other =
     265          67 :       std::find_if(found + 1, contexts.end(), [&](const ExtractedData& data) {
     266          69 :         return data.style == PropagationStyle::W3C &&
     267          69 :                data.trace_id == found->trace_id;
     268             :       });
     269             : 
     270          67 :   if (other != contexts.end()) {
     271           2 :     result.additional_w3c_tracestate = other->additional_w3c_tracestate;
     272             :     result.additional_datadog_w3c_tracestate =
     273           2 :         other->additional_datadog_w3c_tracestate;
     274           4 :     result.headers_examined.insert(result.headers_examined.end(),
     275           2 :                                    other->headers_examined.begin(),
     276           2 :                                    other->headers_examined.end());
     277             :   }
     278             : 
     279          67 :   return result;
     280           0 : }
     281             : 
     282             : }  // namespace tracing
     283             : }  // namespace datadog

Generated by: LCOV version 1.16