systemd-journal.c 27 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699
  1. // SPDX-License-Identifier: GPL-3.0-or-later
  2. /*
  3. * netdata systemd-journal.plugin
  4. * Copyright (C) 2023 Netdata Inc.
  5. * GPL v3+
  6. */
  7. // TODO - 1) MARKDOC 2) HELP TEXT
  8. #include "collectors/all.h"
  9. #include "libnetdata/libnetdata.h"
  10. #include "libnetdata/required_dummies.h"
  11. #include <systemd/sd-journal.h>
  12. #include <syslog.h>
  13. #define FACET_MAX_VALUE_LENGTH 8192
  14. #define SYSTEMD_JOURNAL_FUNCTION_DESCRIPTION "View, search and analyze systemd journal entries."
  15. #define SYSTEMD_JOURNAL_FUNCTION_NAME "systemd-journal"
  16. #define SYSTEMD_JOURNAL_DEFAULT_TIMEOUT 30
  17. #define SYSTEMD_JOURNAL_MAX_PARAMS 100
  18. #define SYSTEMD_JOURNAL_DEFAULT_QUERY_DURATION (3 * 3600)
  19. #define SYSTEMD_JOURNAL_DEFAULT_ITEMS_PER_QUERY 200
  20. #define JOURNAL_PARAMETER_HELP "help"
  21. #define JOURNAL_PARAMETER_AFTER "after"
  22. #define JOURNAL_PARAMETER_BEFORE "before"
  23. #define JOURNAL_PARAMETER_ANCHOR "anchor"
  24. #define JOURNAL_PARAMETER_LAST "last"
  25. #define JOURNAL_PARAMETER_QUERY "query"
  26. #define JOURNAL_PARAMETER_FACETS "facets"
  27. #define JOURNAL_PARAMETER_HISTOGRAM "histogram"
  28. #define JOURNAL_PARAMETER_DIRECTION "direction"
  29. #define JOURNAL_PARAMETER_IF_MODIFIED_SINCE "if_modified_since"
  30. #define JOURNAL_PARAMETER_SOURCE "source"
  31. #define JOURNAL_PARAMETER_INFO "info"
  32. #define SYSTEMD_ALWAYS_VISIBLE_KEYS NULL
  33. #define SYSTEMD_KEYS_EXCLUDED_FROM_FACETS NULL
  34. #define SYSTEMD_KEYS_INCLUDED_IN_FACETS \
  35. "_TRANSPORT" \
  36. "|SYSLOG_IDENTIFIER" \
  37. "|SYSLOG_FACILITY" \
  38. "|PRIORITY" \
  39. "|_UID" \
  40. "|_GID" \
  41. "|_SYSTEMD_UNIT" \
  42. "|_SYSTEMD_SLICE" \
  43. "|_COMM" \
  44. "|UNIT" \
  45. "|CONTAINER_NAME" \
  46. "|IMAGE_NAME" \
  47. ""
  48. static netdata_mutex_t mutex = NETDATA_MUTEX_INITIALIZER;
  49. static bool plugin_should_exit = false;
  50. DICTIONARY *uids = NULL;
  51. DICTIONARY *gids = NULL;
  52. // ----------------------------------------------------------------------------
  53. int systemd_journal_query(BUFFER *wb, FACETS *facets, usec_t after_ut, usec_t before_ut, usec_t if_modified_since, usec_t stop_monotonic_ut) {
  54. sd_journal *j = NULL;
  55. int r;
  56. if(*netdata_configured_host_prefix) {
  57. #ifdef HAVE_SD_JOURNAL_OS_ROOT
  58. // Give our host prefix to systemd journal
  59. r = sd_journal_open_directory(&j, netdata_configured_host_prefix, SD_JOURNAL_OS_ROOT);
  60. #else
  61. char buf[FILENAME_MAX + 1];
  62. snprintfz(buf, FILENAME_MAX, "%s/var/log/journal", netdata_configured_host_prefix);
  63. r = sd_journal_open_directory(&j, buf, 0);
  64. #endif
  65. }
  66. else {
  67. // Open the system journal for reading
  68. r = sd_journal_open(&j, 0);
  69. }
  70. if (r < 0) {
  71. netdata_log_error("SYSTEMD-JOURNAL: Failed to open SystemD Journal, with error %d", r);
  72. return HTTP_RESP_INTERNAL_SERVER_ERROR;
  73. }
  74. facets_rows_begin(facets);
  75. uint64_t first_msg_ut = 0;
  76. bool timed_out = false;
  77. size_t row_counter = 0;
  78. sd_journal_seek_realtime_usec(j, before_ut);
  79. SD_JOURNAL_FOREACH_BACKWARDS(j) {
  80. row_counter++;
  81. uint64_t msg_ut;
  82. sd_journal_get_realtime_usec(j, &msg_ut);
  83. if(unlikely(!first_msg_ut)) {
  84. if(msg_ut == if_modified_since) {
  85. sd_journal_close(j);
  86. return HTTP_RESP_NOT_MODIFIED;
  87. }
  88. first_msg_ut = msg_ut;
  89. }
  90. if (msg_ut < after_ut)
  91. break;
  92. const void *data;
  93. size_t length;
  94. SD_JOURNAL_FOREACH_DATA(j, data, length) {
  95. const char *key = data;
  96. const char *equal = strchr(key, '=');
  97. if(unlikely(!equal))
  98. continue;
  99. const char *value = ++equal;
  100. size_t key_length = value - key; // including '\0'
  101. char key_copy[key_length];
  102. memcpy(key_copy, key, key_length - 1);
  103. key_copy[key_length - 1] = '\0';
  104. size_t value_length = length - key_length; // without '\0'
  105. facets_add_key_value_length(facets, key_copy, value, value_length <= FACET_MAX_VALUE_LENGTH ? value_length : FACET_MAX_VALUE_LENGTH);
  106. }
  107. facets_row_finished(facets, msg_ut);
  108. if((row_counter % 100) == 0 && now_monotonic_usec() > stop_monotonic_ut) {
  109. timed_out = true;
  110. break;
  111. }
  112. }
  113. sd_journal_close(j);
  114. buffer_json_member_add_uint64(wb, "status", HTTP_RESP_OK);
  115. buffer_json_member_add_boolean(wb, "partial", timed_out);
  116. buffer_json_member_add_string(wb, "type", "table");
  117. buffer_json_member_add_time_t(wb, "update_every", 1);
  118. buffer_json_member_add_string(wb, "help", SYSTEMD_JOURNAL_FUNCTION_DESCRIPTION);
  119. buffer_json_member_add_uint64(wb, "last_modified", first_msg_ut);
  120. facets_report(facets, wb);
  121. buffer_json_member_add_time_t(wb, "expires", now_realtime_sec());
  122. buffer_json_finalize(wb);
  123. return HTTP_RESP_OK;
  124. }
  125. static void systemd_journal_function_help(const char *transaction) {
  126. pluginsd_function_result_begin_to_stdout(transaction, HTTP_RESP_OK, "text/plain", now_realtime_sec() + 3600);
  127. fprintf(stdout,
  128. "%s / %s\n"
  129. "\n"
  130. "%s\n"
  131. "\n"
  132. "The following filters are supported:\n"
  133. "\n"
  134. " help\n"
  135. " Shows this help message.\n"
  136. "\n"
  137. " before:TIMESTAMP\n"
  138. " Absolute or relative (to now) timestamp in seconds, to start the query.\n"
  139. " The query is always executed from the most recent to the oldest log entry.\n"
  140. " If not given the default is: now.\n"
  141. "\n"
  142. " after:TIMESTAMP\n"
  143. " Absolute or relative (to `before`) timestamp in seconds, to end the query.\n"
  144. " If not given, the default is %d.\n"
  145. "\n"
  146. " last:ITEMS\n"
  147. " The number of items to return.\n"
  148. " The default is %d.\n"
  149. "\n"
  150. " anchor:NUMBER\n"
  151. " The `timestamp` of the item last received, to return log entries after that.\n"
  152. " If not given, the query will return the top `ITEMS` from the most recent.\n"
  153. "\n"
  154. " facet_id:value_id1,value_id2,value_id3,...\n"
  155. " Apply filters to the query, based on the facet IDs returned.\n"
  156. " Each `facet_id` can be given once, but multiple `facet_ids` can be given.\n"
  157. "\n"
  158. "Filters can be combined. Each filter can be given only one time.\n"
  159. , program_name
  160. , SYSTEMD_JOURNAL_FUNCTION_NAME
  161. , SYSTEMD_JOURNAL_FUNCTION_DESCRIPTION
  162. , -SYSTEMD_JOURNAL_DEFAULT_QUERY_DURATION
  163. , SYSTEMD_JOURNAL_DEFAULT_ITEMS_PER_QUERY
  164. );
  165. pluginsd_function_result_end_to_stdout();
  166. }
  167. static const char *syslog_facility_to_name(int facility) {
  168. switch (facility) {
  169. case LOG_FAC(LOG_KERN): return "kern";
  170. case LOG_FAC(LOG_USER): return "user";
  171. case LOG_FAC(LOG_MAIL): return "mail";
  172. case LOG_FAC(LOG_DAEMON): return "daemon";
  173. case LOG_FAC(LOG_AUTH): return "auth";
  174. case LOG_FAC(LOG_SYSLOG): return "syslog";
  175. case LOG_FAC(LOG_LPR): return "lpr";
  176. case LOG_FAC(LOG_NEWS): return "news";
  177. case LOG_FAC(LOG_UUCP): return "uucp";
  178. case LOG_FAC(LOG_CRON): return "cron";
  179. case LOG_FAC(LOG_AUTHPRIV): return "authpriv";
  180. case LOG_FAC(LOG_FTP): return "ftp";
  181. case LOG_FAC(LOG_LOCAL0): return "local0";
  182. case LOG_FAC(LOG_LOCAL1): return "local1";
  183. case LOG_FAC(LOG_LOCAL2): return "local2";
  184. case LOG_FAC(LOG_LOCAL3): return "local3";
  185. case LOG_FAC(LOG_LOCAL4): return "local4";
  186. case LOG_FAC(LOG_LOCAL5): return "local5";
  187. case LOG_FAC(LOG_LOCAL6): return "local6";
  188. case LOG_FAC(LOG_LOCAL7): return "local7";
  189. default: return NULL;
  190. }
  191. }
  192. static const char *syslog_priority_to_name(int priority) {
  193. switch (priority) {
  194. case LOG_ALERT: return "alert";
  195. case LOG_CRIT: return "critical";
  196. case LOG_DEBUG: return "debug";
  197. case LOG_EMERG: return "panic";
  198. case LOG_ERR: return "error";
  199. case LOG_INFO: return "info";
  200. case LOG_NOTICE: return "notice";
  201. case LOG_WARNING: return "warning";
  202. default: return NULL;
  203. }
  204. }
  205. static char *uid_to_username(uid_t uid, char *buffer, size_t buffer_size) {
  206. struct passwd pw, *result;
  207. char tmp[1024 + 1];
  208. if (getpwuid_r(uid, &pw, tmp, 1024, &result) != 0 || result == NULL)
  209. return NULL;
  210. snprintfz(buffer, buffer_size - 1, "%u (%s)", uid, pw.pw_name);
  211. return buffer;
  212. }
  213. static char *gid_to_groupname(gid_t gid, char* buffer, size_t buffer_size) {
  214. struct group grp, *result;
  215. char tmp[1024 + 1];
  216. if (getgrgid_r(gid, &grp, tmp, 1024, &result) != 0 || result == NULL)
  217. return NULL;
  218. snprintfz(buffer, buffer_size - 1, "%u (%s)", gid, grp.gr_name);
  219. return buffer;
  220. }
  221. static void systemd_journal_transform_syslog_facility(FACETS *facets __maybe_unused, BUFFER *wb, void *data __maybe_unused) {
  222. const char *v = buffer_tostring(wb);
  223. if(*v && isdigit(*v)) {
  224. int facility = str2i(buffer_tostring(wb));
  225. const char *name = syslog_facility_to_name(facility);
  226. if (name) {
  227. buffer_flush(wb);
  228. buffer_strcat(wb, name);
  229. }
  230. }
  231. }
  232. static void systemd_journal_transform_priority(FACETS *facets __maybe_unused, BUFFER *wb, void *data __maybe_unused) {
  233. const char *v = buffer_tostring(wb);
  234. if(*v && isdigit(*v)) {
  235. int priority = str2i(buffer_tostring(wb));
  236. const char *name = syslog_priority_to_name(priority);
  237. if (name) {
  238. buffer_flush(wb);
  239. buffer_strcat(wb, name);
  240. }
  241. }
  242. }
  243. static void systemd_journal_transform_uid(FACETS *facets __maybe_unused, BUFFER *wb, void *data) {
  244. DICTIONARY *cache = data;
  245. const char *v = buffer_tostring(wb);
  246. if(*v && isdigit(*v)) {
  247. const char *sv = dictionary_get(cache, v);
  248. if(!sv) {
  249. char buf[1024 + 1];
  250. int uid = str2i(buffer_tostring(wb));
  251. const char *name = uid_to_username(uid, buf, 1024);
  252. if (!name)
  253. name = v;
  254. sv = dictionary_set(cache, v, (void *)name, strlen(name) + 1);
  255. }
  256. buffer_flush(wb);
  257. buffer_strcat(wb, sv);
  258. }
  259. }
  260. static void systemd_journal_transform_gid(FACETS *facets __maybe_unused, BUFFER *wb, void *data) {
  261. DICTIONARY *cache = data;
  262. const char *v = buffer_tostring(wb);
  263. if(*v && isdigit(*v)) {
  264. const char *sv = dictionary_get(cache, v);
  265. if(!sv) {
  266. char buf[1024 + 1];
  267. int gid = str2i(buffer_tostring(wb));
  268. const char *name = gid_to_groupname(gid, buf, 1024);
  269. if (!name)
  270. name = v;
  271. sv = dictionary_set(cache, v, (void *)name, strlen(name) + 1);
  272. }
  273. buffer_flush(wb);
  274. buffer_strcat(wb, sv);
  275. }
  276. }
  277. static void systemd_journal_dynamic_row_id(FACETS *facets __maybe_unused, BUFFER *json_array, FACET_ROW_KEY_VALUE *rkv, FACET_ROW *row, void *data __maybe_unused) {
  278. FACET_ROW_KEY_VALUE *pid_rkv = dictionary_get(row->dict, "_PID");
  279. const char *pid = pid_rkv ? buffer_tostring(pid_rkv->wb) : FACET_VALUE_UNSET;
  280. FACET_ROW_KEY_VALUE *syslog_identifier_rkv = dictionary_get(row->dict, "SYSLOG_IDENTIFIER");
  281. const char *identifier = syslog_identifier_rkv ? buffer_tostring(syslog_identifier_rkv->wb) : FACET_VALUE_UNSET;
  282. if(strcmp(identifier, FACET_VALUE_UNSET) == 0) {
  283. FACET_ROW_KEY_VALUE *comm_rkv = dictionary_get(row->dict, "_COMM");
  284. identifier = comm_rkv ? buffer_tostring(comm_rkv->wb) : FACET_VALUE_UNSET;
  285. }
  286. buffer_flush(rkv->wb);
  287. if(strcmp(pid, FACET_VALUE_UNSET) == 0)
  288. buffer_strcat(rkv->wb, identifier);
  289. else
  290. buffer_sprintf(rkv->wb, "%s[%s]", identifier, pid);
  291. buffer_json_add_array_item_string(json_array, buffer_tostring(rkv->wb));
  292. }
  293. static void function_systemd_journal(const char *transaction, char *function, char *line_buffer __maybe_unused, int line_max __maybe_unused, int timeout __maybe_unused) {
  294. BUFFER *wb = buffer_create(0, NULL);
  295. buffer_flush(wb);
  296. buffer_json_initialize(wb, "\"", "\"", 0, true, BUFFER_JSON_OPTIONS_NEWLINE_ON_ARRAY_ITEMS);
  297. FACETS *facets = facets_create(50, FACETS_OPTION_ALL_KEYS_FTS,
  298. SYSTEMD_ALWAYS_VISIBLE_KEYS,
  299. SYSTEMD_KEYS_INCLUDED_IN_FACETS,
  300. SYSTEMD_KEYS_EXCLUDED_FROM_FACETS);
  301. facets_accepted_param(facets, JOURNAL_PARAMETER_INFO);
  302. facets_accepted_param(facets, JOURNAL_PARAMETER_SOURCE);
  303. facets_accepted_param(facets, JOURNAL_PARAMETER_AFTER);
  304. facets_accepted_param(facets, JOURNAL_PARAMETER_BEFORE);
  305. facets_accepted_param(facets, JOURNAL_PARAMETER_ANCHOR);
  306. facets_accepted_param(facets, JOURNAL_PARAMETER_DIRECTION);
  307. facets_accepted_param(facets, JOURNAL_PARAMETER_LAST);
  308. facets_accepted_param(facets, JOURNAL_PARAMETER_QUERY);
  309. facets_accepted_param(facets, JOURNAL_PARAMETER_FACETS);
  310. facets_accepted_param(facets, JOURNAL_PARAMETER_HISTOGRAM);
  311. facets_accepted_param(facets, JOURNAL_PARAMETER_IF_MODIFIED_SINCE);
  312. // register the fields in the order you want them on the dashboard
  313. facets_register_dynamic_key_name(facets, "ND_JOURNAL_PROCESS",
  314. FACET_KEY_OPTION_NO_FACET | FACET_KEY_OPTION_VISIBLE | FACET_KEY_OPTION_FTS,
  315. systemd_journal_dynamic_row_id, NULL);
  316. facets_register_key_name(facets, "MESSAGE",
  317. FACET_KEY_OPTION_NO_FACET | FACET_KEY_OPTION_MAIN_TEXT | FACET_KEY_OPTION_VISIBLE |
  318. FACET_KEY_OPTION_FTS);
  319. facets_register_key_name_transformation(facets, "PRIORITY", FACET_KEY_OPTION_FACET | FACET_KEY_OPTION_FTS,
  320. systemd_journal_transform_priority, NULL);
  321. facets_register_key_name_transformation(facets, "SYSLOG_FACILITY", FACET_KEY_OPTION_FACET | FACET_KEY_OPTION_FTS,
  322. systemd_journal_transform_syslog_facility, NULL);
  323. facets_register_key_name(facets, "SYSLOG_IDENTIFIER", FACET_KEY_OPTION_FACET | FACET_KEY_OPTION_FTS);
  324. facets_register_key_name(facets, "UNIT", FACET_KEY_OPTION_FACET | FACET_KEY_OPTION_FTS);
  325. facets_register_key_name(facets, "USER_UNIT", FACET_KEY_OPTION_FACET | FACET_KEY_OPTION_FTS);
  326. facets_register_key_name_transformation(facets, "_UID", FACET_KEY_OPTION_FACET | FACET_KEY_OPTION_FTS,
  327. systemd_journal_transform_uid, uids);
  328. facets_register_key_name_transformation(facets, "_GID", FACET_KEY_OPTION_FACET | FACET_KEY_OPTION_FTS,
  329. systemd_journal_transform_gid, gids);
  330. bool info = false;
  331. time_t after_s = 0, before_s = 0;
  332. usec_t anchor = 0;
  333. usec_t if_modified_since = 0;
  334. size_t last = 0;
  335. FACETS_ANCHOR_DIRECTION direction = FACETS_ANCHOR_DIRECTION_BACKWARD;
  336. const char *query = NULL;
  337. const char *chart = NULL;
  338. const char *source = NULL;
  339. buffer_json_member_add_object(wb, "request");
  340. char *words[SYSTEMD_JOURNAL_MAX_PARAMS] = { NULL };
  341. size_t num_words = quoted_strings_splitter_pluginsd(function, words, SYSTEMD_JOURNAL_MAX_PARAMS);
  342. for(int i = 1; i < SYSTEMD_JOURNAL_MAX_PARAMS ;i++) {
  343. char *keyword = get_word(words, num_words, i);
  344. if(!keyword) break;
  345. if(strcmp(keyword, JOURNAL_PARAMETER_HELP) == 0) {
  346. systemd_journal_function_help(transaction);
  347. goto cleanup;
  348. }
  349. else if(strcmp(keyword, JOURNAL_PARAMETER_INFO) == 0) {
  350. info = true;
  351. }
  352. else if(strncmp(keyword, JOURNAL_PARAMETER_SOURCE ":", sizeof(JOURNAL_PARAMETER_SOURCE ":") - 1) == 0) {
  353. source = &keyword[sizeof(JOURNAL_PARAMETER_SOURCE ":") - 1];
  354. }
  355. else if(strncmp(keyword, JOURNAL_PARAMETER_AFTER ":", sizeof(JOURNAL_PARAMETER_AFTER ":") - 1) == 0) {
  356. after_s = str2l(&keyword[sizeof(JOURNAL_PARAMETER_AFTER ":") - 1]);
  357. }
  358. else if(strncmp(keyword, JOURNAL_PARAMETER_BEFORE ":", sizeof(JOURNAL_PARAMETER_BEFORE ":") - 1) == 0) {
  359. before_s = str2l(&keyword[sizeof(JOURNAL_PARAMETER_BEFORE ":") - 1]);
  360. }
  361. else if(strncmp(keyword, JOURNAL_PARAMETER_IF_MODIFIED_SINCE ":", sizeof(JOURNAL_PARAMETER_IF_MODIFIED_SINCE ":") - 1) == 0) {
  362. if_modified_since = str2ull(&keyword[sizeof(JOURNAL_PARAMETER_IF_MODIFIED_SINCE ":") - 1], NULL);
  363. }
  364. else if(strncmp(keyword, JOURNAL_PARAMETER_ANCHOR ":", sizeof(JOURNAL_PARAMETER_ANCHOR ":") - 1) == 0) {
  365. anchor = str2ull(&keyword[sizeof(JOURNAL_PARAMETER_ANCHOR ":") - 1], NULL);
  366. }
  367. else if(strncmp(keyword, JOURNAL_PARAMETER_DIRECTION ":", sizeof(JOURNAL_PARAMETER_DIRECTION ":") - 1) == 0) {
  368. direction = strcasecmp(&keyword[sizeof(JOURNAL_PARAMETER_DIRECTION ":") - 1], "forward") ? FACETS_ANCHOR_DIRECTION_FORWARD : FACETS_ANCHOR_DIRECTION_BACKWARD;
  369. }
  370. else if(strncmp(keyword, JOURNAL_PARAMETER_LAST ":", sizeof(JOURNAL_PARAMETER_LAST ":") - 1) == 0) {
  371. last = str2ul(&keyword[sizeof(JOURNAL_PARAMETER_LAST ":") - 1]);
  372. }
  373. else if(strncmp(keyword, JOURNAL_PARAMETER_QUERY ":", sizeof(JOURNAL_PARAMETER_QUERY ":") - 1) == 0) {
  374. query= &keyword[sizeof(JOURNAL_PARAMETER_QUERY ":") - 1];
  375. }
  376. else if(strncmp(keyword, JOURNAL_PARAMETER_HISTOGRAM ":", sizeof(JOURNAL_PARAMETER_HISTOGRAM ":") - 1) == 0) {
  377. chart = &keyword[sizeof(JOURNAL_PARAMETER_HISTOGRAM ":") - 1];
  378. }
  379. else if(strncmp(keyword, JOURNAL_PARAMETER_FACETS ":", sizeof(JOURNAL_PARAMETER_FACETS ":") - 1) == 0) {
  380. char *value = &keyword[sizeof(JOURNAL_PARAMETER_FACETS ":") - 1];
  381. if(*value) {
  382. buffer_json_member_add_array(wb, JOURNAL_PARAMETER_FACETS);
  383. while(value) {
  384. char *sep = strchr(value, ',');
  385. if(sep)
  386. *sep++ = '\0';
  387. facets_register_facet_id(facets, value, FACET_KEY_OPTION_FACET|FACET_KEY_OPTION_FTS|FACET_KEY_OPTION_REORDER);
  388. buffer_json_add_array_item_string(wb, value);
  389. value = sep;
  390. }
  391. buffer_json_array_close(wb); // JOURNAL_PARAMETER_FACETS
  392. }
  393. }
  394. else {
  395. char *value = strchr(keyword, ':');
  396. if(value) {
  397. *value++ = '\0';
  398. buffer_json_member_add_array(wb, keyword);
  399. while(value) {
  400. char *sep = strchr(value, ',');
  401. if(sep)
  402. *sep++ = '\0';
  403. facets_register_facet_id_filter(facets, keyword, value, FACET_KEY_OPTION_FACET|FACET_KEY_OPTION_FTS|FACET_KEY_OPTION_REORDER);
  404. buffer_json_add_array_item_string(wb, value);
  405. value = sep;
  406. }
  407. buffer_json_array_close(wb); // keyword
  408. }
  409. }
  410. }
  411. time_t expires = now_realtime_sec() + 1;
  412. time_t now_s;
  413. if(!after_s && !before_s) {
  414. now_s = now_realtime_sec();
  415. before_s = now_s;
  416. after_s = before_s - SYSTEMD_JOURNAL_DEFAULT_QUERY_DURATION;
  417. }
  418. else
  419. rrdr_relative_window_to_absolute(&after_s, &before_s, &now_s, false);
  420. if(after_s > before_s) {
  421. time_t tmp = after_s;
  422. after_s = before_s;
  423. before_s = tmp;
  424. }
  425. if(after_s == before_s)
  426. after_s = before_s - SYSTEMD_JOURNAL_DEFAULT_QUERY_DURATION;
  427. if(!last)
  428. last = SYSTEMD_JOURNAL_DEFAULT_ITEMS_PER_QUERY;
  429. buffer_json_member_add_string(wb, "source", source ? source : "default");
  430. buffer_json_member_add_time_t(wb, "after", after_s);
  431. buffer_json_member_add_time_t(wb, "before", before_s);
  432. buffer_json_member_add_uint64(wb, "if_modified_since", if_modified_since);
  433. buffer_json_member_add_uint64(wb, "anchor", anchor);
  434. buffer_json_member_add_string(wb, "direction", direction == FACETS_ANCHOR_DIRECTION_FORWARD ? "forward" : "backward");
  435. buffer_json_member_add_uint64(wb, "last", last);
  436. buffer_json_member_add_string(wb, "query", query);
  437. buffer_json_member_add_string(wb, "chart", chart);
  438. buffer_json_member_add_time_t(wb, "timeout", timeout);
  439. buffer_json_object_close(wb); // request
  440. int response;
  441. if(info) {
  442. facets_accepted_parameters_to_json_array(facets, wb, false);
  443. buffer_json_member_add_array(wb, "required_params");
  444. {
  445. buffer_json_add_array_item_object(wb);
  446. {
  447. buffer_json_member_add_string(wb, "id", "source");
  448. buffer_json_member_add_string(wb, "name", "source");
  449. buffer_json_member_add_string(wb, "help", "Select the SystemD Journal source to query");
  450. buffer_json_member_add_string(wb, "type", "select");
  451. buffer_json_member_add_array(wb, "options");
  452. {
  453. buffer_json_add_array_item_object(wb);
  454. {
  455. buffer_json_member_add_string(wb, "id", "default");
  456. buffer_json_member_add_string(wb, "name", "default");
  457. }
  458. buffer_json_object_close(wb); // options object
  459. }
  460. buffer_json_array_close(wb); // options array
  461. }
  462. buffer_json_object_close(wb); // required params object
  463. }
  464. buffer_json_array_close(wb); // required_params array
  465. buffer_json_member_add_uint64(wb, "status", HTTP_RESP_OK);
  466. buffer_json_member_add_string(wb, "type", "table");
  467. buffer_json_member_add_string(wb, "help", SYSTEMD_JOURNAL_FUNCTION_DESCRIPTION);
  468. buffer_json_finalize(wb);
  469. response = HTTP_RESP_OK;
  470. goto output;
  471. }
  472. facets_set_items(facets, last);
  473. facets_set_anchor(facets, anchor, direction);
  474. facets_set_query(facets, query);
  475. facets_set_histogram(facets, chart ? chart : "PRIORITY", after_s * USEC_PER_SEC, before_s * USEC_PER_SEC);
  476. response = systemd_journal_query(wb, facets, after_s * USEC_PER_SEC, before_s * USEC_PER_SEC,
  477. if_modified_since, now_monotonic_usec() + (timeout - 1) * USEC_PER_SEC);
  478. if(response != HTTP_RESP_OK) {
  479. pluginsd_function_json_error_to_stdout(transaction, response, "failed");
  480. goto cleanup;
  481. }
  482. output:
  483. pluginsd_function_result_begin_to_stdout(transaction, response, "application/json", expires);
  484. fwrite(buffer_tostring(wb), buffer_strlen(wb), 1, stdout);
  485. pluginsd_function_result_end_to_stdout();
  486. cleanup:
  487. facets_destroy(facets);
  488. buffer_free(wb);
  489. }
  490. static void *reader_main(void *arg __maybe_unused) {
  491. char buffer[PLUGINSD_LINE_MAX + 1];
  492. char *s = NULL;
  493. while(!plugin_should_exit && (s = fgets(buffer, PLUGINSD_LINE_MAX, stdin))) {
  494. char *words[PLUGINSD_MAX_WORDS] = { NULL };
  495. size_t num_words = quoted_strings_splitter_pluginsd(buffer, words, PLUGINSD_MAX_WORDS);
  496. const char *keyword = get_word(words, num_words, 0);
  497. if(keyword && strcmp(keyword, PLUGINSD_KEYWORD_FUNCTION) == 0) {
  498. char *transaction = get_word(words, num_words, 1);
  499. char *timeout_s = get_word(words, num_words, 2);
  500. char *function = get_word(words, num_words, 3);
  501. if(!transaction || !*transaction || !timeout_s || !*timeout_s || !function || !*function) {
  502. netdata_log_error("Received incomplete %s (transaction = '%s', timeout = '%s', function = '%s'). Ignoring it.",
  503. keyword,
  504. transaction?transaction:"(unset)",
  505. timeout_s?timeout_s:"(unset)",
  506. function?function:"(unset)");
  507. }
  508. else {
  509. int timeout = str2i(timeout_s);
  510. if(timeout <= 0) timeout = SYSTEMD_JOURNAL_DEFAULT_TIMEOUT;
  511. netdata_mutex_lock(&mutex);
  512. if(strncmp(function, SYSTEMD_JOURNAL_FUNCTION_NAME, strlen(SYSTEMD_JOURNAL_FUNCTION_NAME)) == 0)
  513. function_systemd_journal(transaction, function, buffer, PLUGINSD_LINE_MAX + 1, timeout);
  514. else
  515. pluginsd_function_json_error_to_stdout(transaction, HTTP_RESP_NOT_FOUND,
  516. "No function with this name found in systemd-journal.plugin.");
  517. fflush(stdout);
  518. netdata_mutex_unlock(&mutex);
  519. }
  520. }
  521. else
  522. netdata_log_error("Received unknown command: %s", keyword?keyword:"(unset)");
  523. }
  524. if(!s || feof(stdin) || ferror(stdin)) {
  525. plugin_should_exit = true;
  526. netdata_log_error("Received error on stdin.");
  527. }
  528. exit(1);
  529. }
  530. int main(int argc __maybe_unused, char **argv __maybe_unused) {
  531. stderror = stderr;
  532. clocks_init();
  533. program_name = "systemd-journal.plugin";
  534. // disable syslog
  535. error_log_syslog = 0;
  536. // set errors flood protection to 100 logs per hour
  537. error_log_errors_per_period = 100;
  538. error_log_throttle_period = 3600;
  539. uids = dictionary_create(0);
  540. gids = dictionary_create(0);
  541. netdata_configured_host_prefix = getenv("NETDATA_HOST_PREFIX");
  542. if(verify_netdata_host_prefix() == -1) exit(1);
  543. // ------------------------------------------------------------------------
  544. // debug
  545. if(argc == 2 && strcmp(argv[1], "debug") == 0) {
  546. char buf[] = "systemd-journal after:-864000 before:0 last:500";
  547. function_systemd_journal("123", buf, "", 0, 30);
  548. exit(1);
  549. }
  550. // ------------------------------------------------------------------------
  551. netdata_thread_t reader_thread;
  552. netdata_thread_create(&reader_thread, "SDJ_READER", NETDATA_THREAD_OPTION_DONT_LOG, reader_main, NULL);
  553. // ------------------------------------------------------------------------
  554. time_t started_t = now_monotonic_sec();
  555. size_t iteration;
  556. usec_t step = 1000 * USEC_PER_MS;
  557. bool tty = isatty(fileno(stderr)) == 1;
  558. netdata_mutex_lock(&mutex);
  559. fprintf(stdout, PLUGINSD_KEYWORD_FUNCTION " GLOBAL \"%s\" %d \"%s\"\n",
  560. SYSTEMD_JOURNAL_FUNCTION_NAME, SYSTEMD_JOURNAL_DEFAULT_TIMEOUT, SYSTEMD_JOURNAL_FUNCTION_DESCRIPTION);
  561. heartbeat_t hb;
  562. heartbeat_init(&hb);
  563. for(iteration = 0; 1 ; iteration++) {
  564. netdata_mutex_unlock(&mutex);
  565. heartbeat_next(&hb, step);
  566. netdata_mutex_lock(&mutex);
  567. if(!tty)
  568. fprintf(stdout, "\n");
  569. fflush(stdout);
  570. time_t now = now_monotonic_sec();
  571. if(now - started_t > 86400)
  572. break;
  573. }
  574. dictionary_destroy(uids);
  575. dictionary_destroy(gids);
  576. exit(0);
  577. }