Line data Source code
1 : #include "span_sampler_config.h"
2 :
3 : #include <cmath>
4 : #include <fstream>
5 : #include <sstream>
6 : #include <unordered_set>
7 :
8 : #include "environment.h"
9 : #include "expected.h"
10 : #include "json.hpp"
11 : #include "logger.h"
12 :
13 : namespace datadog {
14 : namespace tracing {
15 : namespace {
16 :
17 : // `env_var` is the name of the environment variable from which `rules_raw` was
18 : // obtained. It's used for error messages.
19 16 : Expected<std::vector<SpanSamplerConfig::Rule>> parse_rules(StringView rules_raw,
20 : StringView env_var) {
21 16 : std::vector<SpanSamplerConfig::Rule> rules;
22 16 : nlohmann::json json_rules;
23 :
24 : try {
25 19 : json_rules = nlohmann::json::parse(rules_raw);
26 3 : } catch (const nlohmann::json::parse_error &error) {
27 3 : std::string message;
28 3 : message += "Unable to parse JSON from ";
29 3 : append(message, env_var);
30 3 : message += " value ";
31 3 : append(message, rules_raw);
32 3 : message += ": ";
33 3 : message += error.what();
34 3 : return Error{Error::SPAN_SAMPLING_RULES_INVALID_JSON, std::move(message)};
35 3 : }
36 :
37 13 : std::string type = json_rules.type_name();
38 13 : if (type != "array") {
39 1 : std::string message;
40 1 : message += "Trace sampling rules must be an array, but JSON in ";
41 1 : append(message, env_var);
42 1 : message += " has type \"";
43 1 : message += type;
44 1 : message += "\": ";
45 1 : append(message, rules_raw);
46 1 : return Error{Error::SPAN_SAMPLING_RULES_WRONG_TYPE, std::move(message)};
47 1 : }
48 :
49 : const std::unordered_set<std::string> allowed_properties{
50 108 : "service", "name", "resource", "tags", "sample_rate", "max_per_second"};
51 :
52 19 : for (const auto &json_rule : json_rules) {
53 16 : auto matcher = SpanMatcher::from_json(json_rule);
54 16 : if (auto *error = matcher.if_error()) {
55 6 : std::string prefix;
56 6 : prefix += "Unable to create a rule from ";
57 6 : append(prefix, env_var);
58 6 : prefix += " JSON ";
59 6 : append(prefix, rules_raw);
60 6 : prefix += ": ";
61 6 : return error->with_prefix(prefix);
62 6 : }
63 :
64 10 : SpanSamplerConfig::Rule rule{*matcher};
65 :
66 10 : auto sample_rate = json_rule.find("sample_rate");
67 10 : if (sample_rate != json_rule.end()) {
68 4 : type = sample_rate->type_name();
69 4 : if (type != "number") {
70 1 : std::string message;
71 1 : message += "Unable to parse a rule from ";
72 1 : append(message, env_var);
73 1 : message += " JSON ";
74 1 : append(message, rules_raw);
75 1 : message += ". The \"sample_rate\" property of the rule ";
76 1 : message += json_rule.dump();
77 1 : message += " is not a number, but instead has type \"";
78 1 : message += type;
79 1 : message += "\".";
80 2 : return Error{Error::SPAN_SAMPLING_RULES_SAMPLE_RATE_WRONG_TYPE,
81 2 : std::move(message)};
82 1 : }
83 3 : rule.sample_rate = *sample_rate;
84 : }
85 :
86 9 : auto max_per_second = json_rule.find("max_per_second");
87 9 : if (max_per_second != json_rule.end()) {
88 6 : type = max_per_second->type_name();
89 6 : if (type != "number") {
90 1 : std::string message;
91 1 : message += "Unable to parse a rule from ";
92 1 : append(message, env_var);
93 1 : message += " JSON ";
94 1 : append(message, rules_raw);
95 1 : message += ". The \"max_per_second\" property of the rule ";
96 1 : message += json_rule.dump();
97 1 : message += " is not a number, but instead has type \"";
98 1 : message += type;
99 1 : message += "\".";
100 2 : return Error{Error::SPAN_SAMPLING_RULES_MAX_PER_SECOND_WRONG_TYPE,
101 2 : std::move(message)};
102 1 : }
103 5 : rule.max_per_second = *max_per_second;
104 : }
105 :
106 : // Look for unexpected properties.
107 20 : for (const auto &[key, value] : json_rule.items()) {
108 13 : if (allowed_properties.count(key)) {
109 12 : continue;
110 : }
111 1 : std::string message;
112 1 : message += "Unexpected property \"";
113 1 : message += key;
114 1 : message += "\" having value ";
115 1 : message += value.dump();
116 1 : message += " in trace sampling rule ";
117 1 : message += json_rule.dump();
118 1 : message += ". Error occurred while parsing from ";
119 1 : append(message, env_var);
120 1 : message += ": ";
121 1 : append(message, rules_raw);
122 2 : return Error{Error::SPAN_SAMPLING_RULES_UNKNOWN_PROPERTY,
123 2 : std::move(message)};
124 10 : }
125 :
126 7 : rules.emplace_back(std::move(rule));
127 19 : }
128 :
129 3 : return rules;
130 16 : }
131 :
132 : } // namespace
133 :
134 10 : SpanSamplerConfig::Rule::Rule(const SpanMatcher &base) : SpanMatcher(base) {}
135 :
136 743 : Expected<FinalizedSpanSamplerConfig> finalize_config(
137 : const SpanSamplerConfig &config, Logger &logger) {
138 743 : FinalizedSpanSamplerConfig result;
139 :
140 743 : std::vector<SpanSamplerConfig::Rule> rules = config.rules;
141 :
142 743 : auto rules_env = lookup(environment::DD_SPAN_SAMPLING_RULES);
143 743 : if (rules_env) {
144 : auto maybe_rules =
145 14 : parse_rules(*rules_env, name(environment::DD_SPAN_SAMPLING_RULES));
146 14 : if (auto *error = maybe_rules.if_error()) {
147 12 : return std::move(*error);
148 : }
149 2 : rules = std::move(*maybe_rules);
150 14 : }
151 :
152 731 : if (auto file_env = lookup(environment::DD_SPAN_SAMPLING_RULES_FILE)) {
153 4 : if (rules_env) {
154 : const auto rules_file_name =
155 1 : name(environment::DD_SPAN_SAMPLING_RULES_FILE);
156 1 : const auto rules_name = name(environment::DD_SPAN_SAMPLING_RULES);
157 1 : std::string message;
158 1 : append(message, rules_file_name);
159 1 : message += " is overridden by ";
160 1 : append(message, rules_name);
161 1 : message += ". Since both are set, ";
162 1 : append(message, rules_name);
163 1 : message += " takes precedence, and ";
164 1 : append(message, rules_file_name);
165 1 : message += " will be ignored.";
166 1 : logger.log_error(message);
167 1 : } else {
168 3 : const auto span_rules_file = std::string(*file_env);
169 :
170 1 : const auto file_error = [&](const char *operation) {
171 1 : std::string message;
172 1 : message += "Unable to ";
173 1 : message += operation;
174 1 : message += " file \"";
175 1 : message += span_rules_file;
176 1 : message += "\" specified as value of environment variable ";
177 1 : append(message, name(environment::DD_SPAN_SAMPLING_RULES_FILE));
178 :
179 2 : return Error{Error::SPAN_SAMPLING_RULES_FILE_IO, std::move(message)};
180 1 : };
181 :
182 3 : std::ifstream file(span_rules_file);
183 3 : if (!file) {
184 1 : return file_error("open");
185 : }
186 :
187 2 : std::ostringstream rules_stream;
188 2 : rules_stream << file.rdbuf();
189 2 : if (!file) {
190 0 : return file_error("read");
191 : }
192 :
193 : auto maybe_rules = parse_rules(
194 2 : rules_stream.str(), name(environment::DD_SPAN_SAMPLING_RULES_FILE));
195 2 : if (auto *error = maybe_rules.if_error()) {
196 1 : std::string prefix;
197 1 : prefix += "With ";
198 1 : append(prefix, name(environment::DD_SPAN_SAMPLING_RULES_FILE));
199 1 : prefix += '=';
200 1 : append(prefix, *file_env);
201 1 : prefix += ": ";
202 1 : return error->with_prefix(prefix);
203 1 : }
204 :
205 1 : rules = std::move(*maybe_rules);
206 7 : }
207 : }
208 :
209 750 : for (const auto &rule : rules) {
210 32 : auto maybe_rate = Rate::from(rule.sample_rate);
211 32 : if (auto *error = maybe_rate.if_error()) {
212 6 : std::string prefix;
213 : prefix +=
214 : "Unable to parse sample_rate in span sampling rule with span "
215 6 : "pattern ";
216 6 : prefix += rule.to_json().dump();
217 6 : prefix += ": ";
218 6 : return error->with_prefix(prefix);
219 6 : }
220 :
221 26 : const auto allowed_types = {FP_NORMAL, FP_SUBNORMAL};
222 38 : if (rule.max_per_second &&
223 20 : (!(*rule.max_per_second > 0) ||
224 8 : std::find(std::begin(allowed_types), std::end(allowed_types),
225 34 : std::fpclassify(*rule.max_per_second)) ==
226 34 : std::end(allowed_types))) {
227 5 : std::string message;
228 5 : message += "Span sampling rule with pattern ";
229 5 : message += rule.to_json().dump();
230 : message +=
231 : " should have a max_per_second value greater than zero, but the "
232 5 : "following value was given: ";
233 5 : message += std::to_string(*rule.max_per_second);
234 5 : return Error{Error::MAX_PER_SECOND_OUT_OF_RANGE, std::move(message)};
235 5 : }
236 :
237 21 : FinalizedSpanSamplerConfig::Rule finalized;
238 21 : static_cast<SpanMatcher &>(finalized) = rule;
239 21 : finalized.sample_rate = *maybe_rate;
240 21 : finalized.max_per_second = rule.max_per_second;
241 21 : result.rules.push_back(std::move(finalized));
242 32 : }
243 :
244 718 : return result;
245 743 : }
246 :
247 0 : nlohmann::json to_json(const FinalizedSpanSamplerConfig::Rule &rule) {
248 : // Get the base class's fields, then add our own.
249 0 : auto result = static_cast<const SpanMatcher &>(rule).to_json();
250 0 : result["sample_rate"] = double(rule.sample_rate);
251 0 : if (rule.max_per_second) {
252 0 : result["max_per_second"] = *rule.max_per_second;
253 : }
254 :
255 0 : return result;
256 0 : }
257 :
258 : } // namespace tracing
259 : } // namespace datadog
|