view mod_log_ringbuffer/mod_log_ringbuffer.lua @ 4225:d6fb9f7afaa5

mod_log_ringbuffer: Discard old data when buffer is full
author Matthew Wild <mwild1@gmail.com>
date Tue, 20 Oct 2020 15:34:29 +0100
parents b3dd77f2d0d7
children df2ccb42a241
line wrap: on
line source

module:set_global();

local loggingmanager = require "core.loggingmanager";
local format = require "util.format".format;
local pposix = require "util.pposix";
local rb = require "util.ringbuffer";

local default_timestamp = "%b %d %H:%M:%S ";
local max_chunk_size = module:get_option_number("log_ringbuffer_chunk_size", 16384);

local os_date = os.date;

local default_filename_template = "{paths.data}/ringbuffer-logs-{pid}-{count}.log";
local render_filename = require "util.interpolation".new("%b{}", function (s) return s; end, {
	yyyymmdd = function (t)
		return os_date("%Y%m%d", t);
	end;
	hhmmss = function (t)
		return os_date("%H%M%S", t);
	end;
});

local dump_count = 0;

local function dump_buffer(buffer, filename)
	dump_count = dump_count + 1;
	local f, err = io.open(filename, "a+");
	if not f then
		module:log("error", "Unable to open output file: %s", err);
		return;
	end
	local bytes_remaining = buffer:length();
	f:write(("-- Dumping %d bytes at %s --\n"):format(bytes_remaining, os_date(default_timestamp)));
	while bytes_remaining > 0 do
		local chunk_size = math.min(bytes_remaining, max_chunk_size);
		local chunk = buffer:read(chunk_size);
		if not chunk then
			f:write("-- Dump aborted due to error --\n\n");
			f:close();
			return;
		end
		f:write(chunk);
		bytes_remaining = bytes_remaining - chunk_size;
	end
	f:write("-- End of dump --\n\n");
	f:close();
end

local function get_filename(filename_template)
	filename_template = filename_template or default_filename_template;
	return render_filename(filename_template, {
		paths = prosody.paths;
		pid = pposix.getpid();
		count = dump_count;
		time = os.time();
	});
end

local function ringbuffer_log_sink_maker(sink_config)
	local buffer_size = sink_config.size or 100*1024;
	local buffer = rb.new(buffer_size);

	local timestamps = sink_config.timestamps;

	if timestamps == true or timestamps == nil then
		timestamps = default_timestamp; -- Default format
	elseif timestamps then
		timestamps = timestamps .. " ";
	end

	local function dump()
		dump_buffer(buffer, sink_config.filename or get_filename(sink_config.filename_template));
	end

	if sink_config.signal then
		require "util.signal".signal(sink_config.signal, dump);
	elseif sink_config.event then
		module:hook_global(sink_config.event, dump);
	end

	return function (name, level, message, ...)
		local line = format("%s%s\t%s\t%s\n", timestamps and os_date(timestamps) or "", name, level, format(message, ...));
		if not buffer:write(line) then
			if #line > buffer_size then
				buffer:discard(buffer_size);
				buffer:write(line:sub(-buffer_size));
			else
				buffer:discard(#line);
				buffer:write(line);
			end
		end
	end;
end

loggingmanager.register_sink_type("ringbuffer", ringbuffer_log_sink_maker);