journal: expose last 10 high priority logs as metrics
This commit exposes the last 10 high priority logs as metrics so that the systemd-report reports them. The entries are reported as `io.systemd.Journal.HighPriorityMessage` and include all field as the new METRIC_FAMILY_TYPE_OBJECT. Individual fields from a journal entry that are unprintable (invalid utf-8) are skipped. This is archived via a new socket-activated unit listens on /run/systemd/report/io.systemd.Journal
This commit is contained in:
@@ -0,0 +1,93 @@
|
||||
/* SPDX-License-Identifier: LGPL-2.1-or-later */
|
||||
|
||||
#include "sd-journal.h"
|
||||
#include "sd-json.h"
|
||||
#include "sd-varlink.h"
|
||||
|
||||
#include "journalctl.h"
|
||||
#include "journalctl-filter.h"
|
||||
#include "journalctl-metrics.h"
|
||||
#include "log.h"
|
||||
#include "logs-show.h"
|
||||
#include "metrics.h"
|
||||
#include "output-mode.h"
|
||||
|
||||
/* Fallback cap so we never stream unbounded entries when --lines= is "all" or unset. */
|
||||
#define N_RECENT_HIGH_PRIORITY 10
|
||||
|
||||
/* Set a maximum scan factor to avoid unbound iterating through the journal */
|
||||
#define RECENT_HIGH_PRIORITY_SCAN_FACTOR 50
|
||||
|
||||
static int recent_high_priority_generate(const MetricFamily *mf, sd_varlink *link, void *userdata) {
|
||||
_cleanup_(sd_journal_closep) sd_journal *j = NULL;
|
||||
int r;
|
||||
|
||||
assert(mf && mf->name);
|
||||
assert(link);
|
||||
|
||||
r = sd_journal_open(&j, SD_JOURNAL_LOCAL_ONLY | SD_JOURNAL_SYSTEM | SD_JOURNAL_ASSUME_IMMUTABLE);
|
||||
if (r < 0)
|
||||
return log_debug_errno(r, "Failed to open journal, ignoring: %m");
|
||||
|
||||
/* Get filters (--priority=, units, matches, ...) from the command-line. */
|
||||
r = add_filters(j, /* matches= */ NULL);
|
||||
if (r < 0)
|
||||
return log_debug_errno(r, "Failed to add journal filters: %m");
|
||||
|
||||
r = sd_journal_seek_tail(j);
|
||||
if (r < 0)
|
||||
return log_debug_errno(r, "Failed to seek to journal tail: %m");
|
||||
|
||||
/* The --lines=+N argument does not make much sense for a metrics provider so we ignore it here. */
|
||||
if (arg_lines_oldest)
|
||||
log_warning("--lines=+N is not supported when serving the metrics interface, ignoring.");
|
||||
|
||||
uint64_t max_lines = arg_lines_needs_seek_end() ? (uint64_t) arg_lines : N_RECENT_HIGH_PRIORITY;
|
||||
uint64_t max_scan = max_lines * RECENT_HIGH_PRIORITY_SCAN_FACTOR;
|
||||
|
||||
for (uint64_t found = 0, scanned = 0; found < max_lines && scanned < max_scan; scanned++) {
|
||||
_cleanup_(sd_json_variant_unrefp) sd_json_variant *entry = NULL;
|
||||
|
||||
r = sd_journal_previous(j);
|
||||
if (r < 0)
|
||||
return log_debug_errno(r, "Failed to iterate to previous journal entry: %m");
|
||||
if (r == 0)
|
||||
break;
|
||||
|
||||
r = journal_entry_to_json(j, OUTPUT_SHOW_ALL | OUTPUT_SKIP_UNPRINTABLE, /* output_fields= */ NULL, &entry);
|
||||
if (r < 0) {
|
||||
log_debug_errno(r, "Failed to convert journal entry to JSON, skipping entry: %m");
|
||||
continue;
|
||||
}
|
||||
if (r == 0)
|
||||
continue;
|
||||
|
||||
const char *ident = sd_json_variant_string(sd_json_variant_by_key(entry, "SYSLOG_IDENTIFIER"));
|
||||
|
||||
r = metric_build_send_object(mf, link, /* object= */ ident, entry, /* fields= */ NULL);
|
||||
if (r < 0)
|
||||
return log_debug_errno(r, "Failed to send journal metric: %m");
|
||||
|
||||
found++;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const MetricFamily journal_metric_family_table[] = {
|
||||
{
|
||||
.name = METRIC_IO_SYSTEMD_JOURNAL_PREFIX "HighPriorityMessage",
|
||||
.description = "The most recent high-priority journal messages",
|
||||
.type = METRIC_FAMILY_TYPE_OBJECT,
|
||||
.generate = recent_high_priority_generate,
|
||||
},
|
||||
{}
|
||||
};
|
||||
|
||||
int vl_method_describe_metrics(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata) {
|
||||
return metrics_method_describe(journal_metric_family_table, link, parameters, flags, userdata);
|
||||
}
|
||||
|
||||
int vl_method_list_metrics(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata) {
|
||||
return metrics_method_list(journal_metric_family_table, link, parameters, flags, userdata);
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
/* SPDX-License-Identifier: LGPL-2.1-or-later */
|
||||
#pragma once
|
||||
|
||||
#include "shared-forward.h"
|
||||
|
||||
#define METRIC_IO_SYSTEMD_JOURNAL_PREFIX "io.systemd.Journal."
|
||||
|
||||
int vl_method_list_metrics(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata);
|
||||
int vl_method_describe_metrics(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata);
|
||||
+81
-41
@@ -16,6 +16,7 @@
|
||||
#include "journalctl.h"
|
||||
#include "journalctl-authenticate.h"
|
||||
#include "journalctl-catalog.h"
|
||||
#include "journalctl-metrics.h"
|
||||
#include "journalctl-misc.h"
|
||||
#include "journalctl-show.h"
|
||||
#include "journalctl-varlink.h"
|
||||
@@ -40,6 +41,7 @@
|
||||
#include "syslog-util.h"
|
||||
#include "time-util.h"
|
||||
#include "varlink-io.systemd.JournalAccess.h"
|
||||
#include "varlink-io.systemd.Metrics.h"
|
||||
#include "varlink-util.h"
|
||||
|
||||
#define DEFAULT_FSS_INTERVAL_USEC (15*USEC_PER_MINUTE)
|
||||
@@ -214,6 +216,43 @@ default_noarg:
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int parse_priorities(const char *arg) {
|
||||
assert(arg);
|
||||
|
||||
const char *dots = strstr(arg, "..");
|
||||
if (dots) {
|
||||
/* a range */
|
||||
_cleanup_free_ char *a = strndup(arg, dots - arg);
|
||||
if (!a)
|
||||
return log_oom();
|
||||
|
||||
int from = log_level_from_string(a),
|
||||
to = log_level_from_string(dots + 2);
|
||||
|
||||
if (from < 0 || to < 0)
|
||||
return log_error_errno(from < 0 ? from : to,
|
||||
"Failed to parse log level range %s", arg);
|
||||
|
||||
arg_priorities = 0;
|
||||
if (from < to)
|
||||
for (int i = from; i <= to; i++)
|
||||
arg_priorities |= 1 << i;
|
||||
else
|
||||
for (int i = to; i <= from; i++)
|
||||
arg_priorities |= 1 << i;
|
||||
} else {
|
||||
int p = log_level_from_string(arg);
|
||||
if (p < 0)
|
||||
return log_error_errno(p, "Unknown log level %s", arg);
|
||||
|
||||
arg_priorities = 0;
|
||||
for (int i = 0; i <= p; i++)
|
||||
arg_priorities |= 1 << i;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int help_facilities(void) {
|
||||
if (!arg_quiet)
|
||||
puts("Available facilities:");
|
||||
@@ -277,13 +316,23 @@ static int vl_server(void) {
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to allocate Varlink server: %m");
|
||||
|
||||
r = sd_varlink_server_add_interface(varlink_server, &vl_interface_io_systemd_JournalAccess);
|
||||
/* Serve both interfaces regardless of which socket activated us: both activating sockets share the
|
||||
* same access controls (systemd-journal group), so anyone reaching either is already entitled to
|
||||
* both. */
|
||||
r = sd_varlink_server_add_interface_many(
|
||||
varlink_server,
|
||||
&vl_interface_io_systemd_JournalAccess,
|
||||
&vl_interface_io_systemd_Metrics);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to add Varlink interface: %m");
|
||||
return log_error_errno(r, "Failed to add Varlink interfaces: %m");
|
||||
|
||||
r = sd_varlink_server_bind_method(varlink_server, "io.systemd.JournalAccess.GetEntries", vl_method_get_entries);
|
||||
r = sd_varlink_server_bind_method_many(
|
||||
varlink_server,
|
||||
"io.systemd.JournalAccess.GetEntries", vl_method_get_entries,
|
||||
"io.systemd.Metrics.List", vl_method_list_metrics,
|
||||
"io.systemd.Metrics.Describe", vl_method_describe_metrics);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to bind Varlink method: %m");
|
||||
return log_error_errno(r, "Failed to bind Varlink methods: %m");
|
||||
|
||||
r = sd_varlink_server_loop_auto(varlink_server);
|
||||
if (r < 0)
|
||||
@@ -319,13 +368,35 @@ static int parse_argv(int argc, char *argv[], char ***remaining_args) {
|
||||
OPTION_COMMON_USER:
|
||||
arg_varlink_runtime_scope = RUNTIME_SCOPE_USER;
|
||||
break;
|
||||
}
|
||||
|
||||
OPTION('p', "priority", "RANGE", "Show entries within the specified priority range"):
|
||||
r = parse_priorities(opts.arg);
|
||||
if (r < 0)
|
||||
return r;
|
||||
break;
|
||||
|
||||
OPTION_FULL(OPTION_OPTIONAL_ARG, 'n', "lines", "[+]INTEGER",
|
||||
"Number of journal entries to show"): {
|
||||
const char *p = opts.arg ?: option_parser_peek_next_arg(&opts);
|
||||
|
||||
r = parse_lines(p, /* graceful= */ !opts.arg);
|
||||
if (r < 0)
|
||||
return r;
|
||||
if (r > 0 && !opts.arg)
|
||||
(void) option_parser_consume_next_arg(&opts);
|
||||
|
||||
break;
|
||||
}}
|
||||
|
||||
if (arg_varlink_runtime_scope < 0)
|
||||
return log_error_errno(arg_varlink_runtime_scope, "Cannot run in Varlink mode with no runtime scope specified.");
|
||||
|
||||
if (option_parser_get_n_args(&opts) > 0)
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "No arguments expected in Varlink mode.");
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "No positional arguments expected in Varlink mode.");
|
||||
|
||||
/* We return early, skipping the tristate resolution the regular path does below; resolve it
|
||||
* here so add_filters() doesn't trip over the unresolved -1. */
|
||||
arg_boot = false;
|
||||
|
||||
*remaining_args = NULL;
|
||||
return 1;
|
||||
@@ -526,42 +597,11 @@ static int parse_argv(int argc, char *argv[], char ***remaining_args) {
|
||||
return log_oom();
|
||||
break;
|
||||
|
||||
OPTION('p', "priority", "RANGE", "Show entries within the specified priority range"): {
|
||||
|
||||
const char *dots = strstr(opts.arg, "..");
|
||||
if (dots) {
|
||||
/* a range */
|
||||
_cleanup_free_ char *a = strndup(opts.arg, dots - opts.arg);
|
||||
if (!a)
|
||||
return log_oom();
|
||||
|
||||
int from = log_level_from_string(a),
|
||||
to = log_level_from_string(dots + 2);
|
||||
|
||||
if (from < 0 || to < 0)
|
||||
return log_error_errno(from < 0 ? from : to,
|
||||
"Failed to parse log level range %s", opts.arg);
|
||||
|
||||
arg_priorities = 0;
|
||||
if (from < to)
|
||||
for (int i = from; i <= to; i++)
|
||||
arg_priorities |= 1 << i;
|
||||
else
|
||||
for (int i = to; i <= from; i++)
|
||||
arg_priorities |= 1 << i;
|
||||
|
||||
} else {
|
||||
int p = log_level_from_string(opts.arg);
|
||||
if (p < 0)
|
||||
return log_error_errno(p, "Unknown log level %s", opts.arg);
|
||||
|
||||
arg_priorities = 0;
|
||||
for (int i = 0; i <= p; i++)
|
||||
arg_priorities |= 1 << i;
|
||||
}
|
||||
|
||||
OPTION('p', "priority", "RANGE", "Show entries within the specified priority range"):
|
||||
r = parse_priorities(opts.arg);
|
||||
if (r < 0)
|
||||
return r;
|
||||
break;
|
||||
}
|
||||
|
||||
OPTION_LONG("facility", "FACILITY…", "Show entries with the specified facilities"):
|
||||
for (const char *p = opts.arg;;) {
|
||||
|
||||
@@ -34,6 +34,7 @@ journalctl_sources = files(
|
||||
'journalctl-authenticate.c',
|
||||
'journalctl-catalog.c',
|
||||
'journalctl-filter.c',
|
||||
'journalctl-metrics.c',
|
||||
'journalctl-misc.c',
|
||||
'journalctl-show.c',
|
||||
'journalctl-util.c',
|
||||
|
||||
Reference in New Issue
Block a user