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

View File

@ -39,6 +39,8 @@ along with falco. If not, see <http://www.gnu.org/licenses/>.
#include "statsfilewriter.h" #include "statsfilewriter.h"
bool g_terminate = false; bool g_terminate = false;
bool g_reopen_outputs = false;
// //
// Helper functions // Helper functions
// //
@ -47,6 +49,11 @@ static void signal_callback(int signal)
g_terminate = true; g_terminate = true;
} }
static void reopen_outputs(int signal)
{
g_reopen_outputs = true;
}
// //
// Program help // Program help
// //
@ -171,6 +178,12 @@ uint64_t do_inspect(falco_engine *engine,
writer.handle(); writer.handle();
if(g_reopen_outputs)
{
outputs->reopen_outputs();
g_reopen_outputs = false;
}
if (g_terminate) if (g_terminate)
{ {
break; break;
@ -589,6 +602,13 @@ int falco_init(int argc, char **argv)
goto exit; 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()) if (scap_filename.size())
{ {
inspector->open(scap_filename); 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 handle_event(sinsp_evt *ev, std::string &rule, falco_common::priority_type priority, std::string &format);
void reopen_outputs();
private: private:
bool m_initialized; bool m_initialized;
@ -64,5 +66,6 @@ private:
std::string m_lua_add_output = "add_output"; std::string m_lua_add_output = "add_output";
std::string m_lua_output_event = "output_event"; std::string m_lua_output_event = "output_event";
std::string m_lua_output_cleanup = "output_cleanup"; std::string m_lua_output_cleanup = "output_cleanup";
std::string m_lua_output_reopen = "output_reopen";
std::string m_lua_main_filename = "output.lua"; std::string m_lua_main_filename = "output.lua";
}; };

View File

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