Line data Source code
1 : #include "trace_sampler_config.h" 2 : 3 : #include <cmath> 4 : #include <unordered_set> 5 : 6 : #include "environment.h" 7 : #include "json.hpp" 8 : #include "parse_util.h" 9 : 10 : namespace datadog { 11 : namespace tracing { 12 : 13 4 : TraceSamplerConfig::Rule::Rule(const SpanMatcher &base) : SpanMatcher(base) {} 14 : 15 787 : Expected<FinalizedTraceSamplerConfig> finalize_config( 16 : const TraceSamplerConfig &config) { 17 787 : FinalizedTraceSamplerConfig result; 18 : 19 787 : std::vector<TraceSamplerConfig::Rule> rules = config.rules; 20 : 21 787 : if (auto rules_env = lookup(environment::DD_TRACE_SAMPLING_RULES)) { 22 12 : rules.clear(); 23 12 : nlohmann::json json_rules; 24 : try { 25 14 : json_rules = nlohmann::json::parse(*rules_env); 26 2 : } catch (const nlohmann::json::parse_error &error) { 27 2 : std::string message; 28 2 : message += "Unable to parse JSON from "; 29 2 : append(message, name(environment::DD_TRACE_SAMPLING_RULES)); 30 2 : message += " value "; 31 2 : append(message, *rules_env); 32 2 : message += ": "; 33 2 : message += error.what(); 34 4 : return Error{Error::TRACE_SAMPLING_RULES_INVALID_JSON, 35 4 : std::move(message)}; 36 2 : } 37 : 38 10 : std::string type = json_rules.type_name(); 39 10 : if (type != "array") { 40 1 : std::string message; 41 1 : message += "Trace sampling rules must be an array, but "; 42 1 : append(message, name(environment::DD_TRACE_SAMPLING_RULES)); 43 1 : message += " has JSON type \""; 44 1 : message += type; 45 1 : message += "\": "; 46 1 : append(message, *rules_env); 47 1 : return Error{Error::TRACE_SAMPLING_RULES_WRONG_TYPE, std::move(message)}; 48 1 : } 49 : 50 : const std::unordered_set<std::string> allowed_properties{ 51 72 : "service", "name", "resource", "tags", "sample_rate"}; 52 : 53 11 : for (const auto &json_rule : json_rules) { 54 10 : auto matcher = SpanMatcher::from_json(json_rule); 55 10 : if (auto *error = matcher.if_error()) { 56 6 : std::string prefix; 57 6 : prefix += "Unable to create a rule from "; 58 6 : append(prefix, name(environment::DD_TRACE_SAMPLING_RULES)); 59 6 : prefix += " value "; 60 6 : append(prefix, *rules_env); 61 6 : prefix += ": "; 62 6 : return error->with_prefix(prefix); 63 6 : } 64 : 65 4 : TraceSamplerConfig::Rule rule{*matcher}; 66 : 67 4 : auto sample_rate = json_rule.find("sample_rate"); 68 4 : if (sample_rate != json_rule.end()) { 69 2 : type = sample_rate->type_name(); 70 2 : if (type != "number") { 71 1 : std::string message; 72 1 : message += "Unable to parse a rule from "; 73 1 : append(message, name(environment::DD_TRACE_SAMPLING_RULES)); 74 1 : message += " value "; 75 1 : append(message, *rules_env); 76 1 : message += ". The \"sample_rate\" property of the rule "; 77 1 : message += json_rule.dump(); 78 1 : message += " is not a number, but instead has type \""; 79 1 : message += type; 80 1 : message += "\"."; 81 2 : return Error{Error::TRACE_SAMPLING_RULES_SAMPLE_RATE_WRONG_TYPE, 82 2 : std::move(message)}; 83 1 : } 84 1 : rule.sample_rate = *sample_rate; 85 : } 86 : 87 : // Look for unexpected properties. 88 8 : for (const auto &[key, value] : json_rule.items()) { 89 6 : if (allowed_properties.count(key)) { 90 5 : continue; 91 : } 92 1 : std::string message; 93 1 : message += "Unexpected property \""; 94 1 : message += key; 95 1 : message += "\" having value "; 96 1 : message += value.dump(); 97 1 : message += " in trace sampling rule "; 98 1 : message += json_rule.dump(); 99 1 : message += ". Error occurred while parsing "; 100 1 : append(message, name(environment::DD_TRACE_SAMPLING_RULES)); 101 1 : message += ": "; 102 1 : append(message, *rules_env); 103 2 : return Error{Error::TRACE_SAMPLING_RULES_UNKNOWN_PROPERTY, 104 2 : std::move(message)}; 105 5 : } 106 : 107 2 : rules.emplace_back(std::move(rule)); 108 12 : } 109 29 : } 110 : 111 787 : for (const auto &rule : rules) { 112 17 : auto maybe_rate = Rate::from(rule.sample_rate); 113 17 : if (auto *error = maybe_rate.if_error()) { 114 6 : std::string prefix; 115 : prefix += 116 : "Unable to parse sample_rate in trace sampling rule with root span " 117 6 : "pattern "; 118 6 : prefix += rule.to_json().dump(); 119 6 : prefix += ": "; 120 6 : return error->with_prefix(prefix); 121 6 : } 122 : 123 11 : FinalizedTraceSamplerConfig::Rule finalized; 124 11 : static_cast<SpanMatcher &>(finalized) = rule; 125 11 : finalized.sample_rate = *maybe_rate; 126 11 : result.rules.push_back(std::move(finalized)); 127 17 : } 128 : 129 770 : auto sample_rate = config.sample_rate; 130 770 : if (auto sample_rate_env = lookup(environment::DD_TRACE_SAMPLE_RATE)) { 131 12 : auto maybe_sample_rate = parse_double(*sample_rate_env); 132 12 : if (auto *error = maybe_sample_rate.if_error()) { 133 8 : std::string prefix; 134 8 : prefix += "While parsing "; 135 8 : append(prefix, name(environment::DD_TRACE_SAMPLE_RATE)); 136 8 : prefix += ": "; 137 8 : return error->with_prefix(prefix); 138 8 : } 139 4 : sample_rate = *maybe_sample_rate; 140 12 : } 141 : 142 : // If `sample_rate` was specified, then it translates to a "catch-all" rule 143 : // appended to the end of `rules`. First, though, we have to make sure the 144 : // sample rate is valid. 145 762 : if (sample_rate) { 146 59 : auto maybe_rate = Rate::from(*sample_rate); 147 59 : if (auto *error = maybe_rate.if_error()) { 148 4 : return error->with_prefix( 149 2 : "Unable to parse overall sample_rate for trace sampling: "); 150 : } 151 : 152 57 : FinalizedTraceSamplerConfig::Rule catch_all; 153 57 : catch_all.sample_rate = *maybe_rate; 154 57 : result.rules.push_back(std::move(catch_all)); 155 59 : } 156 : 157 760 : auto max_per_second = config.max_per_second; 158 760 : if (auto limit_env = lookup(environment::DD_TRACE_RATE_LIMIT)) { 159 11 : auto maybe_max_per_second = parse_double(*limit_env); 160 11 : if (auto *error = maybe_max_per_second.if_error()) { 161 8 : std::string prefix; 162 8 : prefix += "While parsing "; 163 8 : append(prefix, name(environment::DD_TRACE_RATE_LIMIT)); 164 8 : prefix += ": "; 165 8 : return error->with_prefix(prefix); 166 8 : } 167 3 : max_per_second = *maybe_max_per_second; 168 11 : } 169 : 170 752 : const auto allowed_types = {FP_NORMAL, FP_SUBNORMAL}; 171 1498 : if (!(max_per_second > 0) || 172 746 : std::find(std::begin(allowed_types), std::end(allowed_types), 173 2244 : std::fpclassify(max_per_second)) == std::end(allowed_types)) { 174 7 : std::string message; 175 : message += 176 : "Trace sampling max_per_second must be greater than zero, but the " 177 7 : "following value was given: "; 178 7 : message += std::to_string(config.max_per_second); 179 7 : return Error{Error::MAX_PER_SECOND_OUT_OF_RANGE, std::move(message)}; 180 7 : } 181 745 : result.max_per_second = max_per_second; 182 : 183 745 : return result; 184 787 : } 185 : 186 17 : nlohmann::json to_json(const FinalizedTraceSamplerConfig::Rule &rule) { 187 : // Get the base class's fields, then add our own. 188 17 : auto result = static_cast<const SpanMatcher &>(rule).to_json(); 189 17 : result["sample_rate"] = double(rule.sample_rate); 190 17 : return result; 191 0 : } 192 : 193 : } // namespace tracing 194 : } // namespace datadog