mirror of
https://gitlab.com/libvirt/libvirt.git
synced 2024-12-22 13:45:38 +00:00
logging: add log cleanup for obsolete domains
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> Reviewed-by: Martin Kletzander <mkletzan@redhat.com>
This commit is contained in:
parent
e69a3d1a79
commit
69eeef5dfb
@ -122,6 +122,7 @@ src/locking/lock_driver_lockd.c
|
|||||||
src/locking/lock_driver_sanlock.c
|
src/locking/lock_driver_sanlock.c
|
||||||
src/locking/lock_manager.c
|
src/locking/lock_manager.c
|
||||||
src/locking/sanlock_helper.c
|
src/locking/sanlock_helper.c
|
||||||
|
src/logging/log_cleaner.c
|
||||||
src/logging/log_daemon.c
|
src/logging/log_daemon.c
|
||||||
src/logging/log_daemon_dispatch.c
|
src/logging/log_daemon_dispatch.c
|
||||||
src/logging/log_handler.c
|
src/logging/log_handler.c
|
||||||
|
269
src/logging/log_cleaner.c
Normal file
269
src/logging/log_cleaner.c
Normal file
@ -0,0 +1,269 @@
|
|||||||
|
/*
|
||||||
|
* 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 <unistd.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 char *
|
||||||
|
virLogCleanerParseFilename(const char *path,
|
||||||
|
int *rotated_index)
|
||||||
|
{
|
||||||
|
g_autoptr(GMatchInfo) matchInfo = NULL;
|
||||||
|
g_autofree char *rotated_index_str = NULL;
|
||||||
|
g_autofree char *clear_path = NULL;
|
||||||
|
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
|
||||||
|
virLogCleanerDeleteFile(const char *path)
|
||||||
|
{
|
||||||
|
if (unlink(path) < 0 && errno != ENOENT)
|
||||||
|
VIR_WARN("Unable to delete %s: %s", path, g_strerror(errno));
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
virLogCleanerProcessFile(virLogCleanerData *data,
|
||||||
|
const char *path,
|
||||||
|
struct stat *sb)
|
||||||
|
{
|
||||||
|
int rotated_index = 0;
|
||||||
|
g_autofree 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) {
|
||||||
|
virLogCleanerDeleteFile(path);
|
||||||
|
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(&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;
|
||||||
|
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, 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 = 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
|
||||||
|
virLogCleanerChainCB(gpointer key,
|
||||||
|
gpointer value,
|
||||||
|
gpointer user_data)
|
||||||
|
{
|
||||||
|
char *chain_prefix = key;
|
||||||
|
virLogCleanerChain *chain = value;
|
||||||
|
virLogCleanerData *data = user_data;
|
||||||
|
g_autofree char *path = NULL;
|
||||||
|
size_t i;
|
||||||
|
|
||||||
|
if (chain->last_modified > data->oldest_to_keep)
|
||||||
|
return;
|
||||||
|
|
||||||
|
path = g_strdup_printf("%s.log", chain_prefix);
|
||||||
|
virLogCleanerDeleteFile(path);
|
||||||
|
|
||||||
|
for (i = 0; i <= chain->rotated_max_index; i++) {
|
||||||
|
g_autofree char *rotated_path = g_strdup_printf("%s.%zu", path, i);
|
||||||
|
|
||||||
|
virLogCleanerDeleteFile(rotated_path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
29
src/logging/log_cleaner.h
Normal file
29
src/logging/log_cleaner.h
Normal file
@ -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);
|
@ -48,6 +48,8 @@ struct _virLogHandler {
|
|||||||
bool privileged;
|
bool privileged;
|
||||||
virLogDaemonConfig *config;
|
virLogDaemonConfig *config;
|
||||||
|
|
||||||
|
int cleanup_log_timer;
|
||||||
|
|
||||||
virLogHandlerLogFile **files;
|
virLogHandlerLogFile **files;
|
||||||
size_t nfiles;
|
size_t nfiles;
|
||||||
|
|
||||||
|
@ -30,6 +30,7 @@ log_daemon_sources = files(
|
|||||||
'log_daemon_config.c',
|
'log_daemon_config.c',
|
||||||
'log_daemon_dispatch.c',
|
'log_daemon_dispatch.c',
|
||||||
'log_handler.c',
|
'log_handler.c',
|
||||||
|
'log_cleaner.c',
|
||||||
)
|
)
|
||||||
|
|
||||||
if conf.has('WITH_REMOTE')
|
if conf.has('WITH_REMOTE')
|
||||||
|
Loading…
Reference in New Issue
Block a user