11#include " stable_config.h"
22
3- #include < algorithm>
43#include < fstream>
5- #include < sstream>
64#include < string>
75
6+ #include " yaml_parser.h"
7+
88#ifdef _WIN32
99#include < windows.h>
1010// windows.h defines ERROR as a macro which conflicts with our enum.
@@ -15,9 +15,6 @@ namespace datadog {
1515namespace tracing {
1616namespace {
1717
18- // Maximum file size: 256KB.
19- constexpr std::size_t kMaxFileSize = 256 * 1024 ;
20-
2118#ifdef _WIN32
2219
2320std::string get_windows_agent_dir () {
@@ -67,149 +64,6 @@ std::string get_windows_agent_dir() {
6764
6865#endif // _WIN32
6966
70- // Remove leading and trailing whitespace from `s`.
71- std::string trim (const std::string& s) {
72- const auto begin = s.find_first_not_of (" \t\r\n " );
73- if (begin == std::string::npos) return " " ;
74- const auto end = s.find_last_not_of (" \t\r\n " );
75- return s.substr (begin, end - begin + 1 );
76- }
77-
78- // If `s` is surrounded by matching quotes (single or double), remove them and
79- // return the inner content. Otherwise return `s` as-is.
80- std::string unquote (const std::string& s) {
81- if (s.size () >= 2 ) {
82- const char front = s.front ();
83- const char back = s.back ();
84- if ((front == ' "' && back == ' "' ) || (front == ' \' ' && back == ' \' ' )) {
85- return s.substr (1 , s.size () - 2 );
86- }
87- }
88- return s;
89- }
90-
91- // Strip an inline comment from a value string. Handles quoted values so that
92- // a '#' inside quotes is not treated as a comment.
93- std::string strip_inline_comment (const std::string& s) {
94- if (s.empty ()) return s;
95-
96- // If the value starts with a quote, find the closing quote first.
97- if (s[0 ] == ' "' || s[0 ] == ' \' ' ) {
98- const char quote = s[0 ];
99- auto close = s.find (quote, 1 );
100- if (close != std::string::npos) {
101- // Return just the quoted value (anything after closing quote + whitespace
102- // + '#' is comment).
103- return s.substr (0 , close + 1 );
104- }
105- // No closing quote — return as-is (will be treated as a parse issue
106- // elsewhere or kept verbatim).
107- return s;
108- }
109-
110- // Unquoted value: '#' starts a comment.
111- auto pos = s.find (' #' );
112- if (pos != std::string::npos) {
113- auto result = s.substr (0 , pos);
114- // Trim trailing whitespace before the comment.
115- auto end = result.find_last_not_of (" \t " );
116- if (end != std::string::npos) {
117- return result.substr (0 , end + 1 );
118- }
119- return " " ;
120- }
121- return s;
122- }
123-
124- enum class ParseResult { OK , PARSE_ERROR };
125-
126- // Parse a YAML file's contents into a StableConfig.
127- // Returns OK on success (including empty/missing apm_configuration_default).
128- // Returns ERROR on malformed input.
129- ParseResult parse_yaml (const std::string& content, StableConfig& out) {
130- std::istringstream stream (content);
131- std::string line;
132- bool in_apm_config = false ;
133-
134- while (std::getline (stream, line)) {
135- // Remove carriage return if present (Windows line endings).
136- if (!line.empty () && line.back () == ' \r ' ) {
137- line.pop_back ();
138- }
139-
140- // Strip comments from lines that are entirely comments.
141- auto trimmed = trim (line);
142- if (trimmed.empty () || trimmed[0 ] == ' #' ) {
143- continue ;
144- }
145-
146- // Detect indentation to know if we're in a map or at the top level.
147- const auto first_non_space = line.find_first_not_of (" \t " );
148- const bool is_indented = (first_non_space > 0 );
149-
150- if (!is_indented) {
151- // Top-level key.
152- in_apm_config = false ;
153-
154- auto colon_pos = trimmed.find (' :' );
155- if (colon_pos == std::string::npos) {
156- // Malformed line at top level.
157- return ParseResult::PARSE_ERROR ;
158- }
159-
160- auto key = trim (trimmed.substr (0 , colon_pos));
161- auto value = trim (trimmed.substr (colon_pos + 1 ));
162-
163- // Strip inline comment from value.
164- value = strip_inline_comment (value);
165- value = trim (value);
166-
167- if (key == " apm_configuration_default" ) {
168- in_apm_config = true ;
169- // The value after the colon should be empty (map follows on next
170- // lines). If it's not empty, that's malformed for our purposes.
171- if (!value.empty ()) {
172- return ParseResult::PARSE_ERROR ;
173- }
174- } else if (key == " config_id" ) {
175- out.config_id = unquote (value);
176- }
177- // Unknown top-level keys are silently ignored.
178- } else if (in_apm_config) {
179- // Indented line under apm_configuration_default.
180- auto colon_pos = trimmed.find (' :' );
181- if (colon_pos == std::string::npos) {
182- // Malformed entry.
183- return ParseResult::PARSE_ERROR ;
184- }
185-
186- auto key = trim (trimmed.substr (0 , colon_pos));
187- auto value = trim (trimmed.substr (colon_pos + 1 ));
188-
189- // Strip inline comment.
190- value = strip_inline_comment (value);
191- value = trim (value);
192-
193- // Check for non-scalar values (flow sequences/mappings).
194- if (!value.empty () && (value[0 ] == ' [' || value[0 ] == ' {' ||
195- value[0 ] == ' |' || value[0 ] == ' >' )) {
196- // Skip non-scalar values silently (as per spec: "log warning, skip
197- // that entry").
198- continue ;
199- }
200-
201- // Unquote the value.
202- value = unquote (value);
203-
204- // Store the key-value pair. Last value wins for duplicates.
205- out.values [key] = value;
206- }
207- // Indented lines under unknown top-level keys are silently ignored.
208- }
209-
210- return ParseResult::OK ;
211- }
212-
21367// Read a file and parse it into a StableConfig. Logs warnings on errors.
21468// Returns an empty StableConfig if the file doesn't exist or can't be read.
21569StableConfig load_one (const std::string& path, Logger& logger) {
@@ -231,7 +85,7 @@ StableConfig load_one(const std::string& path, Logger& logger) {
23185 return result;
23286 }
23387
234- if (static_cast <std::size_t >(size) > kMaxFileSize ) {
88+ if (static_cast <std::size_t >(size) > kMaxYamlFileSize ) {
23589 logger.log_error ([&path](std::ostream& log) {
23690 log << " Stable config: file " << path
23791 << " exceeds 256KB size limit; skipping." ;
@@ -248,13 +102,16 @@ StableConfig load_one(const std::string& path, Logger& logger) {
248102 return result;
249103 }
250104
251- if (parse_yaml (content, result) != ParseResult::OK ) {
105+ YamlParseResult parsed;
106+ if (parse_yaml (content, parsed) != YamlParseStatus::OK ) {
252107 logger.log_error ([&path](std::ostream& log) {
253108 log << " Stable config: malformed YAML in " << path << " ; skipping." ;
254109 });
255110 return {}; // Return empty config on parse error.
256111 }
257112
113+ result.config_id = std::move (parsed.config_id );
114+ result.values = std::move (parsed.values );
258115 return result;
259116}
260117
0 commit comments