| 1 | // SPDX-License-Identifier: GPL-3.0-or-later |
| 2 | |
| 3 | /* |
| 4 | * TODO |
| 5 | * _UDEV_DEVLINK is frequently set more than once per field - support multi-value faces |
| 6 | * |
| 7 | */ |
| 8 | |
| 9 | #include "collectors/systemd-journal.plugin/provider/netdata_provider.h" |
| 10 | #include "systemd-internals.h" |
| 11 | #include "systemd-journal-function.h" |
| 12 | |
| 13 | #define ND_SD_JOURNAL_FUNCTION_DESCRIPTION "View, search and analyze systemd journal entries." |
| 14 | #define ND_SD_JOURNAL_FUNCTION_NAME "systemd-journal" |
| 15 | |
| 16 | #define JOURNAL_KEY_ND_JOURNAL_PROCESS "ND_JOURNAL_PROCESS" |
| 17 | |
| 18 | // functions needed by LQS |
| 19 | static __always_inline SD_JOURNAL_FILE_SOURCE_TYPE get_internal_source_type(const char *value) |
| 20 | { |
| 21 | if (strcmp(value, ND_SD_JF_SOURCE_ALL_NAME) == 0) |
| 22 | return ND_SD_JF_ALL; |
| 23 | else if (strcmp(value, ND_SD_JF_SOURCE_LOCAL_NAME) == 0) |
| 24 | return ND_SD_JF_LOCAL_ALL; |
| 25 | else if (strcmp(value, ND_SD_JF_SOURCE_REMOTES_NAME) == 0) |
| 26 | return ND_SD_JF_REMOTE_ALL; |
| 27 | else if (strcmp(value, ND_SD_JF_SOURCE_NAMESPACES_NAME) == 0) |
| 28 | return ND_SD_JF_LOCAL_NAMESPACE; |
| 29 | else if (strcmp(value, ND_SD_JF_SOURCE_LOCAL_SYSTEM_NAME) == 0) |
| 30 | return ND_SD_JF_LOCAL_SYSTEM; |
| 31 | else if (strcmp(value, ND_SD_JF_SOURCE_LOCAL_USERS_NAME) == 0) |
| 32 | return ND_SD_JF_LOCAL_USER; |
| 33 | else if (strcmp(value, ND_SD_JF_SOURCE_LOCAL_OTHER_NAME) == 0) |
| 34 | return ND_SD_JF_LOCAL_OTHER; |
| 35 | |
| 36 | return ND_SD_JF_NONE; |
| 37 | } |
| 38 | |
| 39 | // prepare LQS |
| 40 | #define LQS_FUNCTION_NAME ND_SD_JOURNAL_FUNCTION_NAME |
| 41 | #define LQS_FUNCTION_DESCRIPTION ND_SD_JOURNAL_FUNCTION_DESCRIPTION |
| 42 | #define LQS_DEFAULT_ITEMS_PER_QUERY 200 |
| 43 | #define LQS_DEFAULT_ITEMS_SAMPLING 1000000 |
| 44 | #define LQS_SOURCE_TYPE SD_JOURNAL_FILE_SOURCE_TYPE |
| 45 | #define LQS_SOURCE_TYPE_ALL ND_SD_JF_ALL |
| 46 | #define LQS_SOURCE_TYPE_NONE ND_SD_JF_NONE |
| 47 | #define LQS_PARAMETER_SOURCE_NAME "Journal Sources" // this is how it is shown to users |
| 48 | #define LQS_FUNCTION_GET_INTERNAL_SOURCE_TYPE(value) get_internal_source_type(value) |
| 49 | #define LQS_FUNCTION_SOURCE_TO_JSON_ARRAY(wb) available_journal_file_sources_to_json_array(wb) |
| 50 | |
| 51 | #ifdef HAVE_SD_JOURNAL_RESTART_FIELDS |
| 52 | #define LQS_DEFAULT_SLICE_MODE 1 |
| 53 | #else |
| 54 | #define LQS_DEFAULT_SLICE_MODE 0 |
| 55 | #endif |
| 56 | |
| 57 | #include "libnetdata/facets/logs_query_status.h" |
| 58 | |
| 59 | #define SYSTEMD_ALWAYS_VISIBLE_KEYS NULL |
| 60 | |
| 61 | #define SYSTEMD_KEYS_EXCLUDED_FROM_FACETS \ |
| 62 | "!MESSAGE_ID" \ |
| 63 | "|*MESSAGE*" \ |
| 64 | "|*TIMESTAMP*" \ |
| 65 | "|__*" \ |
| 66 | "" |
| 67 | |
| 68 | #define SYSTEMD_KEYS_INCLUDED_IN_FACETS \ |
| 69 | \ |
| 70 | /* --- USER JOURNAL FIELDS --- */ \ |
| 71 | \ |
| 72 | /* "|MESSAGE" */ \ |
| 73 | "|MESSAGE_ID" \ |
| 74 | "|PRIORITY" \ |
| 75 | "|CODE_FILE" /* "|CODE_LINE" */ \ |
| 76 | "|CODE_FUNC" \ |
| 77 | "|ERRNO" /* "|INVOCATION_ID" */ /* "|USER_INVOCATION_ID" */ \ |
| 78 | "|SYSLOG_FACILITY" \ |
| 79 | "|SYSLOG_IDENTIFIER" /* "|SYSLOG_PID" */ /* "|SYSLOG_TIMESTAMP" */ /* "|SYSLOG_RAW" */ /* "!DOCUMENTATION" */ /* "|TID" */ \ |
| 80 | "|UNIT" \ |
| 81 | "|USER_UNIT" \ |
| 82 | "|UNIT_RESULT" /* undocumented */ \ |
| 83 | \ |
| 84 | /* --- TRUSTED JOURNAL FIELDS --- */ \ |
| 85 | \ |
| 86 | /* "|_PID" */ \ |
| 87 | "|_UID" \ |
| 88 | "|_GID" \ |
| 89 | "|_COMM" \ |
| 90 | "|_EXE" /* "|_CMDLINE" "|_CAP_EFFECTIVE" "|_AUDIT_SESSION" */ \ |
| 91 | "|_AUDIT_LOGINUID" \ |
| 92 | "|_SYSTEMD_CGROUP" \ |
| 93 | "|_SYSTEMD_SLICE" \ |
| 94 | "|_SYSTEMD_UNIT" \ |
| 95 | "|_SYSTEMD_USER_UNIT" \ |
| 96 | "|_SYSTEMD_USER_SLICE" \ |
| 97 | "|_SYSTEMD_SESSION" \ |
| 98 | "|_SYSTEMD_OWNER_UID" \ |
| 99 | "|_SELINUX_CONTEXT" /* "|_SOURCE_REALTIME_TIMESTAMP" */ \ |
| 100 | "|_BOOT_ID" \ |
| 101 | "|_MACHINE_ID" /* "|_SYSTEMD_INVOCATION_ID" */ \ |
| 102 | "|_HOSTNAME" \ |
| 103 | "|_TRANSPORT" /* "|_STREAM_ID" "|LINE_BREAK" */ \ |
| 104 | "|_NAMESPACE" \ |
| 105 | "|_RUNTIME_SCOPE" \ |
| 106 | \ |
| 107 | /* --- KERNEL JOURNAL FIELDS --- */ \ |
| 108 | \ |
| 109 | /* "|_KERNEL_DEVICE" */ \ |
| 110 | "|_KERNEL_SUBSYSTEM" /* "|_UDEV_SYSNAME" */ \ |
| 111 | "|_UDEV_DEVNODE" /* "|_UDEV_DEVLINK" */ \ |
| 112 | \ |
| 113 | /* --- LOGGING ON BEHALF --- */ \ |
| 114 | \ |
| 115 | "|OBJECT_UID" \ |
| 116 | "|OBJECT_GID" \ |
| 117 | "|OBJECT_COMM" \ |
| 118 | "|OBJECT_EXE" /* "|OBJECT_CMDLINE" */ /* "|OBJECT_AUDIT_SESSION" */ \ |
| 119 | "|OBJECT_AUDIT_LOGINUID" \ |
| 120 | "|OBJECT_SYSTEMD_CGROUP" \ |
| 121 | "|OBJECT_SYSTEMD_SESSION" \ |
| 122 | "|OBJECT_SYSTEMD_OWNER_UID" \ |
| 123 | "|OBJECT_SYSTEMD_UNIT" \ |
| 124 | "|OBJECT_SYSTEMD_USER_UNIT" \ |
| 125 | \ |
| 126 | /* --- CORE DUMPS --- */ \ |
| 127 | \ |
| 128 | "|COREDUMP_COMM" \ |
| 129 | "|COREDUMP_UNIT" \ |
| 130 | "|COREDUMP_USER_UNIT" \ |
| 131 | "|COREDUMP_SIGNAL_NAME" \ |
| 132 | "|COREDUMP_CGROUP" \ |
| 133 | \ |
| 134 | /* --- DOCKER --- */ \ |
| 135 | \ |
| 136 | "|CONTAINER_ID" /* "|CONTAINER_ID_FULL" */ \ |
| 137 | "|CONTAINER_NAME" \ |
| 138 | "|CONTAINER_TAG" \ |
| 139 | "|IMAGE_NAME" /* undocumented */ /* "|CONTAINER_PARTIAL_MESSAGE" */ \ |
| 140 | \ |
| 141 | /* --- NETDATA --- */ \ |
| 142 | \ |
| 143 | "|ND_NIDL_NODE" \ |
| 144 | "|ND_NIDL_CONTEXT" \ |
| 145 | "|ND_LOG_SOURCE" /*"|ND_MODULE" */ \ |
| 146 | "|ND_ALERT_NAME" \ |
| 147 | "|ND_ALERT_CLASS" \ |
| 148 | "|ND_ALERT_COMPONENT" \ |
| 149 | "|ND_ALERT_TYPE" \ |
| 150 | "|ND_ALERT_STATUS" \ |
| 151 | \ |
| 152 | "" |
| 153 | |
| 154 | #include "systemd-journal-execute.h" |
| 155 | |
| 156 | static void systemd_journal_register_transformations(LOGS_QUERY_STATUS *lqs) |
| 157 | { |
| 158 | FACETS *facets = lqs->facets; |
| 159 | LOGS_QUERY_REQUEST *rq = &lqs->rq; |
| 160 | |
| 161 | // ---------------------------------------------------------------------------------------------------------------- |
| 162 | // register the fields in the order you want them on the dashboard |
| 163 | |
| 164 | facets_register_row_severity(facets, syslog_priority_to_facet_severity, NULL); |
| 165 | |
| 166 | facets_register_key_name(facets, "_HOSTNAME", rq->default_facet | FACET_KEY_OPTION_VISIBLE); |
| 167 | |
| 168 | facets_register_dynamic_key_name( |
| 169 | facets, |
| 170 | JOURNAL_KEY_ND_JOURNAL_PROCESS, |
| 171 | FACET_KEY_OPTION_NEVER_FACET | FACET_KEY_OPTION_VISIBLE, |
| 172 | nd_sd_journal_dynamic_row_id, |
| 173 | NULL); |
| 174 | |
| 175 | facets_register_key_name( |
| 176 | facets, |
| 177 | "MESSAGE", |
| 178 | FACET_KEY_OPTION_NEVER_FACET | FACET_KEY_OPTION_MAIN_TEXT | FACET_KEY_OPTION_VISIBLE | FACET_KEY_OPTION_FTS); |
| 179 | |
| 180 | facets_register_key_name_transformation( |
| 181 | facets, |
| 182 | "PRIORITY", |
| 183 | rq->default_facet | FACET_KEY_OPTION_TRANSFORM_VIEW | FACET_KEY_OPTION_EXPANDED_FILTER, |
| 184 | nd_sd_journal_transform_priority, |
| 185 | NULL); |
| 186 | |
| 187 | facets_register_key_name_transformation( |
| 188 | facets, |
| 189 | "SYSLOG_FACILITY", |
| 190 | rq->default_facet | FACET_KEY_OPTION_TRANSFORM_VIEW | FACET_KEY_OPTION_EXPANDED_FILTER, |
| 191 | nd_sd_journal_transform_syslog_facility, |
| 192 | NULL); |
| 193 | |
| 194 | facets_register_key_name_transformation( |
| 195 | facets, "ERRNO", rq->default_facet | FACET_KEY_OPTION_TRANSFORM_VIEW, nd_sd_journal_transform_errno, NULL); |
| 196 | |
| 197 | facets_register_key_name(facets, JOURNAL_KEY_ND_JOURNAL_FILE, FACET_KEY_OPTION_NEVER_FACET); |
| 198 | |
| 199 | facets_register_key_name(facets, "SYSLOG_IDENTIFIER", rq->default_facet); |
| 200 | |
| 201 | facets_register_key_name(facets, "UNIT", rq->default_facet); |
| 202 | |
| 203 | facets_register_key_name(facets, "USER_UNIT", rq->default_facet); |
| 204 | |
| 205 | facets_register_key_name_transformation( |
| 206 | facets, |
| 207 | "MESSAGE_ID", |
| 208 | rq->default_facet | FACET_KEY_OPTION_TRANSFORM_VIEW | FACET_KEY_OPTION_EXPANDED_FILTER, |
| 209 | nd_sd_journal_transform_message_id, |
| 210 | NULL); |
| 211 | |
| 212 | facets_register_key_name_transformation( |
| 213 | facets, "_BOOT_ID", rq->default_facet | FACET_KEY_OPTION_TRANSFORM_VIEW, nd_sd_journal_transform_boot_id, NULL); |
| 214 | |
| 215 | facets_register_key_name_transformation( |
| 216 | facets, |
| 217 | "_SYSTEMD_OWNER_UID", |
| 218 | rq->default_facet | FACET_KEY_OPTION_TRANSFORM_VIEW, |
| 219 | nd_sd_journal_transform_uid, |
| 220 | NULL); |
| 221 | |
| 222 | facets_register_key_name_transformation( |
| 223 | facets, "_UID", rq->default_facet | FACET_KEY_OPTION_TRANSFORM_VIEW, nd_sd_journal_transform_uid, NULL); |
| 224 | |
| 225 | facets_register_key_name_transformation( |
| 226 | facets, |
| 227 | "OBJECT_SYSTEMD_OWNER_UID", |
| 228 | rq->default_facet | FACET_KEY_OPTION_TRANSFORM_VIEW, |
| 229 | nd_sd_journal_transform_uid, |
| 230 | NULL); |
| 231 | |
| 232 | facets_register_key_name_transformation( |
| 233 | facets, "OBJECT_UID", rq->default_facet | FACET_KEY_OPTION_TRANSFORM_VIEW, nd_sd_journal_transform_uid, NULL); |
| 234 | |
| 235 | facets_register_key_name_transformation( |
| 236 | facets, "_GID", rq->default_facet | FACET_KEY_OPTION_TRANSFORM_VIEW, nd_sd_journal_transform_gid, NULL); |
| 237 | |
| 238 | facets_register_key_name_transformation( |
| 239 | facets, "OBJECT_GID", rq->default_facet | FACET_KEY_OPTION_TRANSFORM_VIEW, nd_sd_journal_transform_gid, NULL); |
| 240 | |
| 241 | facets_register_key_name_transformation( |
| 242 | facets, "_CAP_EFFECTIVE", FACET_KEY_OPTION_TRANSFORM_VIEW, nd_sd_journal_transform_cap_effective, NULL); |
| 243 | |
| 244 | facets_register_key_name_transformation( |
| 245 | facets, "_AUDIT_LOGINUID", FACET_KEY_OPTION_TRANSFORM_VIEW, nd_sd_journal_transform_uid, NULL); |
| 246 | |
| 247 | facets_register_key_name_transformation( |
| 248 | facets, "OBJECT_AUDIT_LOGINUID", FACET_KEY_OPTION_TRANSFORM_VIEW, nd_sd_journal_transform_uid, NULL); |
| 249 | |
| 250 | facets_register_key_name_transformation( |
| 251 | facets, |
| 252 | "_SOURCE_REALTIME_TIMESTAMP", |
| 253 | FACET_KEY_OPTION_TRANSFORM_VIEW, |
| 254 | nd_sd_journal_transform_timestamp_usec, |
| 255 | NULL); |
| 256 | } |
| 257 | |
| 258 | BUFFER *function_systemd_journal_result( |
| 259 | const char *transaction, |
| 260 | char *function, |
| 261 | usec_t *stop_monotonic_ut, |
| 262 | bool *cancelled, |
| 263 | BUFFER *payload, |
| 264 | HTTP_ACCESS access __maybe_unused, |
| 265 | const char *source __maybe_unused, |
| 266 | void *data __maybe_unused) |
| 267 | { |
| 268 | fstat_thread_calls = 0; |
| 269 | fstat_thread_cached_responses = 0; |
| 270 | |
| 271 | #ifdef HAVE_SD_JOURNAL_RESTART_FIELDS |
| 272 | bool have_slice = true; |
| 273 | #else |
| 274 | bool have_slice = false; |
| 275 | #endif // HAVE_SD_JOURNAL_RESTART_FIELDS |
| 276 | |
| 277 | LOGS_QUERY_STATUS tmp_fqs = { |
| 278 | .facets = lqs_facets_create( |
| 279 | LQS_DEFAULT_ITEMS_PER_QUERY, |
| 280 | FACETS_OPTION_ALL_KEYS_FTS | FACETS_OPTION_HASH_IDS, |
| 281 | SYSTEMD_ALWAYS_VISIBLE_KEYS, |
| 282 | SYSTEMD_KEYS_INCLUDED_IN_FACETS, |
| 283 | SYSTEMD_KEYS_EXCLUDED_FROM_FACETS, |
| 284 | have_slice), |
| 285 | |
| 286 | .rq = LOGS_QUERY_REQUEST_DEFAULTS(transaction, LQS_DEFAULT_SLICE_MODE, JOURNAL_DEFAULT_DIRECTION), |
| 287 | |
| 288 | .cancelled = cancelled, |
| 289 | .stop_monotonic_ut = stop_monotonic_ut, |
| 290 | }; |
| 291 | LOGS_QUERY_STATUS *lqs = &tmp_fqs; |
| 292 | |
| 293 | BUFFER *wb = lqs_create_output_buffer(); |
| 294 | |
| 295 | // ------------------------------------------------------------------------ |
| 296 | // parse the parameters |
| 297 | |
| 298 | if (lqs_request_parse_and_validate(lqs, wb, function, payload, have_slice, "PRIORITY")) { |
| 299 | systemd_journal_register_transformations(lqs); |
| 300 | |
| 301 | // ------------------------------------------------------------------------ |
| 302 | // add versions to the response |
| 303 | |
| 304 | buffer_json_journal_versions(wb); |
| 305 | |
| 306 | // ------------------------------------------------------------------------ |
| 307 | // run the request |
| 308 | |
| 309 | if (lqs->rq.info) |
| 310 | lqs_info_response(wb, lqs->facets); |
| 311 | else { |
| 312 | nd_sd_journal_query(wb, lqs); |
| 313 | if (wb->response_code == HTTP_RESP_OK) |
| 314 | buffer_json_finalize(wb); |
| 315 | } |
| 316 | } |
| 317 | |
| 318 | lqs_cleanup(lqs); |
| 319 | |
| 320 | return wb; |
| 321 | } |
| 322 | |
| 323 | void function_systemd_journal( |
| 324 | const char *transaction, |
| 325 | char *function, |
| 326 | usec_t *stop_monotonic_ut, |
| 327 | bool *cancelled, |
| 328 | BUFFER *payload, |
| 329 | HTTP_ACCESS access, |
| 330 | const char *source, |
| 331 | void *data) |
| 332 | { |
| 333 | BUFFER *wb = function_systemd_journal_result( |
| 334 | transaction, function, stop_monotonic_ut, cancelled, payload, access, source, data); |
| 335 | |
| 336 | netdata_mutex_lock(&stdout_mutex); |
| 337 | pluginsd_function_result_to_stdout(transaction, wb); |
| 338 | netdata_mutex_unlock(&stdout_mutex); |
| 339 | |
| 340 | buffer_free(wb); |
| 341 | } |