master
c 416 lines 13 KB
Raw
1 // SPDX-License-Identifier: GPL-3.0-or-later
2
3 #include "log2journal.h"
4
5 // ----------------------------------------------------------------------------
6
7 void log_job_init(LOG_JOB *jb) {
8 memset(jb, 0, sizeof(*jb));
9 simple_hashtable_init_KEY(&jb->hashtable, 32);
10 hashed_key_set(&jb->line.key, "LINE", -1);
11 }
12
13 static void simple_hashtable_cleanup_allocated_keys(SIMPLE_HASHTABLE_KEY *ht) {
14 SIMPLE_HASHTABLE_FOREACH_READ_ONLY(ht, sl, _KEY) {
15 HASHED_KEY *k = SIMPLE_HASHTABLE_FOREACH_READ_ONLY_VALUE(sl);
16 if(k && k->flags & HK_HASHTABLE_ALLOCATED) {
17 // the order of these statements is important!
18 simple_hashtable_del_slot_KEY(ht, sl); // remove any references to n
19 hashed_key_cleanup(k); // cleanup the internals of n
20 freez(k); // free n
21 }
22 }
23 }
24
25 void log_job_cleanup(LOG_JOB *jb) {
26 hashed_key_cleanup(&jb->line.key);
27
28 if(jb->prefix) {
29 freez((void *) jb->prefix);
30 jb->prefix = NULL;
31 }
32
33 if(jb->pattern) {
34 freez((void *) jb->pattern);
35 jb->pattern = NULL;
36 }
37
38 for(size_t i = 0; i < jb->injections.used ;i++)
39 injection_cleanup(&jb->injections.keys[i]);
40
41 for(size_t i = 0; i < jb->unmatched.injections.used ;i++)
42 injection_cleanup(&jb->unmatched.injections.keys[i]);
43
44 for(size_t i = 0; i < jb->renames.used ;i++)
45 rename_cleanup(&jb->renames.array[i]);
46
47 for(size_t i = 0; i < jb->rewrites.used; i++)
48 rewrite_cleanup(&jb->rewrites.array[i]);
49
50 search_pattern_cleanup(&jb->filter.include);
51 search_pattern_cleanup(&jb->filter.exclude);
52
53 hashed_key_cleanup(&jb->filename.key);
54 hashed_key_cleanup(&jb->unmatched.key);
55
56 txt_l2j_cleanup(&jb->rewrites.tmp);
57 txt_l2j_cleanup(&jb->filename.current);
58
59 simple_hashtable_cleanup_allocated_keys(&jb->hashtable);
60 simple_hashtable_destroy_KEY(&jb->hashtable);
61
62 // remove references to everything else, to reveal them in valgrind
63 memset(jb, 0, sizeof(*jb));
64 }
65
66 // ----------------------------------------------------------------------------
67
68 bool log_job_filename_key_set(LOG_JOB *jb, const char *key, size_t key_len) {
69 if(!key || !*key) {
70 l2j_log("filename key cannot be empty.");
71 return false;
72 }
73
74 hashed_key_set(&jb->filename.key, key, key_len);
75
76 return true;
77 }
78
79 bool log_job_key_prefix_set(LOG_JOB *jb, const char *prefix, size_t prefix_len) {
80 if(!prefix || !*prefix) {
81 l2j_log("filename key cannot be empty.");
82 return false;
83 }
84
85 if(jb->prefix)
86 freez((char*)jb->prefix);
87
88 jb->prefix = strndupz(prefix, prefix_len);
89
90 return true;
91 }
92
93 bool log_job_pattern_set(LOG_JOB *jb, const char *pattern, size_t pattern_len) {
94 if(!pattern || !*pattern) {
95 l2j_log("filename key cannot be empty.");
96 return false;
97 }
98
99 if(jb->pattern)
100 freez((char*)jb->pattern);
101
102 jb->pattern = strndupz(pattern, pattern_len);
103
104 return true;
105 }
106
107 bool log_job_include_pattern_set(LOG_JOB *jb, const char *pattern, size_t pattern_len) {
108 if(jb->filter.include.re) {
109 l2j_log("FILTER INCLUDE: there is already an include filter set");
110 return false;
111 }
112
113 if(!search_pattern_set(&jb->filter.include, pattern, pattern_len)) {
114 l2j_log("FILTER INCLUDE: failed: %s", jb->filter.include.error.txt);
115 return false;
116 }
117
118 return true;
119 }
120
121 bool log_job_exclude_pattern_set(LOG_JOB *jb, const char *pattern, size_t pattern_len) {
122 if(jb->filter.exclude.re) {
123 l2j_log("FILTER INCLUDE: there is already an exclude filter set");
124 return false;
125 }
126
127 if(!search_pattern_set(&jb->filter.exclude, pattern, pattern_len)) {
128 l2j_log("FILTER EXCLUDE: failed: %s", jb->filter.exclude.error.txt);
129 return false;
130 }
131
132 return true;
133 }
134
135 // ----------------------------------------------------------------------------
136
137 static bool parse_rename(LOG_JOB *jb, const char *param) {
138 // Search for '=' in param
139 const char *equal_sign = strchr(param, '=');
140 if (!equal_sign || equal_sign == param) {
141 l2j_log("Error: Invalid rename format, '=' not found in %s", param);
142 return false;
143 }
144
145 const char *new_key = param;
146 size_t new_key_len = equal_sign - new_key;
147
148 const char *old_key = equal_sign + 1;
149 size_t old_key_len = strlen(old_key);
150
151 return log_job_rename_add(jb, new_key, new_key_len, old_key, old_key_len);
152 }
153
154 static bool is_symbol(char c) {
155 return !isalpha(c) && !isdigit(c) && !iscntrl(c);
156 }
157
158 struct {
159 const char *keyword;
160 int action;
161 RW_FLAGS flag;
162 } rewrite_flags[] = {
163 {"match", 1, RW_MATCH_PCRE2},
164 {"match", 0, RW_MATCH_NON_EMPTY},
165
166 {"regex", 1, RW_MATCH_PCRE2},
167 {"regex", 0, RW_MATCH_NON_EMPTY},
168
169 {"pcre2", 1, RW_MATCH_PCRE2},
170 {"pcre2", 0, RW_MATCH_NON_EMPTY},
171
172 {"non_empty", 1, RW_MATCH_NON_EMPTY},
173 {"non_empty", 0, RW_MATCH_PCRE2},
174
175 {"non-empty", 1, RW_MATCH_NON_EMPTY},
176 {"non-empty", 0, RW_MATCH_PCRE2},
177
178 {"not_empty", 1, RW_MATCH_NON_EMPTY},
179 {"not_empty", 0, RW_MATCH_PCRE2},
180
181 {"not-empty", 1, RW_MATCH_NON_EMPTY},
182 {"not-empty", 0, RW_MATCH_PCRE2},
183
184 {"stop", 0, RW_DONT_STOP},
185 {"no-stop", 1, RW_DONT_STOP},
186 {"no_stop", 1, RW_DONT_STOP},
187 {"dont-stop", 1, RW_DONT_STOP},
188 {"dont_stop", 1, RW_DONT_STOP},
189 {"continue", 1, RW_DONT_STOP},
190 {"inject", 1, RW_INJECT},
191 {"existing", 0, RW_INJECT},
192 };
193
194 RW_FLAGS parse_rewrite_flags(const char *options) {
195 RW_FLAGS flags = RW_MATCH_PCRE2; // Default option
196
197 // Tokenize the input options using ","
198 char *token;
199 char *optionsCopy = strdup(options); // Make a copy to avoid modifying the original
200 token = strtok(optionsCopy, ",");
201
202 while (token != NULL) {
203 // Find the keyword-action mapping
204 bool found = false;
205
206 for (size_t i = 0; i < sizeof(rewrite_flags) / sizeof(rewrite_flags[0]); i++) {
207 if (strcmp(token, rewrite_flags[i].keyword) == 0) {
208 if (rewrite_flags[i].action == 1) {
209 flags |= rewrite_flags[i].flag; // Set the flag
210 } else {
211 flags &= ~rewrite_flags[i].flag; // Unset the flag
212 }
213
214 found = true;
215 }
216 }
217
218 if(!found)
219 l2j_log("Warning: rewrite options '%s' is not understood.", token);
220
221 // Get the next token
222 token = strtok(NULL, ",");
223 }
224
225 free(optionsCopy); // Free the copied string
226
227 return flags;
228 }
229
230
231 static bool parse_rewrite(LOG_JOB *jb, const char *param) {
232 // Search for '=' in param
233 const char *equal_sign = strchr(param, '=');
234 if (!equal_sign || equal_sign == param) {
235 l2j_log("Error: Invalid rewrite format, '=' not found in %s", param);
236 return false;
237 }
238
239 // Get the next character as the separator
240 char separator = *(equal_sign + 1);
241 if (!separator || !is_symbol(separator)) {
242 l2j_log("Error: rewrite separator not found after '=', or is not one of /\\|-# in: %s", param);
243 return false;
244 }
245
246 // Find the next occurrence of the separator
247 const char *second_separator = strchr(equal_sign + 2, separator);
248 if (!second_separator) {
249 l2j_log("Error: rewrite second separator not found in: %s", param);
250 return false;
251 }
252
253 // Check if the search pattern is empty
254 if (equal_sign + 1 == second_separator) {
255 l2j_log("Error: rewrite search pattern is empty in: %s", param);
256 return false;
257 }
258
259 // Check if the replacement pattern is empty
260 if (*(second_separator + 1) == '\0') {
261 l2j_log("Error: rewrite replacement pattern is empty in: %s", param);
262 return false;
263 }
264
265 RW_FLAGS flags = RW_MATCH_PCRE2;
266 const char *third_separator = strchr(second_separator + 1, separator);
267 if(third_separator)
268 flags = parse_rewrite_flags(third_separator + 1);
269
270 // Extract key, search pattern, and replacement pattern
271 char *key = strndupz(param, equal_sign - param);
272 char *search_pattern = strndupz(equal_sign + 2, second_separator - (equal_sign + 2));
273 char *replace_pattern = third_separator ? strndup(second_separator + 1, third_separator - (second_separator + 1)) : strdupz(second_separator + 1);
274
275 if(!*search_pattern)
276 flags &= ~RW_MATCH_PCRE2;
277
278 bool ret = log_job_rewrite_add(jb, key, flags, search_pattern, replace_pattern);
279
280 freez(key);
281 freez(search_pattern);
282 freez(replace_pattern);
283
284 return ret;
285 }
286
287 static bool parse_inject(LOG_JOB *jb, const char *value, bool unmatched) {
288 const char *equal = strchr(value, '=');
289 if (!equal) {
290 l2j_log("Error: injection '%s' does not have an equal sign.", value);
291 return false;
292 }
293
294 const char *key = value;
295 const char *val = equal + 1;
296 log_job_injection_add(jb, key, equal - key, val, strlen(val), unmatched);
297
298 return true;
299 }
300
301 bool log_job_command_line_parse_parameters(LOG_JOB *jb, int argc, char **argv) {
302 for (int i = 1; i < argc; i++) {
303 char *arg = argv[i];
304 if (strcmp(arg, "--help") == 0 || strcmp(arg, "-h") == 0) {
305 log_job_command_line_help(argv[0]);
306 exit(0);
307 }
308 #if defined(NETDATA_DEV_MODE) || defined(NETDATA_INTERNAL_CHECKS)
309 else if(strcmp(arg, "--test") == 0) {
310 // logfmt_test();
311 json_test();
312 exit(1);
313 }
314 #endif
315 else if (strcmp(arg, "--show-config") == 0) {
316 jb->show_config = true;
317 }
318 else {
319 char buffer[1024];
320 char *param = NULL;
321 char *value = NULL;
322
323 char *equal_sign = strchr(arg, '=');
324 if (equal_sign) {
325 copy_to_buffer(buffer, sizeof(buffer), arg, equal_sign - arg);
326 param = buffer;
327 value = equal_sign + 1;
328 }
329 else {
330 param = arg;
331 if (i + 1 < argc) {
332 value = argv[++i];
333 }
334 else {
335 if (!jb->pattern) {
336 log_job_pattern_set(jb, arg, strlen(arg));
337 continue;
338 } else {
339 l2j_log(
340 "Error: Multiple patterns detected. Specify only one pattern. The first is '%s', the second is '%s'",
341 jb->pattern,
342 arg);
343 return false;
344 }
345 }
346 }
347
348 if (strcmp(param, "--filename-key") == 0) {
349 if(!log_job_filename_key_set(jb, value, value ? strlen(value) : 0))
350 return false;
351 }
352 else if (strcmp(param, "--prefix") == 0) {
353 if(!log_job_key_prefix_set(jb, value, value ? strlen(value) : 0))
354 return false;
355 }
356 #ifdef HAVE_LIBYAML
357 else if (strcmp(param, "-f") == 0 || strcmp(param, "--file") == 0) {
358 if (!yaml_parse_file(value, jb))
359 return false;
360 }
361 else if (strcmp(param, "-c") == 0 || strcmp(param, "--config") == 0) {
362 if (!yaml_parse_config(value, jb))
363 return false;
364 }
365 #endif
366 else if (strcmp(param, "--unmatched-key") == 0)
367 hashed_key_set(&jb->unmatched.key, value, -1);
368 else if (strcmp(param, "--inject") == 0) {
369 if (!parse_inject(jb, value, false))
370 return false;
371 }
372 else if (strcmp(param, "--inject-unmatched") == 0) {
373 if (!parse_inject(jb, value, true))
374 return false;
375 }
376 else if (strcmp(param, "--rewrite") == 0) {
377 if (!parse_rewrite(jb, value))
378 return false;
379 }
380 else if (strcmp(param, "--rename") == 0) {
381 if (!parse_rename(jb, value))
382 return false;
383 }
384 else if (strcmp(param, "--include") == 0) {
385 if (!log_job_include_pattern_set(jb, value, strlen(value)))
386 return false;
387 }
388 else if (strcmp(param, "--exclude") == 0) {
389 if (!log_job_exclude_pattern_set(jb, value, strlen(value)))
390 return false;
391 }
392 else {
393 i--;
394 if (!jb->pattern) {
395 log_job_pattern_set(jb, arg, strlen(arg));
396 continue;
397 } else {
398 l2j_log(
399 "Error: Multiple patterns detected. Specify only one pattern. The first is '%s', the second is '%s'",
400 jb->pattern,
401 arg);
402 return false;
403 }
404 }
405 }
406 }
407
408 // Check if a pattern is set and exactly one pattern is specified
409 if (!jb->pattern) {
410 l2j_log("Warning: pattern not specified. Try the default config with: -c default");
411 log_job_command_line_help(argv[0]);
412 return false;
413 }
414
415 return true;
416 }