mirror of
https://github.com/rsyslog/rsyslog.git
synced 2026-04-23 13:48:12 +02:00
impstats: add log.file.overwrite parameter for atomic overwrites
This change adds the capability to overwrite the statistics log file instead of appending to it. This is particularly useful for observability tools like Prometheus scraping sidecars or node exporter, which expect a consistent and complete set of metrics in a single file. The implementation ensures atomicity by writing the statistics to a temporary file and then renaming it to the final destination. This prevents reader processes from seeing partial or inconsistent data during the emission process. This commit includes: - The implementation in impstats.c. - New test cases in the testbench. - User-facing documentation for the new parameter. Impact: Users can now enable atomic overwrites using log.file.overwrite="on". Default behavior remains append. Refs: no issue AI-Agent: Antigravity
This commit is contained in:
parent
853165bf29
commit
280fde6164
@ -103,6 +103,10 @@ Module Parameters
|
||||
- .. include:: ../../reference/parameters/impstats-log-file.rst
|
||||
:start-after: .. summary-start
|
||||
:end-before: .. summary-end
|
||||
* - :ref:`param-impstats-log-file-overwrite`
|
||||
- .. include:: ../../reference/parameters/impstats-log-file-overwrite.rst
|
||||
:start-after: .. summary-start
|
||||
:end-before: .. summary-end
|
||||
* - :ref:`param-impstats-ruleset`
|
||||
- .. include:: ../../reference/parameters/impstats-ruleset.rst
|
||||
:start-after: .. summary-start
|
||||
@ -294,5 +298,6 @@ See Also
|
||||
../../reference/parameters/impstats-format
|
||||
../../reference/parameters/impstats-log-syslog
|
||||
../../reference/parameters/impstats-log-file
|
||||
../../reference/parameters/impstats-log-file-overwrite
|
||||
../../reference/parameters/impstats-ruleset
|
||||
../../reference/parameters/impstats-bracketing
|
||||
|
||||
@ -0,0 +1,52 @@
|
||||
.. _param-impstats-log-file-overwrite:
|
||||
.. _impstats.parameter.module.log-file-overwrite:
|
||||
|
||||
log.file.overwrite
|
||||
==================
|
||||
|
||||
.. index::
|
||||
single: impstats; log.file.overwrite
|
||||
single: log.file.overwrite
|
||||
|
||||
.. summary-start
|
||||
|
||||
If set to "on", the statistics log file specified by :ref:`param-impstats-log-file`
|
||||
is overwritten with each emission instead of being appended to.
|
||||
|
||||
.. summary-end
|
||||
|
||||
This parameter applies to :doc:`../../configuration/modules/impstats`.
|
||||
|
||||
:Name: log.file.overwrite
|
||||
:Scope: module
|
||||
:Type: binary
|
||||
:Default: off
|
||||
:Required?: no
|
||||
:Introduced: 8.2602.0
|
||||
|
||||
Description
|
||||
-----------
|
||||
When this parameter is set to ``on``, rsyslog will overwrite the file specified
|
||||
in ``log.file`` every time it emits statistics. This is useful for external
|
||||
monitoring tools (like Prometheus sidecars or node exporter) that expect to
|
||||
read a single, consistent set of metrics from a file.
|
||||
|
||||
To ensure that reader processes always see a complete and consistent set of
|
||||
statistics, rsyslog writes the data to a temporary file first and then
|
||||
atomically renames it to the final destination.
|
||||
|
||||
Note that this parameter only has an effect if ``log.file`` is also specified.
|
||||
|
||||
Module usage
|
||||
------------
|
||||
.. _impstats.parameter.module.log-file-overwrite-usage:
|
||||
|
||||
.. code-block:: rsyslog
|
||||
|
||||
module(load="impstats"
|
||||
logFile="/var/log/rsyslog-stats"
|
||||
log.file.overwrite="on")
|
||||
|
||||
See also
|
||||
--------
|
||||
See also :doc:`../../configuration/modules/impstats`.
|
||||
@ -89,6 +89,7 @@ struct modConfData_s {
|
||||
sbool bLogToSyslog;
|
||||
sbool bResetCtrs;
|
||||
sbool bBracketing;
|
||||
sbool bLogOverwrite;
|
||||
char *logfile;
|
||||
sbool configSetViaV2Method;
|
||||
uchar *pszBindRuleset; /* name of ruleset to bind to */
|
||||
@ -102,9 +103,11 @@ static prop_t *pInputName = NULL;
|
||||
|
||||
/* module-global parameters */
|
||||
static struct cnfparamdescr modpdescr[] = {
|
||||
{"interval", eCmdHdlrInt, 0}, {"facility", eCmdHdlrInt, 0}, {"severity", eCmdHdlrInt, 0},
|
||||
{"bracketing", eCmdHdlrBinary, 0}, {"log.syslog", eCmdHdlrBinary, 0}, {"resetcounters", eCmdHdlrBinary, 0},
|
||||
{"log.file", eCmdHdlrGetWord, 0}, {"format", eCmdHdlrGetWord, 0}, {"ruleset", eCmdHdlrString, 0}};
|
||||
{"interval", eCmdHdlrInt, 0}, {"facility", eCmdHdlrInt, 0},
|
||||
{"severity", eCmdHdlrInt, 0}, {"bracketing", eCmdHdlrBinary, 0},
|
||||
{"log.syslog", eCmdHdlrBinary, 0}, {"resetcounters", eCmdHdlrBinary, 0},
|
||||
{"log.file", eCmdHdlrGetWord, 0}, {"format", eCmdHdlrGetWord, 0},
|
||||
{"ruleset", eCmdHdlrString, 0}, {"log.file.overwrite", eCmdHdlrBinary, 0}};
|
||||
|
||||
static struct cnfparamblk modpblk = {CNFPARAMBLK_VERSION, sizeof(modpdescr) / sizeof(struct cnfparamdescr), modpdescr};
|
||||
|
||||
@ -204,6 +207,12 @@ static void doLogToFile(const char *ln, const size_t lenLn) {
|
||||
if (lenLn == 0) goto done;
|
||||
|
||||
if (runModConf->logfd == -1) {
|
||||
if (runModConf->bLogOverwrite) {
|
||||
/* If overwriting, the file should have been opened by the main loop.
|
||||
* If it's not open, we just skip logging to file.
|
||||
*/
|
||||
goto done;
|
||||
}
|
||||
runModConf->logfd = open(runModConf->logfile, O_WRONLY | O_CREAT | O_APPEND | O_CLOEXEC, S_IRUSR | S_IWUSR);
|
||||
if (runModConf->logfd == -1) {
|
||||
DBGPRINTF("impstats: error opening stats file %s\n", runModConf->logfile);
|
||||
@ -308,6 +317,7 @@ BEGINbeginCnfLoad
|
||||
loadModConf->bLogToSyslog = 1;
|
||||
loadModConf->bBracketing = 0;
|
||||
loadModConf->bResetCtrs = 0;
|
||||
loadModConf->bLogOverwrite = 0;
|
||||
bLegacyCnfModGlobalsPermitted = 1;
|
||||
/* init legacy config vars */
|
||||
initConfigSettings();
|
||||
@ -344,6 +354,8 @@ BEGINsetModCnf
|
||||
loadModConf->bResetCtrs = (sbool)pvals[i].val.d.n;
|
||||
} else if (!strcmp(modpblk.descr[i].name, "log.file")) {
|
||||
loadModConf->logfile = es_str2cstr(pvals[i].val.d.estr, NULL);
|
||||
} else if (!strcmp(modpblk.descr[i].name, "log.file.overwrite")) {
|
||||
loadModConf->bLogOverwrite = (sbool)pvals[i].val.d.n;
|
||||
} else if (!strcmp(modpblk.descr[i].name, "format")) {
|
||||
mode = es_str2cstr(pvals[i].val.d.estr, NULL);
|
||||
if (!strcasecmp(mode, "json")) {
|
||||
@ -441,9 +453,11 @@ BEGINdoHUP
|
||||
DBGPRINTF("impstats: received HUP\n");
|
||||
pthread_mutex_lock(&hup_mutex);
|
||||
if (runModConf->logfd != -1) {
|
||||
DBGPRINTF("impstats: closing log file due to HUP\n");
|
||||
close(runModConf->logfd);
|
||||
runModConf->logfd = -1;
|
||||
if (!runModConf->bLogOverwrite) {
|
||||
DBGPRINTF("impstats: closing log file due to HUP\n");
|
||||
close(runModConf->logfd);
|
||||
runModConf->logfd = -1;
|
||||
}
|
||||
}
|
||||
pthread_mutex_unlock(&hup_mutex);
|
||||
ENDdoHUP
|
||||
@ -525,9 +539,41 @@ BEGINrunInput
|
||||
while (glbl.GetGlobalInputTermState() == 0) {
|
||||
srSleep(runModConf->iStatsInterval, 0); /* seconds, micro seconds */
|
||||
DBGPRINTF("impstats: woke up, generating messages\n");
|
||||
|
||||
char *tmp_logfile = NULL;
|
||||
if (runModConf->bLogOverwrite && runModConf->logfile != NULL) {
|
||||
const size_t len_tmp_logfile = strlen(runModConf->logfile) + 5;
|
||||
if ((tmp_logfile = malloc(len_tmp_logfile)) == NULL) {
|
||||
LogError(errno, RS_RET_OUT_OF_MEMORY, "impstats: could not allocate memory for temp log file name");
|
||||
} else {
|
||||
snprintf(tmp_logfile, len_tmp_logfile, "%s.tmp", runModConf->logfile);
|
||||
pthread_mutex_lock(&hup_mutex);
|
||||
runModConf->logfd = open(tmp_logfile, O_WRONLY | O_CREAT | O_TRUNC | O_CLOEXEC, S_IRUSR | S_IWUSR);
|
||||
if (runModConf->logfd == -1) {
|
||||
LogError(errno, RS_RET_ERR, "impstats: error opening temp stats file %s", tmp_logfile);
|
||||
free(tmp_logfile);
|
||||
tmp_logfile = NULL;
|
||||
}
|
||||
pthread_mutex_unlock(&hup_mutex);
|
||||
}
|
||||
}
|
||||
|
||||
if (runModConf->bBracketing) submitLine("BEGIN", sizeof("BEGIN") - 1);
|
||||
generateStatsMsgs();
|
||||
if (runModConf->bBracketing) submitLine("END", sizeof("END") - 1);
|
||||
|
||||
if (tmp_logfile != NULL) {
|
||||
pthread_mutex_lock(&hup_mutex);
|
||||
close(runModConf->logfd);
|
||||
runModConf->logfd = -1;
|
||||
pthread_mutex_unlock(&hup_mutex);
|
||||
if (rename(tmp_logfile, runModConf->logfile) != 0) {
|
||||
LogError(errno, RS_RET_ERR, "impstats: error renaming temp stats file %s to %s", tmp_logfile,
|
||||
runModConf->logfile);
|
||||
unlink(tmp_logfile);
|
||||
}
|
||||
free(tmp_logfile);
|
||||
}
|
||||
}
|
||||
ENDrunInput
|
||||
|
||||
|
||||
@ -1277,6 +1277,8 @@ endif # ENABLE_REDIS_TESTS
|
||||
if ENABLE_IMPSTATS
|
||||
TESTS += \
|
||||
impstats-hup.sh \
|
||||
impstats-overwrite.sh \
|
||||
impstats-no-overwrite.sh \
|
||||
perctile-simple.sh \
|
||||
dynstats.sh \
|
||||
dynstats_overflow.sh \
|
||||
@ -3163,6 +3165,8 @@ EXTRA_DIST= \
|
||||
dynstats_reset.sh \
|
||||
dynstats_reset-vg.sh \
|
||||
impstats-hup.sh \
|
||||
impstats-overwrite.sh \
|
||||
impstats-no-overwrite.sh \
|
||||
dynstats.sh \
|
||||
dynstats-vg.sh \
|
||||
dynstats_prevent_premature_eviction.sh \
|
||||
|
||||
29
tests/impstats-no-overwrite.sh
Executable file
29
tests/impstats-no-overwrite.sh
Executable file
@ -0,0 +1,29 @@
|
||||
#!/bin/bash
|
||||
# test if impstats appends by default (no log.file.overwrite)
|
||||
# This file is part of the rsyslog project, released under ASL 2.0
|
||||
. ${srcdir:=.}/diag.sh init
|
||||
generate_conf
|
||||
add_conf '
|
||||
module(load="../plugins/impstats/.libs/impstats"
|
||||
log.file=`echo $RSYSLOG_OUT_LOG`
|
||||
interval="1")
|
||||
'
|
||||
startup
|
||||
# Wait for at least two emissions
|
||||
./msleep 2500
|
||||
shutdown_when_empty
|
||||
wait_shutdown
|
||||
|
||||
# Check how many times "resource-usage" appears in the log file.
|
||||
# It should appear at least twice.
|
||||
NUM_EMISSIONS=$(grep -c "resource-usage" $RSYSLOG_OUT_LOG)
|
||||
|
||||
echo "Number of resource-usage entries found: $NUM_EMISSIONS"
|
||||
|
||||
if [ "$NUM_EMISSIONS" -lt 2 ]; then
|
||||
echo "FAIL: expected at least 2 emissions in log file, but found $NUM_EMISSIONS"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "SUCCESS: impstats appended as expected"
|
||||
exit_test
|
||||
31
tests/impstats-overwrite.sh
Executable file
31
tests/impstats-overwrite.sh
Executable file
@ -0,0 +1,31 @@
|
||||
#!/bin/bash
|
||||
# test if log.file.overwrite works for impstats
|
||||
# This file is part of the rsyslog project, released under ASL 2.0
|
||||
. ${srcdir:=.}/diag.sh init
|
||||
generate_conf
|
||||
add_conf '
|
||||
module(load="../plugins/impstats/.libs/impstats"
|
||||
log.file=`echo $RSYSLOG_OUT_LOG`
|
||||
log.file.overwrite="on"
|
||||
interval="1")
|
||||
'
|
||||
startup
|
||||
# Wait for at least two emissions
|
||||
./msleep 2500
|
||||
shutdown_when_empty
|
||||
wait_shutdown
|
||||
|
||||
# Check how many times "resource-usage" appears in the log file.
|
||||
# It should appear exactly once if overwrite is working correctly.
|
||||
NUM_EMISSIONS=$(grep -c "resource-usage" $RSYSLOG_OUT_LOG)
|
||||
cat -n $RSYSLOG_OUT_LOG
|
||||
|
||||
echo "Number of resource-usage entries found: $NUM_EMISSIONS"
|
||||
|
||||
if [ "$NUM_EMISSIONS" -ne 1 ]; then
|
||||
echo "FAIL: expected 1 emission in log file, but found $NUM_EMISSIONS"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "SUCCESS: log.file.overwrite worked as expected"
|
||||
exit_test
|
||||
Loading…
x
Reference in New Issue
Block a user