// SPDX-License-Identifier: GPL-3.0-or-later
/*
* netdata systemd-journal.plugin
* Copyright (C) 2023 Netdata Inc.
* GPL v3+
*/
#include "collectors/all.h"
#include "libnetdata/libnetdata.h"
#include "libnetdata/required_dummies.h"
#include <systemd/sd-journal.h>
#include <syslog.h>
#define FACET_MAX_VALUE_LENGTH 8192
#define SYSTEMD_JOURNAL_FUNCTION_DESCRIPTION "View, search and analyze systemd journal entries."
#define SYSTEMD_JOURNAL_FUNCTION_NAME "systemd-journal"
#define SYSTEMD_JOURNAL_DEFAULT_TIMEOUT 30
#define SYSTEMD_JOURNAL_MAX_PARAMS 100
#define SYSTEMD_JOURNAL_DEFAULT_QUERY_DURATION (3 * 3600)
#define SYSTEMD_JOURNAL_DEFAULT_ITEMS_PER_QUERY 200
#define SYSTEMD_JOURNAL_EXCESS_ROWS_ALLOWED 50
#define SYSTEMD_JOURNAL_WORKER_THREADS 2
#define JOURNAL_PARAMETER_HELP "help"
#define JOURNAL_PARAMETER_AFTER "after"
#define JOURNAL_PARAMETER_BEFORE "before"
#define JOURNAL_PARAMETER_ANCHOR "anchor"
#define JOURNAL_PARAMETER_LAST "last"
#define JOURNAL_PARAMETER_QUERY "query"
#define JOURNAL_PARAMETER_FACETS "facets"
#define JOURNAL_PARAMETER_HISTOGRAM "histogram"
#define JOURNAL_PARAMETER_DIRECTION "direction"
#define JOURNAL_PARAMETER_IF_MODIFIED_SINCE "if_modified_since"
#define JOURNAL_PARAMETER_DATA_ONLY "data_only"
#define JOURNAL_PARAMETER_SOURCE "source"
#define JOURNAL_PARAMETER_INFO "info"
#define SYSTEMD_ALWAYS_VISIBLE_KEYS NULL
#define SYSTEMD_KEYS_EXCLUDED_FROM_FACETS NULL
#define SYSTEMD_KEYS_INCLUDED_IN_FACETS \
"_TRANSPORT" \
"|SYSLOG_IDENTIFIER" \
"|SYSLOG_FACILITY" \
"|PRIORITY" \
"|_UID" \
"|_GID" \
"|_SYSTEMD_UNIT" \
"|_SYSTEMD_SLICE" \
"|_COMM" \
"|UNIT" \
"|CONTAINER_NAME" \
"|IMAGE_NAME" \
""
static netdata_mutex_t stdout_mutex = NETDATA_MUTEX_INITIALIZER;
static bool plugin_should_exit = false;
// ----------------------------------------------------------------------------
static inline sd_journal *netdata_open_systemd_journal(void) {
sd_journal *j = NULL;
int r;
if(*netdata_configured_host_prefix) {
#ifdef HAVE_SD_JOURNAL_OS_ROOT
// Give our host prefix to systemd journal
r = sd_journal_open_directory(&j, netdata_configured_host_prefix, SD_JOURNAL_OS_ROOT);
#else
char buf[FILENAME_MAX + 1];
snprintfz(buf, FILENAME_MAX, "%s/var/log/journal", netdata_configured_host_prefix);
r = sd_journal_open_directory(&j, buf, 0);
#endif
}
else {
// Open the system journal for reading
r = sd_journal_open(&j, 0);
}
if (r < 0) {
netdata_log_error("SYSTEMD-JOURNAL: Failed to open SystemD Journal, with error %d", r);
return NULL;
}
return j;
}
typedef enum {
ND_SD_JOURNAL_FAILED_TO_SEEK,
ND_SD_JOURNAL_TIMED_OUT,
ND_SD_JOURNAL_OK,
ND_SD_JOURNAL_NOT_MODIFIED,
ND_SD_JOURNAL_CANCELLED,
} ND_SD_JOURNAL_STATUS;
static inline bool netdata_systemd_journal_seek_to(sd_journal *j, usec_t timestamp) {
if(sd_journal_seek_realtime_usec(j, timestamp) < 0) {
netdata_log_error("SYSTEMD-JOURNAL: Failed to seek to %" PRIu64, timestamp);
if(sd_journal_seek_tail(j) < 0) {
netdata_log_error("SYSTEMD-JOURNAL: Failed to seek to journal's tail");
return false;
}
}
return true;
}
static inline void netdata_systemd_journal_process_row(sd_journal *j, FACETS *facets) {
const void *data;
size_t length;
SD_JOURNAL_FOREACH_DATA(j, data, length) {
const char *key = data;
const char *equal = strchr(key, '=');
if(unlikely(!equal))
continue;
const char *value = ++equal;
size_t key_length = value - key; // including '\0'
char key_copy[key_length];
memcpy(key_copy, key, key_length - 1);
key_copy[key_length - 1] = '\0';
size_t value_length = length - key_length; // without '\0'
facets_add_key_value_length(facets, key_copy, key_length - 1, value, value_length <= FACET_MAX_VALUE_LENGTH ? value_length : FACET_MAX_VALUE_LENGTH);
}
}
static inline ND_SD_JOURNAL_STATUS check_stop(size_t row_counter, const bool *cancelled, usec_t stop_monotonic_ut) {
if((row_counter % 1000) == 0) {
if(cancelled && __ato