[PATCH v3 0/5] logging: add log cleanup for obsolete domains

Presently, logs from deleted domains remain forever. Particular motivation comes from the case when libguestfs has repeatedly created transient VMs, which in turn created plenty of logs. This takes up space and lots of files troubles filesystem navigation. More motivation in [1]. Patch solving same problem in [2]. Changes in v3: codestyle cleanup, minor fixes Changes in v2: substantial rework according to Martin Kletzander's comments v1: https://www.mail-archive.com/libvir-list@redhat.com/msg233754.html v2: https://www.spinics.net/linux/fedora/libvir/msg236081.html [1]: https://listman.redhat.com/archives/libvir-list/2022-February/228149.html [2]: https://listman.redhat.com/archives/libvir-list/2022-February/msg00865.html CC: Martin Kletzander <mkletzan@redhat.com> Oleg Vasilev (5): logging: refactor to store config inside log handler logging: move virLogHandler to header logging: add configuration for future log cleaner logging: add log cleanup for obsolete domains logging: use the log cleaner po/POTFILES | 1 + src/logging/log_cleaner.c | 268 +++++++++++++++++++++++++++++++ src/logging/log_cleaner.h | 29 ++++ src/logging/log_daemon.c | 6 +- src/logging/log_daemon_config.c | 9 ++ src/logging/log_daemon_config.h | 3 + src/logging/log_handler.c | 64 +++----- src/logging/log_handler.h | 50 ++++-- src/logging/meson.build | 1 + src/logging/test_virtlogd.aug.in | 2 + src/logging/virtlogd.aug | 2 + src/logging/virtlogd.conf | 14 ++ 12 files changed, 391 insertions(+), 58 deletions(-) create mode 100644 src/logging/log_cleaner.c create mode 100644 src/logging/log_cleaner.h -- 2.39.1

