Rotate logs (#347)

* Reopen file/program outputs on SIGUSR1

When signaled with SIGUSR1, close and reopen file and program based
outputs. This is useful when combined with logrotate to rotate logs.

* Example logrotate config

Example logrotate config that relies on SIGUSR1 to rotate logs.

* Ensure options exist for all outputs

Options may not be provided for some outputs (like stdout), so create an
empty set of options in that case.
This commit is contained in:
Mark Stemm 2018-04-05 14:31:36 -07:00 committed by GitHub
parent a5daf8b058
commit 8389e44d7b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 117 additions and 23 deletions

7
examples/logrotate/falco Normal file
View File

@ -0,0 +1,7 @@
/var/log/falco-events.log {
rotate 5
size 1M
postrotate
/usr/bin/killall -USR1 falco
endscript
}

View File

@ -66,6 +66,10 @@ syslog_output:
# continuously written to, with each output message on its own
# line. If keep_alive is set to false, the file will be re-opened
# for each output message.
#
# Also, the file will be closed and reopened if falco is signaled with
# SIGUSR1.
file_output:
enabled: false
keep_alive: false
@ -86,7 +90,9 @@ stdout_output:
# continuously written to, with each output message on its own
# line. If keep_alive is set to false, the program will be re-spawned
# for each output message.
#
# Also, the program will be closed and reopened if falco is signaled with
# SIGUSR1.
program_output:
enabled: false
keep_alive: false

View File

@ -39,6 +39,8 @@ along with falco. If not, see <http://www.gnu.org/licenses/>.
#include "statsfilewriter.h"
bool g_terminate = false;
bool g_reopen_outputs = false;
//
// Helper functions
//
@ -47,6 +49,11 @@ static void signal_callback(int signal)
g_terminate = true;
}
static void reopen_outputs(int signal)
{
g_reopen_outputs = true;
}
//
// Program help
//
@ -171,6 +178,12 @@ uint64_t do_inspect(falco_engine *engine,
writer.handle();
if(g_reopen_outputs)
{
outputs->reopen_outputs();
g_reopen_outputs = false;
}
if (g_terminate)
{
break;
@ -589,6 +602,13 @@ int falco_init(int argc, char **argv)
goto exit;
}
if(signal(SIGUSR1, reopen_outputs) == SIG_ERR)
{
fprintf(stderr, "An error occurred while setting SIGUSR1 signal handler.\n");
result = EXIT_FAILURE;
goto exit;
}
if (scap_filename.size())
{
inspector->open(scap_filename);

View File

@ -142,3 +142,19 @@ void falco_outputs::handle_event(sinsp_evt *ev, string &rule, falco_common::prio
}
}
void falco_outputs::reopen_outputs()
{
lua_getglobal(m_ls, m_lua_output_reopen.c_str());
if(!lua_isfunction(m_ls, -1))
{
throw falco_exception("No function " + m_lua_output_reopen + " found. ");
}
if(lua_pcall(m_ls, 0, 0, 0) != 0)
{
const char* lerr = lua_tostring(m_ls, -1);
throw falco_exception(string(lerr));
}
}

View File

@ -53,6 +53,8 @@ public:
//
void handle_event(sinsp_evt *ev, std::string &rule, falco_common::priority_type priority, std::string &format);
void reopen_outputs();
private:
bool m_initialized;
@ -64,5 +66,6 @@ private:
std::string m_lua_add_output = "add_output";
std::string m_lua_output_event = "output_event";
std::string m_lua_output_cleanup = "output_cleanup";
std::string m_lua_output_reopen = "output_reopen";
std::string m_lua_main_filename = "output.lua";
};

View File

@ -20,8 +20,8 @@ local mod = {}
local outputs = {}
function mod.stdout(priority, priority_num, buffered, msg)
if buffered == 0 then
function mod.stdout(priority, priority_num, msg, options)
if options.buffered == 0 then
io.stdout:setvbuf 'no'
end
print (msg)
@ -31,6 +31,10 @@ function mod.stdout_cleanup()
io.stdout:flush()
end
-- Note: not actually closing/reopening stdout
function mod.stdout_reopen(options)
end
function mod.file_validate(options)
if (not type(options.filename) == 'string') then
error("File output needs to be configured with a valid filename")
@ -44,14 +48,18 @@ function mod.file_validate(options)
end
function mod.file(priority, priority_num, buffered, msg, options)
if options.keep_alive == "true" then
if ffile == nil then
ffile = io.open(options.filename, "a+")
if buffered == 0 then
ffile:setvbuf 'no'
end
function mod.file_open(options)
if ffile == nil then
ffile = io.open(options.filename, "a+")
if options.buffered == 0 then
ffile:setvbuf 'no'
end
end
end
function mod.file(priority, priority_num, msg, options)
if options.keep_alive == "true" then
mod.file_open(options)
else
ffile = io.open(options.filename, "a+")
end
@ -73,26 +81,40 @@ function mod.file_cleanup()
end
end
function mod.syslog(priority, priority_num, buffered, msg, options)
function mod.file_reopen(options)
if options.keep_alive == "true" then
mod.file_cleanup()
mod.file_open(options)
end
end
function mod.syslog(priority, priority_num, msg, options)
falco.syslog(priority_num, msg)
end
function mod.syslog_cleanup()
end
function mod.program(priority, priority_num, buffered, msg, options)
function mod.syslog_reopen()
end
function mod.program_open(options)
if pfile == nil then
pfile = io.popen(options.program, "w")
if options.buffered == 0 then
pfile:setvbuf 'no'
end
end
end
function mod.program(priority, priority_num, msg, options)
-- XXX Ideally we'd check that the program ran
-- successfully. However, the luajit we're using returns true even
-- when the shell can't run the program.
-- Note: options are all strings
if options.keep_alive == "true" then
if pfile == nil then
pfile = io.popen(options.program, "w")
if buffered == 0 then
pfile:setvbuf 'no'
end
end
mod.program_open(options)
else
pfile = io.popen(options.program, "w")
end
@ -114,6 +136,13 @@ function mod.program_cleanup()
end
end
function mod.program_reopen(options)
if options.keep_alive == "true" then
mod.program_cleanup()
mod.program_open(options)
end
end
function output_event(event, rule, priority, priority_num, format)
-- If format starts with a *, remove it, as we're adding our own
-- prefix here.
@ -126,7 +155,7 @@ function output_event(event, rule, priority, priority_num, format)
msg = formats.format_event(event, rule, priority, format)
for index,o in ipairs(outputs) do
o.output(priority, priority_num, o.buffered, msg, o.config)
o.output(priority, priority_num, msg, o.options)
end
end
@ -137,20 +166,33 @@ function output_cleanup()
end
end
function add_output(output_name, buffered, config)
function output_reopen()
for index,o in ipairs(outputs) do
o.reopen(o.options)
end
end
function add_output(output_name, buffered, options)
if not (type(mod[output_name]) == 'function') then
error("rule_loader.add_output(): invalid output_name: "..output_name)
end
-- outputs can optionally define a validation function so that we don't
-- find out at runtime (when an event finally matches a rule!) that the config is invalid
-- find out at runtime (when an event finally matches a rule!) that the options are invalid
if (type(mod[output_name.."_validate"]) == 'function') then
mod[output_name.."_validate"](config)
mod[output_name.."_validate"](options)
end
if options == nil then
options = {}
end
options.buffered = buffered
table.insert(outputs, {output = mod[output_name],
cleanup = mod[output_name.."_cleanup"],
buffered=buffered, config=config})
reopen = mod[output_name.."_reopen"],
options=options})
end
return mod