master
c 366 lines 13 KB
Raw
1 // SPDX-License-Identifier: GPL-3.0-or-later
2
3 #include "log2journal.h"
4 #include "libnetdata/json/json-c-parser-inline.h"
5 #include "libnetdata/yaml/yaml.h"
6
7 // ----------------------------------------------------------------------------
8 // yaml configuration file
9
10 #ifdef HAVE_LIBYAML
11
12 // ----------------------------------------------------------------------------
13 // JSON-C based YAML parsing using libnetdata's YAML parser
14
15 static bool log2journal_config_from_json(json_object *jobj, void *data, BUFFER *error) {
16 char path[1024]; path[0] = '\0';
17 LOG_JOB *jb = data;
18
19 // Parse pattern (optional - despite being conceptually required, we handle it gracefully)
20 CLEAN_CHAR_P *pattern = NULL;
21 JSONC_PARSE_TXT2STRDUPZ_OR_ERROR_AND_RETURN(jobj, path, "pattern", pattern, error, JSONC_OPTIONAL);
22 if(pattern) {
23 if(!log_job_pattern_set(jb, pattern, strlen(pattern))) {
24 buffer_sprintf(error, "failed to set pattern");
25 return false;
26 }
27 }
28
29 // Parse prefix (optional)
30 CLEAN_CHAR_P *prefix = NULL;
31 JSONC_PARSE_TXT2STRDUPZ_OR_ERROR_AND_RETURN(jobj, path, "prefix", prefix, error, JSONC_OPTIONAL);
32 if(prefix) {
33 if(!log_job_key_prefix_set(jb, prefix, strlen(prefix))) {
34 buffer_sprintf(error, "failed to set prefix");
35 return false;
36 }
37 }
38
39 // Parse filename injection (optional)
40 JSONC_PARSE_SUBOBJECT(jobj, path, "filename", error, JSONC_STRICT, {
41 CLEAN_CHAR_P *key = NULL;
42 JSONC_PARSE_TXT2STRDUPZ_OR_ERROR_AND_RETURN(jobj, path, "key", key, error, JSONC_REQUIRED);
43 if(key) {
44 if(!log_job_filename_key_set(jb, key, strlen(key))) {
45 buffer_sprintf(error, "failed to set filename key");
46 return false;
47 }
48 }
49 });
50
51 // Parse filter (optional)
52 JSONC_PARSE_SUBOBJECT(jobj, path, "filter", error, JSONC_STRICT, {
53 CLEAN_CHAR_P *include = NULL;
54 JSONC_PARSE_TXT2STRDUPZ_OR_ERROR_AND_RETURN(jobj, path, "include", include, error, JSONC_OPTIONAL);
55 if(include) {
56 if(!log_job_include_pattern_set(jb, include, strlen(include))) {
57 buffer_sprintf(error, "failed to set include pattern");
58 return false;
59 }
60 }
61
62 CLEAN_CHAR_P *exclude = NULL;
63 JSONC_PARSE_TXT2STRDUPZ_OR_ERROR_AND_RETURN(jobj, path, "exclude", exclude, error, JSONC_OPTIONAL);
64 if(exclude) {
65 if(!log_job_exclude_pattern_set(jb, exclude, strlen(exclude))) {
66 buffer_sprintf(error, "failed to set exclude pattern");
67 return false;
68 }
69 }
70 });
71
72 // Parse injections array (optional)
73 JSONC_PARSE_ARRAY(jobj, path, "inject", error, JSONC_STRICT, {
74 size_t i;
75 JSONC_PARSE_ARRAY_ITEM_OBJECT(jobj, path, i, JSONC_REQUIRED, {
76 CLEAN_CHAR_P *key = NULL;
77 CLEAN_CHAR_P *value = NULL;
78 JSONC_PARSE_TXT2STRDUPZ_OR_ERROR_AND_RETURN(jobj, path, "key", key, error, JSONC_REQUIRED);
79 JSONC_PARSE_SCALAR2STRDUPZ_OR_ERROR_AND_RETURN(jobj, path, "value", value, error, JSONC_REQUIRED);
80 if(key && value) {
81 if(!log_job_injection_add(jb, key, strlen(key), value, strlen(value), false)) {
82 buffer_sprintf(error, "failed to add injection for '%s.inject'", path);
83 return false;
84 }
85 }
86 });
87 });
88
89 // Parse rename array (optional)
90 JSONC_PARSE_ARRAY(jobj, path, "rename", error, JSONC_STRICT, {
91 size_t i;
92 JSONC_PARSE_ARRAY_ITEM_OBJECT(jobj, path, i, JSONC_REQUIRED, {
93 CLEAN_CHAR_P *new_key = NULL;
94 CLEAN_CHAR_P *old_key = NULL;
95 JSONC_PARSE_TXT2STRDUPZ_OR_ERROR_AND_RETURN(jobj, path, "new_key", new_key, error, JSONC_REQUIRED);
96 JSONC_PARSE_TXT2STRDUPZ_OR_ERROR_AND_RETURN(jobj, path, "old_key", old_key, error, JSONC_REQUIRED);
97 if(new_key && old_key) {
98 if(!log_job_rename_add(jb, new_key, strlen(new_key), old_key, strlen(old_key))) {
99 buffer_sprintf(error, "failed to add rename for '%s.rename'", path);
100 return false;
101 }
102 }
103 });
104 });
105
106 // Parse rewrite array (optional)
107 JSONC_PARSE_ARRAY(jobj, path, "rewrite", error, JSONC_STRICT, {
108 size_t i;
109 JSONC_PARSE_ARRAY_ITEM_OBJECT(jobj, path, i, JSONC_REQUIRED, {
110 CLEAN_CHAR_P *key = NULL;
111 CLEAN_CHAR_P *match = NULL;
112 CLEAN_CHAR_P *not_empty = NULL;
113 CLEAN_CHAR_P *value = NULL;
114 RW_FLAGS flags = RW_NONE;
115
116 JSONC_PARSE_TXT2STRDUPZ_OR_ERROR_AND_RETURN(jobj, path, "key", key, error, JSONC_REQUIRED);
117 JSONC_PARSE_TXT2STRDUPZ_OR_ERROR_AND_RETURN(jobj, path, "match", match, error, JSONC_OPTIONAL);
118 JSONC_PARSE_TXT2STRDUPZ_OR_ERROR_AND_RETURN(jobj, path, "not_empty", not_empty, error, JSONC_OPTIONAL);
119 JSONC_PARSE_SCALAR2STRDUPZ_OR_ERROR_AND_RETURN(jobj, path, "value", value, error, JSONC_REQUIRED);
120
121 bool stop = true;
122 bool inject = false;
123 JSONC_PARSE_BOOL_OR_ERROR_AND_RETURN(jobj, path, "stop", stop, error, JSONC_OPTIONAL);
124 JSONC_PARSE_BOOL_OR_ERROR_AND_RETURN(jobj, path, "inject", inject, error, JSONC_OPTIONAL);
125
126 if(match) flags |= RW_MATCH_PCRE2;
127 else if(not_empty) flags |= RW_MATCH_NON_EMPTY;
128 if(!stop) flags |= RW_DONT_STOP;
129 if(inject) flags |= RW_INJECT;
130
131 if(key && value) {
132 if(!log_job_rewrite_add(jb, key, flags, match ? match : not_empty, value)) {
133 buffer_sprintf(error, "failed to add rewrite for '%s.rewrite'", path);
134 return false;
135 }
136 }
137 });
138 });
139
140 // Parse unmatched section (optional)
141 JSONC_PARSE_SUBOBJECT(jobj, path, "unmatched", error, JSONC_STRICT, {
142 CLEAN_CHAR_P *key = NULL;
143 JSONC_PARSE_TXT2STRDUPZ_OR_ERROR_AND_RETURN(jobj, path, "key", key, error, JSONC_OPTIONAL);
144 if(key) {
145 hashed_key_set(&jb->unmatched.key, key, strlen(key));
146 }
147
148 // Parse unmatched injections
149 JSONC_PARSE_ARRAY(jobj, path, "inject", error, JSONC_STRICT, {
150 size_t i;
151 JSONC_PARSE_ARRAY_ITEM_OBJECT(jobj, path, i, JSONC_REQUIRED, {
152 CLEAN_CHAR_P *inj_key = NULL;
153 CLEAN_CHAR_P *inj_value = NULL;
154 JSONC_PARSE_TXT2STRDUPZ_OR_ERROR_AND_RETURN(jobj, path, "key", inj_key, error, JSONC_REQUIRED);
155 JSONC_PARSE_SCALAR2STRDUPZ_OR_ERROR_AND_RETURN(jobj, path, "value", inj_value, error, JSONC_REQUIRED);
156 if(inj_key && inj_value) {
157 if(!log_job_injection_add(jb, inj_key, strlen(inj_key), inj_value, strlen(inj_value), true)) {
158 buffer_sprintf(error, "failed to add unmatched injection for '%s.unmatched.inject'", path);
159 return false;
160 }
161 }
162 });
163 });
164 });
165
166 return true;
167 }
168
169 bool yaml_parse_file(const char *config_file_path, LOG_JOB *jb) {
170 if(!config_file_path || !*config_file_path) {
171 l2j_log("yaml configuration filename cannot be empty.");
172 return false;
173 }
174
175 BUFFER *error = buffer_create(0, NULL);
176
177 // Parse YAML to JSON-C using libnetdata's YAML parser with all values as strings
178 struct json_object *json = yaml_parse_filename(config_file_path, error, YAML2JSON_ALL_VALUES_AS_STRINGS);
179 if (!json) {
180 l2j_log("Error parsing YAML file %s: %s", config_file_path, buffer_tostring(error));
181 buffer_free(error);
182 return false;
183 }
184
185 // Parse JSON-C to LOG_JOB structure
186 buffer_flush(error);
187 bool success = log2journal_config_from_json(json, jb, error);
188
189 if(!success) {
190 l2j_log("Error parsing configuration: %s", buffer_tostring(error));
191 }
192
193 json_object_put(json);
194 buffer_free(error);
195
196 return success;
197 }
198
199 bool yaml_parse_config(const char *config_name, LOG_JOB *jb) {
200 char filename[FILENAME_MAX + 1];
201
202 snprintf(filename, sizeof(filename), "%s/%s.yaml", LOG2JOURNAL_CONFIG_PATH, config_name);
203 return yaml_parse_file(filename, jb);
204 }
205
206 #endif // HAVE_LIBYAML
207
208 // ----------------------------------------------------------------------------
209 // printing yaml
210
211 static void yaml_print_multiline_value(const char *s, size_t depth) {
212 if (!s)
213 s = "";
214
215 do {
216 const char* next = strchr(s, '\n');
217 if(next) next++;
218
219 size_t len = next ? (size_t)(next - s) : strlen(s);
220 char *buf = mallocz(len + 1);
221 copy_to_buffer(buf, len + 1, s, len);
222
223 fprintf(stderr, "%.*s%s%s",
224 (int)(depth * 2), " ",
225 buf, next ? "" : "\n");
226
227 freez(buf);
228 s = next;
229 } while(s && *s);
230 }
231
232 static bool needs_quotes_in_yaml(const char *str) {
233 // Lookup table for special YAML characters
234 static bool special_chars[256] = { false };
235 static bool table_initialized = false;
236
237 if (!table_initialized) {
238 // Initialize the lookup table
239 const char *special_chars_str = ":{}[],&*!|>'\"%@`^";
240 for (const char *c = special_chars_str; *c; ++c) {
241 special_chars[(unsigned char)*c] = true;
242 }
243 table_initialized = true;
244 }
245
246 while (*str) {
247 if (special_chars[(unsigned char)*str]) {
248 return true;
249 }
250 str++;
251 }
252 return false;
253 }
254
255 static void yaml_print_node(const char *key, const char *value, size_t depth, bool dash) {
256 if(depth > 10) depth = 10;
257 const char *quote = "'";
258
259 const char *second_line = NULL;
260 if(value && strchr(value, '\n')) {
261 second_line = value;
262 value = "|";
263 quote = "";
264 }
265 else if(!value || !needs_quotes_in_yaml(value))
266 quote = "";
267
268 fprintf(stderr, "%.*s%s%s%s%s%s%s\n",
269 (int)(depth * 2), " ", dash ? "- ": "",
270 key ? key : "", key ? ": " : "",
271 quote, value ? value : "", quote);
272
273 if(second_line) {
274 yaml_print_multiline_value(second_line, depth + 1);
275 }
276 }
277
278 void log_job_configuration_to_yaml(LOG_JOB *jb) {
279 if(jb->pattern)
280 yaml_print_node("pattern", jb->pattern, 0, false);
281
282 if(jb->prefix) {
283 fprintf(stderr, "\n");
284 yaml_print_node("prefix", jb->prefix, 0, false);
285 }
286
287 if(jb->filename.key.key) {
288 fprintf(stderr, "\n");
289 yaml_print_node("filename", NULL, 0, false);
290 yaml_print_node("key", jb->filename.key.key, 1, false);
291 }
292
293 if(jb->filter.include.pattern || jb->filter.exclude.pattern) {
294 fprintf(stderr, "\n");
295 yaml_print_node("filter", NULL, 0, false);
296
297 if(jb->filter.include.pattern)
298 yaml_print_node("include", jb->filter.include.pattern, 1, false);
299
300 if(jb->filter.exclude.pattern)
301 yaml_print_node("exclude", jb->filter.exclude.pattern, 1, false);
302 }
303
304 if(jb->renames.used) {
305 fprintf(stderr, "\n");
306 yaml_print_node("rename", NULL, 0, false);
307
308 for(size_t i = 0; i < jb->renames.used ;i++) {
309 yaml_print_node("new_key", jb->renames.array[i].new_key.key, 1, true);
310 yaml_print_node("old_key", jb->renames.array[i].old_key.key, 2, false);
311 }
312 }
313
314 if(jb->injections.used) {
315 fprintf(stderr, "\n");
316 yaml_print_node("inject", NULL, 0, false);
317
318 for (size_t i = 0; i < jb->injections.used; i++) {
319 yaml_print_node("key", jb->injections.keys[i].key.key, 1, true);
320 yaml_print_node("value", jb->injections.keys[i].value.pattern, 2, false);
321 }
322 }
323
324 if(jb->rewrites.used) {
325 fprintf(stderr, "\n");
326 yaml_print_node("rewrite", NULL, 0, false);
327
328 for(size_t i = 0; i < jb->rewrites.used ;i++) {
329 REWRITE *rw = &jb->rewrites.array[i];
330
331 yaml_print_node("key", rw->key.key, 1, true);
332
333 if(rw->flags & RW_MATCH_PCRE2)
334 yaml_print_node("match", rw->match_pcre2.pattern, 2, false);
335
336 else if(rw->flags & RW_MATCH_NON_EMPTY)
337 yaml_print_node("not_empty", rw->match_non_empty.pattern, 2, false);
338
339 yaml_print_node("value", rw->value.pattern, 2, false);
340
341 if(rw->flags & RW_INJECT)
342 yaml_print_node("inject", "yes", 2, false);
343
344 if(rw->flags & RW_DONT_STOP)
345 yaml_print_node("stop", "no", 2, false);
346 }
347 }
348
349 if(jb->unmatched.key.key || jb->unmatched.injections.used) {
350 fprintf(stderr, "\n");
351 yaml_print_node("unmatched", NULL, 0, false);
352
353 if(jb->unmatched.key.key)
354 yaml_print_node("key", jb->unmatched.key.key, 1, false);
355
356 if(jb->unmatched.injections.used) {
357 fprintf(stderr, "\n");
358 yaml_print_node("inject", NULL, 1, false);
359
360 for (size_t i = 0; i < jb->unmatched.injections.used; i++) {
361 yaml_print_node("key", jb->unmatched.injections.keys[i].key.key, 2, true);
362 yaml_print_node("value", jb->unmatched.injections.keys[i].value.pattern, 3, false);
363 }
364 }
365 }
366 }