Signed-off-by: Oleg Vasilev <oleg.vasilev@virtuozzo.com> --- src/logging/log_daemon.c | 6 ++---- src/logging/log_handler.c | 29 ++++++++++++----------------- src/logging/log_handler.h | 17 ++++++++--------- 3 files changed, 22 insertions(+), 30 deletions(-) diff --git a/src/logging/log_daemon.c b/src/logging/log_daemon.c index 9b70ffad2f..8025245350 100644 --- a/src/logging/log_daemon.c +++ b/src/logging/log_daemon.c @@ -145,8 +145,7 @@ virLogDaemonNew(virLogDaemonConfig *config, bool privileged) g_clear_pointer(&srv, virObjectUnref); if (!(logd->handler = virLogHandlerNew(privileged, - config->max_size, - config->max_backups, + config, virLogDaemonInhibitor, logd))) goto error; @@ -231,8 +230,7 @@ virLogDaemonNewPostExecRestart(virJSONValue *object, bool privileged, if (!(logd->handler = virLogHandlerNewPostExecRestart(child, privileged, - config->max_size, - config->max_backups, + config, virLogDaemonInhibitor, logd))) goto error; diff --git a/src/logging/log_handler.c b/src/logging/log_handler.c index bf1e3f35c5..7342404b00 100644 --- a/src/logging/log_handler.c +++ b/src/logging/log_handler.c @@ -58,8 +58,7 @@ struct _virLogHandler { virObjectLockable parent; bool privileged; - size_t max_size; - size_t max_backups; + virLogDaemonConfig *config; virLogHandlerLogFile **files; size_t nfiles; @@ -185,8 +184,7 @@ virLogHandlerDomainLogFileEvent(int watch, virLogHandler * virLogHandlerNew(bool privileged, - size_t max_size, - size_t max_backups, + virLogDaemonConfig *config, virLogHandlerShutdownInhibitor inhibitor, void *opaque) { @@ -199,8 +197,7 @@ virLogHandlerNew(bool privileged, return NULL; handler->privileged = privileged; - handler->max_size = max_size; - handler->max_backups = max_backups; + handler->config = config; handler->inhibitor = inhibitor; handler->opaque = opaque; @@ -253,8 +250,8 @@ virLogHandlerLogFilePostExecRestart(virLogHandler *handler, } if ((file->file = virRotatingFileWriterNew(path, - handler->max_size, - handler->max_backups, + handler->config->max_size, + handler->config->max_backups, false, DEFAULT_MODE)) == NULL) goto error; @@ -282,8 +279,7 @@ virLogHandlerLogFilePostExecRestart(virLogHandler *handler, virLogHandler * virLogHandlerNewPostExecRestart(virJSONValue *object, bool privileged, - size_t max_size, - size_t max_backups, + virLogDaemonConfig *config, virLogHandlerShutdownInhibitor inhibitor, void *opaque) { @@ -292,8 +288,7 @@ virLogHandlerNewPostExecRestart(virJSONValue *object, size_t i; if (!(handler = virLogHandlerNew(privileged, - max_size, - max_backups, + config, inhibitor, opaque))) return NULL; @@ -393,8 +388,8 @@ virLogHandlerDomainOpenLogFile(virLogHandler *handler, file->domname = g_strdup(domname); if ((file->file = virRotatingFileWriterNew(path, - handler->max_size, - handler->max_backups, + handler->config->max_size, + handler->config->max_backups, trunc, DEFAULT_MODE)) == NULL) goto error; @@ -525,7 +520,7 @@ virLogHandlerDomainReadLogFile(virLogHandler *handler, virObjectLock(handler); - if (!(file = virRotatingFileReaderNew(path, handler->max_backups))) + if (!(file = virRotatingFileReaderNew(path, handler->config->max_backups))) goto error; if (virRotatingFileReaderSeek(file, inode, offset) < 0) @@ -579,8 +574,8 @@ virLogHandlerDomainAppendLogFile(virLogHandler *handler, if (!writer) { if (!(newwriter = virRotatingFileWriterNew(path, - handler->max_size, - handler->max_backups, + handler->config->max_size, + handler->config->max_backups, false, DEFAULT_MODE))) goto cleanup; diff --git a/src/logging/log_handler.h b/src/logging/log_handler.h index 099378b361..c9af033cd3 100644 --- a/src/logging/log_handler.h +++ b/src/logging/log_handler.h @@ -22,6 +22,7 @@ #include "internal.h" #include "virjson.h" +#include "log_daemon_config.h" typedef struct _virLogHandler virLogHandler; @@ -30,16 +31,14 @@ typedef void (*virLogHandlerShutdownInhibitor)(bool inhibit, void *opaque); virLogHandler *virLogHandlerNew(bool privileged, - size_t max_size, - size_t max_backups, - virLogHandlerShutdownInhibitor inhibitor, - void *opaque); + virLogDaemonConfig *config, + virLogHandlerShutdownInhibitor inhibitor, + void *opaque); virLogHandler *virLogHandlerNewPostExecRestart(virJSONValue *child, - bool privileged, - size_t max_size, - size_t max_backups, - virLogHandlerShutdownInhibitor inhibitor, - void *opaque); + bool privileged, + virLogDaemonConfig *config, + virLogHandlerShutdownInhibitor inhibitor, + void *opaque); void virLogHandlerFree(virLogHandler *handler); -- 2.39.1

Signed-off-by: Oleg Vasilev <oleg.vasilev@virtuozzo.com> --- src/logging/log_handler.c | 26 -------------------------- src/logging/log_handler.h | 31 ++++++++++++++++++++++++++++--- 2 files changed, 28 insertions(+), 29 deletions(-) diff --git a/src/logging/log_handler.c b/src/logging/log_handler.c index 7342404b00..8fc7e9b2a8 100644 --- a/src/logging/log_handler.c +++ b/src/logging/log_handler.c @@ -22,11 +22,9 @@ #include "log_handler.h" #include "virerror.h" -#include "virobject.h" #include "virfile.h" #include "viralloc.h" #include "virlog.h" -#include "virrotatingfile.h" #include "viruuid.h" #include "virutil.h" @@ -42,30 +40,6 @@ VIR_LOG_INIT("logging.log_handler"); #define DEFAULT_MODE 0600 -typedef struct _virLogHandlerLogFile virLogHandlerLogFile; -struct _virLogHandlerLogFile { - virRotatingFileWriter *file; - int watch; - int pipefd; /* Read from QEMU via this */ - bool drained; - - char *driver; - unsigned char domuuid[VIR_UUID_BUFLEN]; - char *domname; -}; - -struct _virLogHandler { - virObjectLockable parent; - - bool privileged; - virLogDaemonConfig *config; - - virLogHandlerLogFile **files; - size_t nfiles; - - virLogHandlerShutdownInhibitor inhibitor; - void *opaque; -}; static virClass *virLogHandlerClass; static void virLogHandlerDispose(void *obj); diff --git a/src/logging/log_handler.h b/src/logging/log_handler.h index c9af033cd3..d473a19fc6 100644 --- a/src/logging/log_handler.h +++ b/src/logging/log_handler.h @@ -23,13 +23,38 @@ #include "internal.h" #include "virjson.h" #include "log_daemon_config.h" - -typedef struct _virLogHandler virLogHandler; - +#include "virobject.h" +#include "virrotatingfile.h" typedef void (*virLogHandlerShutdownInhibitor)(bool inhibit, void *opaque); +typedef struct _virLogHandlerLogFile virLogHandlerLogFile; +struct _virLogHandlerLogFile { + virRotatingFileWriter *file; + int watch; + int pipefd; /* Read from QEMU via this */ + bool drained; + + char *driver; + unsigned char domuuid[VIR_UUID_BUFLEN]; + char *domname; +}; + +typedef struct _virLogHandler virLogHandler; +struct _virLogHandler { + virObjectLockable parent; + + bool privileged; + virLogDaemonConfig *config; + + virLogHandlerLogFile **files; + size_t nfiles; + + virLogHandlerShutdownInhibitor inhibitor; + void *opaque; +}; + virLogHandler *virLogHandlerNew(bool privileged, virLogDaemonConfig *config, virLogHandlerShutdownInhibitor inhibitor, -- 2.39.1

We want to specify the folder to clean and how much time can a log chain live. Signed-off-by: Oleg Vasilev <oleg.vasilev@virtuozzo.com> --- src/logging/log_daemon_config.c | 9 +++++++++ src/logging/log_daemon_config.h | 3 +++ src/logging/test_virtlogd.aug.in | 2 ++ src/logging/virtlogd.aug | 2 ++ src/logging/virtlogd.conf | 14 ++++++++++++++ 5 files changed, 30 insertions(+) diff --git a/src/logging/log_daemon_config.c b/src/logging/log_daemon_config.c index 4436745488..248bd927d3 100644 --- a/src/logging/log_daemon_config.c +++ b/src/logging/log_daemon_config.c @@ -28,6 +28,7 @@ #include "virutil.h" #define VIR_FROM_THIS VIR_FROM_CONF +#define DEFAULT_LOG_ROOT LOCALSTATEDIR "/log/libvirt/" VIR_LOG_INIT("logging.log_daemon_config"); @@ -60,6 +61,7 @@ virLogDaemonConfigNew(bool privileged G_GNUC_UNUSED) data->admin_max_clients = 5000; data->max_size = 1024 * 1024 * 2; data->max_backups = 3; + data->max_age_days = 0; return data; } @@ -72,6 +74,7 @@ virLogDaemonConfigFree(virLogDaemonConfig *data) g_free(data->log_filters); g_free(data->log_outputs); + g_free(data->log_root); g_free(data); } @@ -94,6 +97,12 @@ virLogDaemonConfigLoadOptions(virLogDaemonConfig *data, return -1; if (virConfGetValueSizeT(conf, "max_backups", &data->max_backups) < 0) return -1; + if (virConfGetValueSizeT(conf, "max_age_days", &data->max_age_days) < 0) + return -1; + if (virConfGetValueString(conf, "log_root", &data->log_root) < 0) + return -1; + if (!data->log_root) + data->log_root = g_strdup(DEFAULT_LOG_ROOT); return 0; } diff --git a/src/logging/log_daemon_config.h b/src/logging/log_daemon_config.h index 2ab0f67c96..43922feedf 100644 --- a/src/logging/log_daemon_config.h +++ b/src/logging/log_daemon_config.h @@ -33,6 +33,9 @@ struct _virLogDaemonConfig { size_t max_backups; size_t max_size; + + char *log_root; + size_t max_age_days; }; diff --git a/src/logging/test_virtlogd.aug.in b/src/logging/test_virtlogd.aug.in index cd5b0d91f8..8dfad39506 100644 --- a/src/logging/test_virtlogd.aug.in +++ b/src/logging/test_virtlogd.aug.in @@ -9,3 +9,5 @@ module Test_virtlogd = { "admin_max_clients" = "5" } { "max_size" = "2097152" } { "max_backups" = "3" } + { "max_age_days" = "0" } + { "log_root" = "/var/log/libvirt" } diff --git a/src/logging/virtlogd.aug b/src/logging/virtlogd.aug index 0f1b290c72..bdf61dea6e 100644 --- a/src/logging/virtlogd.aug +++ b/src/logging/virtlogd.aug @@ -31,6 +31,8 @@ module Virtlogd = | int_entry "admin_max_clients" | int_entry "max_size" | int_entry "max_backups" + | int_entry "max_age_days" + | str_entry "log_root" (* Each entry in the config is one of the following three ... *) let entry = logging_entry diff --git a/src/logging/virtlogd.conf b/src/logging/virtlogd.conf index c53a1112bd..5214e96121 100644 --- a/src/logging/virtlogd.conf +++ b/src/logging/virtlogd.conf @@ -101,3 +101,17 @@ # Maximum number of backup files to keep. Defaults to 3, # not including the primary active file #max_backups = 3 + +# Maximum age for log files to live after the last modification. +# Defaults to 0, which means "forever". +# +# WARNING: since virtlogd has no way to differentiate which files it used to +# manage, the garbage collection mechanism will collect ALL files, once its age +# reach max_age_days. Use only if you know what you mean. +#max_age_days = 0 + +# Root of all logs managed by virtlogd. Used to GC logs from obsolete machines. +# +# WARNING: all files under this location potentially can be GC-ed. See the +# warning for max_age_days. +#log_root = "/var/log/libvirt" -- 2.39.1

Before, logs from deleted machines have been piling up, since there were no garbage collection mechanism. Now, virtlogd can be configured to periodically scan the log folder for orphan logs with no recent modifications and delete it. A single chain of recent and rotated logs is deleted in a single transaction. Signed-off-by: Oleg Vasilev <oleg.vasilev@virtuozzo.com> --- po/POTFILES | 1 + src/logging/log_cleaner.c | 268 ++++++++++++++++++++++++++++++++++++++ src/logging/log_cleaner.h | 29 +++++ src/logging/log_handler.h | 2 + src/logging/meson.build | 1 + 5 files changed, 301 insertions(+) create mode 100644 src/logging/log_cleaner.c create mode 100644 src/logging/log_cleaner.h diff --git a/po/POTFILES b/po/POTFILES index 169e2a41dc..2fb6d18e27 100644 --- a/po/POTFILES +++ b/po/POTFILES @@ -123,6 +123,7 @@ src/locking/lock_driver_lockd.c src/locking/lock_driver_sanlock.c src/locking/lock_manager.c src/locking/sanlock_helper.c +src/logging/log_cleaner.c src/logging/log_daemon.c src/logging/log_daemon_dispatch.c src/logging/log_handler.c diff --git a/src/logging/log_cleaner.c b/src/logging/log_cleaner.c new file mode 100644 index 0000000000..3de54d0795 --- /dev/null +++ b/src/logging/log_cleaner.c @@ -0,0 +1,268 @@ +/* + * log_cleaner.c: cleans obsolete log files + * + * Copyright (C) 2022 Virtuozzo International GmbH + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; If not, see + * <http://www.gnu.org/licenses/>. + */ + +#include <config.h> + +#include "log_cleaner.h" +#include "log_handler.h" + +#include "virerror.h" +#include "virobject.h" +#include "virfile.h" +#include "viralloc.h" +#include "virlog.h" +#include "virrotatingfile.h" +#include "virstring.h" + +#define VIR_FROM_THIS VIR_FROM_LOGGING + +VIR_LOG_INIT("logging.log_cleaner"); + +/* Cleanup log root (/var/log/libvirt) and all subfolders (e.g. /var/log/libvirt/qemu) */ +#define CLEANER_LOG_DEPTH 1 +#define CLEANER_LOG_TIMEOUT_MS (24 * 3600 * 1000) /* One day */ +#define MAX_TIME ((time_t) G_MAXINT64) + +static GRegex *log_regex; + +typedef struct _virLogCleanerChain virLogCleanerChain; +struct _virLogCleanerChain { + int rotated_max_index; + time_t last_modified; +}; + +typedef struct _virLogCleanerData virLogCleanerData; +struct _virLogCleanerData { + virLogHandler *handler; + time_t oldest_to_keep; + GHashTable *chains; +}; + +static const char* +virLogCleanerParseFilename(const char *path, + int *rotated_index) +{ + g_autoptr(GMatchInfo) matchInfo = NULL; + g_autofree const char *rotated_index_str = NULL; + g_autofree const char *clear_path = NULL; + const char *chain_prefix = NULL; + + clear_path = realpath(path, NULL); + if (!clear_path) { + VIR_WARN("Failed to resolve path %s: %s", path, g_strerror(errno)); + return NULL; + } + + if (!g_regex_match(log_regex, path, 0, &matchInfo)) + return NULL; + + chain_prefix = g_match_info_fetch(matchInfo, 1); + if (!rotated_index) + return chain_prefix; + + *rotated_index = 0; + rotated_index_str = g_match_info_fetch(matchInfo, 3); + + if (!rotated_index_str) + return chain_prefix; + + if (virStrToLong_i(rotated_index_str, NULL, 10, rotated_index) < 0) { + virReportError(VIR_ERR_INTERNAL_ERROR, + _("Failed to parse rotated index from '%s'"), + rotated_index_str); + return NULL; + } + return chain_prefix; +} + +static void +virLogCleanerProcessFile(virLogCleanerData *data, + const char *path, + struct stat *sb) +{ + int rotated_index = 0; + g_autofree const char *chain_prefix = NULL; + virLogCleanerChain *chain; + + if (!S_ISREG(sb->st_mode)) + return; + + chain_prefix = virLogCleanerParseFilename(path, &rotated_index); + + if (!chain_prefix) + return; + + if (rotated_index > data->handler->config->max_backups) { + if (unlink(path) < 0) { + VIR_WARN("Unable to delete %s: %s", path, g_strerror(errno)); + } + return; + } + + chain = g_hash_table_lookup(data->chains, chain_prefix); + + if (!chain) { + chain = g_new0(virLogCleanerChain, 1); + g_hash_table_insert(data->chains, g_steal_pointer((gpointer*) &chain_prefix), chain); + } + + chain->last_modified = MAX(chain->last_modified, sb->st_mtime); + chain->rotated_max_index = MAX(chain->rotated_max_index, + rotated_index); +} + +static GHashTable* +virLogCleanerCreateTable(virLogHandler *handler) +{ + /* HashTable: (const char*) chain_prefix -> (virLogCleanerChain*) chain */ + GHashTable *chains = g_hash_table_new_full(g_str_hash, g_str_equal, + g_free, g_free); + size_t i; + virLogHandlerLogFile *file; + const char *chain_prefix; + virLogCleanerChain *chain; + VIR_LOCK_GUARD lock = virObjectLockGuard(handler); + + for (i = 0; i < handler->nfiles; i++) { + file = handler->files[i]; + chain_prefix = virLogCleanerParseFilename(virRotatingFileWriterGetPath(file->file), + NULL); + if (!chain_prefix) + continue; + + chain = g_new0(virLogCleanerChain, 1); + chain->last_modified = MAX_TIME; /* Here we set MAX_TIME to the currently + * opened files to prevent its deletion. */ + g_hash_table_insert(chains, (void *) chain_prefix, chain); + } + + return chains; +} + +static void +virLogCleanerProcessFolder(virLogCleanerData *data, + const char *path, + int depth_left) +{ + DIR *dir; + struct dirent *entry; + struct stat sb; + + if (virDirOpenIfExists(&dir, path) < 0) + return; + + while (virDirRead(dir, &entry, path) > 0) { + g_autofree char *newpath = NULL; + + newpath = g_strdup_printf("%s/%s", path, entry->d_name); + + if (stat(newpath, &sb) < 0) { + VIR_WARN("Unable to stat %s: %s", newpath, g_strerror(errno)); + continue; + } + + if (S_ISDIR(sb.st_mode)) { + if (depth_left > 0) + virLogCleanerProcessFolder(data, newpath, depth_left - 1); + continue; + } + + virLogCleanerProcessFile(data, newpath, &sb); + } + + virDirClose(dir); +} + +static void +virLogCleanerDeleteFile(char *path) +{ + int rc; + if ((rc = unlink(path)) < 0 && errno != ENOENT) + VIR_WARN("Unable to delete %s: %s", path, g_strerror(errno)); + VIR_FREE(path); +} + +static void +virLogCleanerChainCB(gpointer key, + gpointer value, + gpointer user_data) +{ + const char* chain_prefix = key; + virLogCleanerChain* chain = value; + virLogCleanerData* data = user_data; + size_t i; + + if (chain->last_modified > data->oldest_to_keep) + return; + + virLogCleanerDeleteFile(g_strdup_printf("%s.log", chain_prefix)); + + for (i = 0; i <= chain->rotated_max_index; i++) + virLogCleanerDeleteFile(g_strdup_printf("%s.log.%ld", chain_prefix, i)); +} + +static void +virLogCleanerTimer(int timer G_GNUC_UNUSED, void *opaque) +{ + virLogHandler *handler = opaque; + virLogCleanerData data = { + .handler = handler, + .oldest_to_keep = time(NULL) - 3600 * 24 * handler->config->max_age_days, + .chains = virLogCleanerCreateTable(handler), + }; + + /* First prepare the hashmap of chains to delete */ + virLogCleanerProcessFolder(&data, + handler->config->log_root, + CLEANER_LOG_DEPTH); + g_hash_table_foreach(data.chains, virLogCleanerChainCB, &data); + g_hash_table_destroy(data.chains); +} + +int +virLogCleanerInit(virLogHandler *handler) +{ + if (handler->config->max_age_days <= 0) + return 0; + + log_regex = g_regex_new("^(.*)\\.log(\\.(\\d+))?$", 0, 0, NULL); + if (!log_regex) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("Unable to compile regex")); + return -1; + } + + handler->cleanup_log_timer = virEventAddTimeout(CLEANER_LOG_TIMEOUT_MS, + virLogCleanerTimer, + handler, NULL); + return handler->cleanup_log_timer; +} + +void +virLogCleanerShutdown(virLogHandler *handler) +{ + if (handler->cleanup_log_timer != -1) { + virEventRemoveTimeout(handler->cleanup_log_timer); + handler->cleanup_log_timer = -1; + } + + g_regex_unref(log_regex); + log_regex = NULL; +} diff --git a/src/logging/log_cleaner.h b/src/logging/log_cleaner.h new file mode 100644 index 0000000000..1a9a94b68d --- /dev/null +++ b/src/logging/log_cleaner.h @@ -0,0 +1,29 @@ +/* + * log_cleaner.h: cleans obsolete log files + * + * Copyright (C) 2022 Virtuozzo International GmbH + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; If not, see + * <http://www.gnu.org/licenses/>. + */ + +#pragma once + +#include "log_handler.h" + +int +virLogCleanerInit(virLogHandler *handler); + +void +virLogCleanerShutdown(virLogHandler *handler); diff --git a/src/logging/log_handler.h b/src/logging/log_handler.h index d473a19fc6..97dad27eda 100644 --- a/src/logging/log_handler.h +++ b/src/logging/log_handler.h @@ -48,6 +48,8 @@ struct _virLogHandler { bool privileged; virLogDaemonConfig *config; + int cleanup_log_timer; + virLogHandlerLogFile **files; size_t nfiles; diff --git a/src/logging/meson.build b/src/logging/meson.build index 7066f16fad..fa6af50fa6 100644 --- a/src/logging/meson.build +++ b/src/logging/meson.build @@ -30,6 +30,7 @@ log_daemon_sources = files( 'log_daemon_config.c', 'log_daemon_dispatch.c', 'log_handler.c', + 'log_cleaner.c', ) if conf.has('WITH_REMOTE') -- 2.39.1

On Mon, Jan 30, 2023 at 09:00:01PM +0600, Oleg Vasilev wrote:
Before, logs from deleted machines have been piling up, since there were no garbage collection mechanism. Now, virtlogd can be configured to periodically scan the log folder for orphan logs with no recent modifications and delete it.
A single chain of recent and rotated logs is deleted in a single transaction.
Signed-off-by: Oleg Vasilev <oleg.vasilev@virtuozzo.com> --- po/POTFILES | 1 + src/logging/log_cleaner.c | 268 ++++++++++++++++++++++++++++++++++++++ src/logging/log_cleaner.h | 29 +++++ src/logging/log_handler.h | 2 + src/logging/meson.build | 1 + 5 files changed, 301 insertions(+) create mode 100644 src/logging/log_cleaner.c create mode 100644 src/logging/log_cleaner.h
diff --git a/po/POTFILES b/po/POTFILES index 169e2a41dc..2fb6d18e27 100644 --- a/po/POTFILES +++ b/po/POTFILES @@ -123,6 +123,7 @@ src/locking/lock_driver_lockd.c src/locking/lock_driver_sanlock.c src/locking/lock_manager.c src/locking/sanlock_helper.c +src/logging/log_cleaner.c src/logging/log_daemon.c src/logging/log_daemon_dispatch.c src/logging/log_handler.c diff --git a/src/logging/log_cleaner.c b/src/logging/log_cleaner.c new file mode 100644 index 0000000000..3de54d0795 --- /dev/null +++ b/src/logging/log_cleaner.c @@ -0,0 +1,268 @@ +/* + * log_cleaner.c: cleans obsolete log files + * + * Copyright (C) 2022 Virtuozzo International GmbH + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; If not, see + * <http://www.gnu.org/licenses/>. + */ + +#include <config.h> + +#include "log_cleaner.h" +#include "log_handler.h" + +#include "virerror.h" +#include "virobject.h" +#include "virfile.h" +#include "viralloc.h" +#include "virlog.h" +#include "virrotatingfile.h" +#include "virstring.h" + +#define VIR_FROM_THIS VIR_FROM_LOGGING + +VIR_LOG_INIT("logging.log_cleaner"); + +/* Cleanup log root (/var/log/libvirt) and all subfolders (e.g. /var/log/libvirt/qemu) */ +#define CLEANER_LOG_DEPTH 1 +#define CLEANER_LOG_TIMEOUT_MS (24 * 3600 * 1000) /* One day */ +#define MAX_TIME ((time_t) G_MAXINT64) + +static GRegex *log_regex; + +typedef struct _virLogCleanerChain virLogCleanerChain; +struct _virLogCleanerChain { + int rotated_max_index; + time_t last_modified; +}; + +typedef struct _virLogCleanerData virLogCleanerData; +struct _virLogCleanerData { + virLogHandler *handler; + time_t oldest_to_keep; + GHashTable *chains; +}; + +static const char*
This does not return a const char *, just char *, also the space is off.
+virLogCleanerParseFilename(const char *path, + int *rotated_index) +{ + g_autoptr(GMatchInfo) matchInfo = NULL; + g_autofree const char *rotated_index_str = NULL; + g_autofree const char *clear_path = NULL; + const char *chain_prefix = NULL;
None of these is const.
+ + clear_path = realpath(path, NULL); + if (!clear_path) { + VIR_WARN("Failed to resolve path %s: %s", path, g_strerror(errno)); + return NULL; + } + + if (!g_regex_match(log_regex, path, 0, &matchInfo)) + return NULL; + + chain_prefix = g_match_info_fetch(matchInfo, 1); + if (!rotated_index) + return chain_prefix; + + *rotated_index = 0; + rotated_index_str = g_match_info_fetch(matchInfo, 3); + + if (!rotated_index_str) + return chain_prefix; + + if (virStrToLong_i(rotated_index_str, NULL, 10, rotated_index) < 0) { + virReportError(VIR_ERR_INTERNAL_ERROR, + _("Failed to parse rotated index from '%s'"), + rotated_index_str); + return NULL; + } + return chain_prefix; +} + +static void +virLogCleanerProcessFile(virLogCleanerData *data, + const char *path, + struct stat *sb) +{ + int rotated_index = 0; + g_autofree const char *chain_prefix = NULL; + virLogCleanerChain *chain; + + if (!S_ISREG(sb->st_mode)) + return; + + chain_prefix = virLogCleanerParseFilename(path, &rotated_index); + + if (!chain_prefix) + return; + + if (rotated_index > data->handler->config->max_backups) { + if (unlink(path) < 0) { + VIR_WARN("Unable to delete %s: %s", path, g_strerror(errno)); + }
There's a better function for this you are adding later.
+ return; + } + + chain = g_hash_table_lookup(data->chains, chain_prefix); + + if (!chain) { + chain = g_new0(virLogCleanerChain, 1); + g_hash_table_insert(data->chains, g_steal_pointer((gpointer*) &chain_prefix), chain);
With the removal of the consts this cast can be removed.
+ } + + chain->last_modified = MAX(chain->last_modified, sb->st_mtime); + chain->rotated_max_index = MAX(chain->rotated_max_index, + rotated_index); +} + +static GHashTable*
Missing space before asterisk.
+virLogCleanerCreateTable(virLogHandler *handler) +{ + /* HashTable: (const char*) chain_prefix -> (virLogCleanerChain*) chain */ + GHashTable *chains = g_hash_table_new_full(g_str_hash, g_str_equal, + g_free, g_free); + size_t i; + virLogHandlerLogFile *file; + const char *chain_prefix;
Not a const.
+ virLogCleanerChain *chain; + VIR_LOCK_GUARD lock = virObjectLockGuard(handler); + + for (i = 0; i < handler->nfiles; i++) { + file = handler->files[i]; + chain_prefix = virLogCleanerParseFilename(virRotatingFileWriterGetPath(file->file), + NULL); + if (!chain_prefix) + continue; + + chain = g_new0(virLogCleanerChain, 1); + chain->last_modified = MAX_TIME; /* Here we set MAX_TIME to the currently + * opened files to prevent its deletion. */ + g_hash_table_insert(chains, (void *) chain_prefix, chain);
Again, cast can be removed.
+ } + + return chains; +} + +static void +virLogCleanerProcessFolder(virLogCleanerData *data, + const char *path, + int depth_left) +{ + DIR *dir; + struct dirent *entry; + struct stat sb; + + if (virDirOpenIfExists(&dir, path) < 0) + return; + + while (virDirRead(dir, &entry, path) > 0) { + g_autofree char *newpath = NULL; + + newpath = g_strdup_printf("%s/%s", path, entry->d_name); + + if (stat(newpath, &sb) < 0) { + VIR_WARN("Unable to stat %s: %s", newpath, g_strerror(errno)); + continue; + } + + if (S_ISDIR(sb.st_mode)) { + if (depth_left > 0) + virLogCleanerProcessFolder(data, newpath, depth_left - 1); + continue; + } + + virLogCleanerProcessFile(data, newpath, &sb); + } + + virDirClose(dir); +} + +static void +virLogCleanerDeleteFile(char *path) +{ + int rc;
Pointless variable.
+ if ((rc = unlink(path)) < 0 && errno != ENOENT) + VIR_WARN("Unable to delete %s: %s", path, g_strerror(errno)); + VIR_FREE(path); +} + +static void +virLogCleanerChainCB(gpointer key, + gpointer value, + gpointer user_data) +{ + const char* chain_prefix = key;
Not const.
+ virLogCleanerChain* chain = value; + virLogCleanerData* data = user_data;
Spacing is off. Rest is fine, I'll adjust the above before pushing.

On 03.02.2023 19:45, Martin Kletzander wrote:
On Mon, Jan 30, 2023 at 09:00:01PM +0600, Oleg Vasilev wrote:
Before, logs from deleted machines have been piling up, since there were no garbage collection mechanism. Now, virtlogd can be configured to periodically scan the log folder for orphan logs with no recent modifications and delete it.
A single chain of recent and rotated logs is deleted in a single transaction.
Signed-off-by: Oleg Vasilev <oleg.vasilev@virtuozzo.com> --- po/POTFILES | 1 + src/logging/log_cleaner.c | 268 ++++++++++++++++++++++++++++++++++++++ src/logging/log_cleaner.h | 29 +++++ src/logging/log_handler.h | 2 + src/logging/meson.build | 1 + 5 files changed, 301 insertions(+) create mode 100644 src/logging/log_cleaner.c create mode 100644 src/logging/log_cleaner.h
diff --git a/po/POTFILES b/po/POTFILES index 169e2a41dc..2fb6d18e27 100644 --- a/po/POTFILES +++ b/po/POTFILES @@ -123,6 +123,7 @@ src/locking/lock_driver_lockd.c src/locking/lock_driver_sanlock.c src/locking/lock_manager.c src/locking/sanlock_helper.c +src/logging/log_cleaner.c src/logging/log_daemon.c src/logging/log_daemon_dispatch.c src/logging/log_handler.c diff --git a/src/logging/log_cleaner.c b/src/logging/log_cleaner.c new file mode 100644 index 0000000000..3de54d0795 --- /dev/null +++ b/src/logging/log_cleaner.c @@ -0,0 +1,268 @@ +/* + * log_cleaner.c: cleans obsolete log files + * + * Copyright (C) 2022 Virtuozzo International GmbH + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; If not, see + * <http://www.gnu.org/licenses/>. + */ + +#include <config.h> + +#include "log_cleaner.h" +#include "log_handler.h" + +#include "virerror.h" +#include "virobject.h" +#include "virfile.h" +#include "viralloc.h" +#include "virlog.h" +#include "virrotatingfile.h" +#include "virstring.h" + +#define VIR_FROM_THIS VIR_FROM_LOGGING + +VIR_LOG_INIT("logging.log_cleaner"); + +/* Cleanup log root (/var/log/libvirt) and all subfolders (e.g. /var/log/libvirt/qemu) */ +#define CLEANER_LOG_DEPTH 1 +#define CLEANER_LOG_TIMEOUT_MS (24 * 3600 * 1000) /* One day */ +#define MAX_TIME ((time_t) G_MAXINT64) + +static GRegex *log_regex; + +typedef struct _virLogCleanerChain virLogCleanerChain; +struct _virLogCleanerChain { + int rotated_max_index; + time_t last_modified; +}; + +typedef struct _virLogCleanerData virLogCleanerData; +struct _virLogCleanerData { + virLogHandler *handler; + time_t oldest_to_keep; + GHashTable *chains; +}; + +static const char*
This does not return a const char *, just char *, also the space is off.
+virLogCleanerParseFilename(const char *path, + int *rotated_index) +{ + g_autoptr(GMatchInfo) matchInfo = NULL; + g_autofree const char *rotated_index_str = NULL; + g_autofree const char *clear_path = NULL; + const char *chain_prefix = NULL;
None of these is const.
Just to educate myself, why are these not const? These are only set and not changed. There is of course the issue with type erasure, which requires the cast, but that I consider the limitation of GHashTable API. Or should I never attempt to put a const value into a type-erased void*? Also, I see a number of tasks failed in a pipeline because of missing unlink definition. Probably I forgot #include <unistd.h>. Should have I tested the patch somehow else before submitting, apart from running test on the machine I had at hand, e.g., have my own GitLab pipeline setup? Thanks, Oleg
+ + clear_path = realpath(path, NULL); + if (!clear_path) { + VIR_WARN("Failed to resolve path %s: %s", path, g_strerror(errno)); + return NULL; + } + + if (!g_regex_match(log_regex, path, 0, &matchInfo)) + return NULL; + + chain_prefix = g_match_info_fetch(matchInfo, 1); + if (!rotated_index) + return chain_prefix; + + *rotated_index = 0; + rotated_index_str = g_match_info_fetch(matchInfo, 3); + + if (!rotated_index_str) + return chain_prefix; + + if (virStrToLong_i(rotated_index_str, NULL, 10, rotated_index) < 0) { + virReportError(VIR_ERR_INTERNAL_ERROR, + _("Failed to parse rotated index from '%s'"), + rotated_index_str); + return NULL; + } + return chain_prefix; +} + +static void +virLogCleanerProcessFile(virLogCleanerData *data, + const char *path, + struct stat *sb) +{ + int rotated_index = 0; + g_autofree const char *chain_prefix = NULL; + virLogCleanerChain *chain; + + if (!S_ISREG(sb->st_mode)) + return; + + chain_prefix = virLogCleanerParseFilename(path, &rotated_index); + + if (!chain_prefix) + return; + + if (rotated_index > data->handler->config->max_backups) { + if (unlink(path) < 0) { + VIR_WARN("Unable to delete %s: %s", path, g_strerror(errno)); + }
There's a better function for this you are adding later.
+ return; + } + + chain = g_hash_table_lookup(data->chains, chain_prefix); + + if (!chain) { + chain = g_new0(virLogCleanerChain, 1); + g_hash_table_insert(data->chains, g_steal_pointer((gpointer*) &chain_prefix), chain);
With the removal of the consts this cast can be removed.
+ } + + chain->last_modified = MAX(chain->last_modified, sb->st_mtime); + chain->rotated_max_index = MAX(chain->rotated_max_index, + rotated_index); +} + +static GHashTable*
Missing space before asterisk.
+virLogCleanerCreateTable(virLogHandler *handler) +{ + /* HashTable: (const char*) chain_prefix -> (virLogCleanerChain*) chain */ + GHashTable *chains = g_hash_table_new_full(g_str_hash, g_str_equal, + g_free, g_free); + size_t i; + virLogHandlerLogFile *file; + const char *chain_prefix;
Not a const.
+ virLogCleanerChain *chain; + VIR_LOCK_GUARD lock = virObjectLockGuard(handler); + + for (i = 0; i < handler->nfiles; i++) { + file = handler->files[i]; + chain_prefix = virLogCleanerParseFilename(virRotatingFileWriterGetPath(file->file), + NULL); + if (!chain_prefix) + continue; + + chain = g_new0(virLogCleanerChain, 1); + chain->last_modified = MAX_TIME; /* Here we set MAX_TIME to the currently + * opened files to prevent its deletion. */ + g_hash_table_insert(chains, (void *) chain_prefix, chain);
Again, cast can be removed.
+ } + + return chains; +} + +static void +virLogCleanerProcessFolder(virLogCleanerData *data, + const char *path, + int depth_left) +{ + DIR *dir; + struct dirent *entry; + struct stat sb; + + if (virDirOpenIfExists(&dir, path) < 0) + return; + + while (virDirRead(dir, &entry, path) > 0) { + g_autofree char *newpath = NULL; + + newpath = g_strdup_printf("%s/%s", path, entry->d_name); + + if (stat(newpath, &sb) < 0) { + VIR_WARN("Unable to stat %s: %s", newpath, g_strerror(errno)); + continue; + } + + if (S_ISDIR(sb.st_mode)) { + if (depth_left > 0) + virLogCleanerProcessFolder(data, newpath, depth_left - 1); + continue; + } + + virLogCleanerProcessFile(data, newpath, &sb); + } + + virDirClose(dir); +} + +static void +virLogCleanerDeleteFile(char *path) +{ + int rc;
Pointless variable.
+ if ((rc = unlink(path)) < 0 && errno != ENOENT) + VIR_WARN("Unable to delete %s: %s", path, g_strerror(errno)); + VIR_FREE(path); +} + +static void +virLogCleanerChainCB(gpointer key, + gpointer value, + gpointer user_data) +{ + const char* chain_prefix = key;
Not const.
+ virLogCleanerChain* chain = value; + virLogCleanerData* data = user_data;
Spacing is off.
Rest is fine, I'll adjust the above before pushing.

On Mon, Feb 06, 2023 at 01:26:09PM +0600, Oleg Vasilev wrote:
On 03.02.2023 19:45, Martin Kletzander wrote:
On Mon, Jan 30, 2023 at 09:00:01PM +0600, Oleg Vasilev wrote:
Before, logs from deleted machines have been piling up, since there were no garbage collection mechanism. Now, virtlogd can be configured to periodically scan the log folder for orphan logs with no recent modifications and delete it.
A single chain of recent and rotated logs is deleted in a single transaction.
Signed-off-by: Oleg Vasilev <oleg.vasilev@virtuozzo.com> --- po/POTFILES | 1 + src/logging/log_cleaner.c | 268 ++++++++++++++++++++++++++++++++++++++ src/logging/log_cleaner.h | 29 +++++ src/logging/log_handler.h | 2 + src/logging/meson.build | 1 + 5 files changed, 301 insertions(+) create mode 100644 src/logging/log_cleaner.c create mode 100644 src/logging/log_cleaner.h
diff --git a/po/POTFILES b/po/POTFILES index 169e2a41dc..2fb6d18e27 100644 --- a/po/POTFILES +++ b/po/POTFILES @@ -123,6 +123,7 @@ src/locking/lock_driver_lockd.c src/locking/lock_driver_sanlock.c src/locking/lock_manager.c src/locking/sanlock_helper.c +src/logging/log_cleaner.c src/logging/log_daemon.c src/logging/log_daemon_dispatch.c src/logging/log_handler.c diff --git a/src/logging/log_cleaner.c b/src/logging/log_cleaner.c new file mode 100644 index 0000000000..3de54d0795 --- /dev/null +++ b/src/logging/log_cleaner.c @@ -0,0 +1,268 @@ +/* + * log_cleaner.c: cleans obsolete log files + * + * Copyright (C) 2022 Virtuozzo International GmbH + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; If not, see + * <http://www.gnu.org/licenses/>. + */ + +#include <config.h> + +#include "log_cleaner.h" +#include "log_handler.h" + +#include "virerror.h" +#include "virobject.h" +#include "virfile.h" +#include "viralloc.h" +#include "virlog.h" +#include "virrotatingfile.h" +#include "virstring.h" + +#define VIR_FROM_THIS VIR_FROM_LOGGING + +VIR_LOG_INIT("logging.log_cleaner"); + +/* Cleanup log root (/var/log/libvirt) and all subfolders (e.g. /var/log/libvirt/qemu) */ +#define CLEANER_LOG_DEPTH 1 +#define CLEANER_LOG_TIMEOUT_MS (24 * 3600 * 1000) /* One day */ +#define MAX_TIME ((time_t) G_MAXINT64) + +static GRegex *log_regex; + +typedef struct _virLogCleanerChain virLogCleanerChain; +struct _virLogCleanerChain { + int rotated_max_index; + time_t last_modified; +}; + +typedef struct _virLogCleanerData virLogCleanerData; +struct _virLogCleanerData { + virLogHandler *handler; + time_t oldest_to_keep; + GHashTable *chains; +}; + +static const char*
This does not return a const char *, just char *, also the space is off.
+virLogCleanerParseFilename(const char *path, + int *rotated_index) +{ + g_autoptr(GMatchInfo) matchInfo = NULL; + g_autofree const char *rotated_index_str = NULL; + g_autofree const char *clear_path = NULL; + const char *chain_prefix = NULL;
None of these is const.
Just to educate myself, why are these not const? These are only set and not changed.
These strings are free()'d and that method doesn't accept 'const'. It happens to be ok when using g_autofree because that discards the const. It is surprising to reviewers since a 'const' declaration is typically taken to indicate the variabled does not need free'ing. With regards, Daniel -- |: https://berrange.com -o- https://www.flickr.com/photos/dberrange :| |: https://libvirt.org -o- https://fstop138.berrange.com :| |: https://entangle-photo.org -o- https://www.instagram.com/dberrange :|

On Mon, Feb 06, 2023 at 01:26:09PM +0600, Oleg Vasilev wrote:
On 03.02.2023 19:45, Martin Kletzander wrote:
On Mon, Jan 30, 2023 at 09:00:01PM +0600, Oleg Vasilev wrote:
Before, logs from deleted machines have been piling up, since there were no garbage collection mechanism. Now, virtlogd can be configured to periodically scan the log folder for orphan logs with no recent modifications and delete it.
A single chain of recent and rotated logs is deleted in a single transaction.
Signed-off-by: Oleg Vasilev <oleg.vasilev@virtuozzo.com> --- po/POTFILES | 1 + src/logging/log_cleaner.c | 268 ++++++++++++++++++++++++++++++++++++++ src/logging/log_cleaner.h | 29 +++++ src/logging/log_handler.h | 2 + src/logging/meson.build | 1 + 5 files changed, 301 insertions(+) create mode 100644 src/logging/log_cleaner.c create mode 100644 src/logging/log_cleaner.h
diff --git a/po/POTFILES b/po/POTFILES index 169e2a41dc..2fb6d18e27 100644 --- a/po/POTFILES +++ b/po/POTFILES @@ -123,6 +123,7 @@ src/locking/lock_driver_lockd.c src/locking/lock_driver_sanlock.c src/locking/lock_manager.c src/locking/sanlock_helper.c +src/logging/log_cleaner.c src/logging/log_daemon.c src/logging/log_daemon_dispatch.c src/logging/log_handler.c diff --git a/src/logging/log_cleaner.c b/src/logging/log_cleaner.c new file mode 100644 index 0000000000..3de54d0795 --- /dev/null +++ b/src/logging/log_cleaner.c @@ -0,0 +1,268 @@ +/* + * log_cleaner.c: cleans obsolete log files + * + * Copyright (C) 2022 Virtuozzo International GmbH + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; If not, see + * <http://www.gnu.org/licenses/>. + */ + +#include <config.h> + +#include "log_cleaner.h" +#include "log_handler.h" + +#include "virerror.h" +#include "virobject.h" +#include "virfile.h" +#include "viralloc.h" +#include "virlog.h" +#include "virrotatingfile.h" +#include "virstring.h" + +#define VIR_FROM_THIS VIR_FROM_LOGGING + +VIR_LOG_INIT("logging.log_cleaner"); + +/* Cleanup log root (/var/log/libvirt) and all subfolders (e.g. /var/log/libvirt/qemu) */ +#define CLEANER_LOG_DEPTH 1 +#define CLEANER_LOG_TIMEOUT_MS (24 * 3600 * 1000) /* One day */ +#define MAX_TIME ((time_t) G_MAXINT64) + +static GRegex *log_regex; + +typedef struct _virLogCleanerChain virLogCleanerChain; +struct _virLogCleanerChain { + int rotated_max_index; + time_t last_modified; +}; + +typedef struct _virLogCleanerData virLogCleanerData; +struct _virLogCleanerData { + virLogHandler *handler; + time_t oldest_to_keep; + GHashTable *chains; +}; + +static const char*
This does not return a const char *, just char *, also the space is off.
+virLogCleanerParseFilename(const char *path, + int *rotated_index) +{ + g_autoptr(GMatchInfo) matchInfo = NULL; + g_autofree const char *rotated_index_str = NULL; + g_autofree const char *clear_path = NULL; + const char *chain_prefix = NULL;
None of these is const.
Just to educate myself, why are these not const? These are only set and not changed.
They are allocated and free'd.
There is of course the issue with type erasure, which requires the cast, but that I consider the limitation of GHashTable API. Or should I never attempt to put a const value into a type-erased void*?
Also, I see a number of tasks failed in a pipeline because of missing unlink definition. Probably I forgot #include <unistd.h>. Should have I tested the patch somehow else before submitting, apart from running test on the machine I had at hand, e.g., have my own GitLab pipeline setup?
It's definitely beneficial to test this. There are various ways, we have documented here: https://libvirt.org/testing.html I pushed it upstream.

Actually use the log cleaner introduced by previous commit. Signed-off-by: Oleg Vasilev <oleg.vasilev@virtuozzo.com> --- src/logging/log_handler.c | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/logging/log_handler.c b/src/logging/log_handler.c index 8fc7e9b2a8..a739211c7c 100644 --- a/src/logging/log_handler.c +++ b/src/logging/log_handler.c @@ -21,6 +21,7 @@ #include <config.h> #include "log_handler.h" +#include "log_cleaner.h" #include "virerror.h" #include "virfile.h" #include "viralloc.h" @@ -175,7 +176,15 @@ virLogHandlerNew(bool privileged, handler->inhibitor = inhibitor; handler->opaque = opaque; + if (virLogCleanerInit(handler) < 0) { + goto error; + } + return handler; + + error: + virObjectUnref(handler); + return NULL; } @@ -313,6 +322,8 @@ virLogHandlerDispose(void *obj) virLogHandler *handler = obj; size_t i; + virLogCleanerShutdown(handler); + for (i = 0; i < handler->nfiles; i++) { handler->inhibitor(false, handler->opaque); virLogHandlerLogFileFree(handler->files[i]); -- 2.39.1

On Mon, Jan 30, 2023 at 08:59:57PM +0600, Oleg Vasilev wrote:
Presently, logs from deleted domains remain forever. Particular motivation comes from the case when libguestfs has repeatedly created transient VMs, which in turn created plenty of logs. This takes up space and lots of files troubles filesystem navigation.
More motivation in [1]. Patch solving same problem in [2].
Changes in v3: codestyle cleanup, minor fixes
Changes in v2: substantial rework according to Martin Kletzander's comments
v1: https://www.mail-archive.com/libvir-list@redhat.com/msg233754.html
v2: https://www.spinics.net/linux/fedora/libvir/msg236081.html
[1]: https://listman.redhat.com/archives/libvir-list/2022-February/228149.html
[2]: https://listman.redhat.com/archives/libvir-list/2022-February/msg00865.html
CC: Martin Kletzander <mkletzan@redhat.com>
Oleg Vasilev (5): logging: refactor to store config inside log handler logging: move virLogHandler to header logging: add configuration for future log cleaner logging: add log cleanup for obsolete domains logging: use the log cleaner
Reviewed-by: Martin Kletzander <mkletzan@redhat.com> and I will push it once the pipeline finishes because I found a few more issues in there on top of fixes mentioned in 4/5: https://gitlab.com/nertpinx/libvirt/-/pipelines/766719872
po/POTFILES | 1 + src/logging/log_cleaner.c | 268 +++++++++++++++++++++++++++++++ src/logging/log_cleaner.h | 29 ++++ src/logging/log_daemon.c | 6 +- src/logging/log_daemon_config.c | 9 ++ src/logging/log_daemon_config.h | 3 + src/logging/log_handler.c | 64 +++----- src/logging/log_handler.h | 50 ++++-- src/logging/meson.build | 1 + src/logging/test_virtlogd.aug.in | 2 + src/logging/virtlogd.aug | 2 + src/logging/virtlogd.conf | 14 ++ 12 files changed, 391 insertions(+), 58 deletions(-) create mode 100644 src/logging/log_cleaner.c create mode 100644 src/logging/log_cleaner.h
-- 2.39.1
participants (3)
-
Daniel P. Berrangé
-
Martin Kletzander
-
Oleg Vasilev