22
33#include < datadog/telemetry/telemetry.h>
44
5- #include " json_serializer.h"
65#include " parse_util.h"
76#include " string_util.h"
87#include " trace_sampler.h"
@@ -26,69 +25,168 @@ nlohmann::json to_json(const SpanDefaults& defaults) {
2625}
2726
2827using Rules = std::vector<TraceSamplerRule>;
28+ using Tags = std::unordered_map<std::string, std::string>;
29+
30+ Expected<Tags> parse_tags_from_sampling_rules (const nlohmann::json& json_tags) {
31+ assert (json_tags.is_array ());
32+
33+ Tags tags;
34+ for (const auto & json_tag_entry : json_tags) {
35+ auto key = json_tag_entry.find (" key" );
36+ if (key == json_tag_entry.cend () || key->is_string () == false ) {
37+ std::string err_msg =
38+ " Failed to parse tags: the required \" key\" field is either missing "
39+ " or incorrectly formatted. (input: " ;
40+ err_msg += json_tags.dump ();
41+ err_msg += " )" ;
42+ return Error{Error::TRACE_SAMPLING_RULES_INVALID_JSON,
43+ std::move (err_msg)};
44+ }
2945
30- Expected<Rules> parse_trace_sampling_rules (const nlohmann::json& json_rules) {
31- Rules parsed_rules;
46+ auto value = json_tag_entry.find (" value_glob" );
47+ if (value == json_tag_entry.cend () || value->is_string () == false ) {
48+ std::string err_msg =
49+ " Failed to parse tags: the required \" value_glob\" field is either "
50+ " missing or incorrectly formatted. (input: " ;
51+ err_msg += json_tags.dump ();
52+ err_msg += " )" ;
53+ return Error{Error::TRACE_SAMPLING_RULES_INVALID_JSON,
54+ std::move (err_msg)};
55+ }
56+
57+ tags.emplace (*key, *value);
58+ }
59+
60+ return tags;
61+ }
3262
33- std::string type = json_rules.type_name ();
34- if (type != " array" ) {
63+ Expected<TraceSamplerRule> parse_rule (const nlohmann::json& json_rule) {
64+ assert (json_rule.is_object ());
65+
66+ auto make_error = [&json_rule](StringView field_name) {
67+ std::string err_msg = " Failed to parse sampling rule: the required \" " ;
68+ append (err_msg, field_name);
69+ err_msg += " \" field is missing. (input: " ;
70+ err_msg += json_rule.dump ();
71+ err_msg += " )" ;
72+ return Error{Error::TRACE_SAMPLING_RULES_INVALID_JSON, std::move (err_msg)};
73+ };
74+
75+ const auto make_property_error = [&json_rule](StringView property,
76+ const nlohmann::json& value,
77+ StringView expected_type) {
3578 std::string message;
36- return Error{Error::TRACE_SAMPLING_RULES_WRONG_TYPE, std::move (message)};
79+ message += " Rule property \" " ;
80+ append (message, property);
81+ message += " \" should have type \" " ;
82+ append (message, expected_type);
83+ message += " \" , but has type \" " ;
84+ message += value.type_name ();
85+ message += " \" : " ;
86+ message += value.dump ();
87+ message += " in rule " ;
88+ message += json_rule.dump ();
89+ return Error{Error::RULE_PROPERTY_WRONG_TYPE, std::move (message)};
90+ };
91+
92+ TraceSamplerRule rule;
93+
94+ // Required: service, resource, sample_rate, provenance.
95+ if (auto service = json_rule.find (" service" ); service != json_rule.cend ()) {
96+ if (service->is_string () == false ) {
97+ return make_property_error (" service" , *service, " string" );
98+ }
99+ rule.matcher .service = *service;
100+ } else {
101+ return make_error (" service" );
37102 }
38103
39- for (const auto & json_rule : json_rules) {
40- auto matcher = from_json (json_rule);
41- if (auto * error = matcher.if_error ()) {
42- std::string prefix;
43- return error->with_prefix (prefix);
104+ if (auto resource = json_rule.find (" resource" );
105+ resource != json_rule.cend ()) {
106+ if (resource->is_string () == false ) {
107+ return make_property_error (" resource" , *resource, " string" );
44108 }
109+ rule.matcher .resource = *resource;
110+ } else {
111+ return make_error (" resource" );
112+ }
45113
46- TraceSamplerRule rule;
47- rule.matcher = std::move (*matcher);
48-
49- if (auto sample_rate = json_rule.find (" sample_rate" );
50- sample_rate != json_rule.end ()) {
51- type = sample_rate->type_name ();
52- if (type != " number" ) {
53- std::string message;
54- return Error{Error::TRACE_SAMPLING_RULES_SAMPLE_RATE_WRONG_TYPE,
55- std::move (message)};
56- }
114+ if (auto sample_rate = json_rule.find (" sample_rate" );
115+ sample_rate != json_rule.end ()) {
116+ if (sample_rate->is_number () == false ) {
117+ return make_property_error (" sample_rate" , *sample_rate, " number" );
118+ }
57119
58- auto maybe_rate = Rate::from (*sample_rate);
59- if (auto error = maybe_rate.if_error ()) {
60- return *error;
61- }
120+ auto maybe_rate = Rate::from (*sample_rate);
121+ if (auto error = maybe_rate.if_error ()) {
122+ return *error;
123+ }
62124
63- rule.rate = *maybe_rate;
125+ rule.rate = *maybe_rate;
126+ } else {
127+ return make_error (" sample_rate" );
128+ }
129+
130+ if (auto provenance_it = json_rule.find (" provenance" );
131+ provenance_it != json_rule.cend ()) {
132+ if (!provenance_it->is_string ()) {
133+ return make_property_error (" provenance" , *provenance_it, " string" );
134+ }
135+
136+ auto provenance = to_lower (provenance_it->get <StringView>());
137+ if (provenance == " customer" ) {
138+ rule.mechanism = SamplingMechanism::REMOTE_RULE;
139+ } else if (provenance == " dynamic" ) {
140+ rule.mechanism = SamplingMechanism::REMOTE_ADAPTIVE_RULE;
64141 } else {
65- return Error{Error::TRACE_SAMPLING_RULES_INVALID_JSON,
66- " Missing \" sample_rate\" field" };
142+ std::string err_msg = " Failed to parse sampling rule: unknown \" " ;
143+ err_msg += provenance;
144+ err_msg += " \" value. Expected either \" customer\" or \" dynamic\" " ;
145+ return Error{Error::TRACE_SAMPLING_RULES_UNKNOWN_PROPERTY,
146+ std::move (err_msg)};
147+ }
148+ } else {
149+ return make_error (" provenance" );
150+ }
151+
152+ // Parse optional fields: name, tags
153+ if (auto name = json_rule.find (" name" ); name != json_rule.cend ()) {
154+ if (name->is_string () == false ) {
155+ return make_property_error (" name" , *name, " string" );
67156 }
157+ rule.matcher .name = *name;
158+ }
68159
69- if (auto provenance_it = json_rule.find (" provenance" );
70- provenance_it != json_rule.cend ()) {
71- if (!provenance_it->is_string ()) {
72- std::string message;
73- return Error{Error::TRACE_SAMPLING_RULES_SAMPLE_RATE_WRONG_TYPE,
74- std::move (message)};
75- }
160+ if (auto tags = json_rule.find (" tags" ); tags != json_rule.cend ()) {
161+ if (tags->is_array () == false ) {
162+ return make_property_error (" tags" , *tags, " array" );
163+ }
76164
77- auto provenance = to_lower (provenance_it->get <StringView>());
78- if (provenance == " customer" ) {
79- rule.mechanism = SamplingMechanism::REMOTE_RULE;
80- } else if (provenance == " dynamic" ) {
81- rule.mechanism = SamplingMechanism::REMOTE_ADAPTIVE_RULE;
82- } else {
83- return Error{Error::TRACE_SAMPLING_RULES_UNKNOWN_PROPERTY,
84- " Unknown \" provenance\" value" };
85- }
86- } else {
87- return Error{Error::TRACE_SAMPLING_RULES_INVALID_JSON,
88- " Missing \" provenance\" field" };
165+ auto maybe_tags = parse_tags_from_sampling_rules (*tags);
166+ if (auto * error = maybe_tags.if_error ()) {
167+ return *error;
168+ }
169+
170+ rule.matcher .tags = std::move (*maybe_tags);
171+ }
172+
173+ return rule;
174+ }
175+
176+ Expected<Rules> parse_trace_sampling_rules (const nlohmann::json& json_rules) {
177+ if (json_rules.is_array () == false ) {
178+ std::string message;
179+ return Error{Error::TRACE_SAMPLING_RULES_WRONG_TYPE, std::move (message)};
180+ }
181+
182+ Rules parsed_rules;
183+ for (const auto & json_rule : json_rules) {
184+ auto maybe_rule = parse_rule (json_rule);
185+ if (auto error = maybe_rule.if_error ()) {
186+ return *error;
89187 }
90188
91- parsed_rules.emplace_back (std::move (rule ));
189+ parsed_rules.emplace_back (std::move (*maybe_rule ));
92190 }
93191
94192 return parsed_rules;
@@ -215,7 +313,19 @@ std::vector<ConfigMetadata> ConfigManager::apply_update(
215313 rule.rate = *rate;
216314 rule.matcher = catch_all;
217315 rule.mechanism = SamplingMechanism::RULE;
218- rules.emplace (rules.cbegin (), std::move (rule));
316+
317+ // Convention: Catch-all rules should ALWAYS be the last in the list.
318+ // If a catch-all rule already exists, replace it.
319+ // If NOT, add the new one at the end of the rules list.
320+ if (rules.empty ()) {
321+ rules.emplace_back (std::move (rule));
322+ } else {
323+ if (auto & last_rule = rules.back (); last_rule.matcher == catch_all) {
324+ last_rule = rule;
325+ } else {
326+ rules.emplace_back (std::move (rule));
327+ }
328+ }
219329
220330 metadata.emplace_back (std::move (trace_sampling_metadata));
221331 }
0 commit comments