log2journal-params.c 13 KB

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