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
|