mod_websocket: Fire pre-session-close event (fixes #1800)
This event was added in a7c183bb4e64 and is required to make mod_smacks know
that a session was intentionally closed and shouldn't be hibernated (see
fcea4d9e7502).
Because this was missing from mod_websocket's session.close(), mod_smacks
would always attempt to hibernate websocket sessions even if they closed
cleanly.
That mod_websocket has its own copy of session.close() is something to fix
another day (probably not in the stable branch). So for now this commit makes
the minimal change to get things working again.
Thanks to Damian and the Jitsi team for reporting.
-- Prosody IM
-- Copyright (C) 2008-2010 Matthew Wild
-- Copyright (C) 2008-2010 Waqas Hussain
--
-- This project is MIT/X11 licensed. Please see the
-- COPYING file in the source package for more information.
--
local format = require "util.format".format;
local setmetatable, rawset, pairs, ipairs, type =
setmetatable, rawset, pairs, ipairs, type;
local stdout = io.stdout;
local io_open = io.open;
local math_max, rep = math.max, string.rep;
local os_date = os.date;
local getstyle, getstring = require "util.termcolours".getstyle, require "util.termcolours".getstring;
local st = require "util.stanza";
local config = require "core.configmanager";
local logger = require "util.logger";
local have_pposix, pposix = pcall(require, "util.pposix");
have_pposix = have_pposix and pposix._VERSION == "0.4.0";
local _ENV = nil;
-- luacheck: std none
-- The log config used if none specified in the config file (see reload_logging for initialization)
local default_logging;
local default_file_logging;
local default_timestamp = "%b %d %H:%M:%S ";
-- The actual config loggingmanager is using
local logging_config;
local apply_sink_rules;
local log_sink_types = setmetatable({}, { __newindex = function (t, k, v) rawset(t, k, v); apply_sink_rules(k); end; });
local get_levels;
local logging_levels = { "debug", "info", "warn", "error" }
local function id(x) return x end
-- Put a rule into action. Requires that the sink type has already been registered.
-- This function is called automatically when a new sink type is added [see apply_sink_rules()]
local function add_rule(sink_config)
local sink_maker = log_sink_types[sink_config.to];
if not sink_maker then
return; -- No such sink type
end
-- Create sink
local sink = sink_maker(sink_config);
-- Set sink for all chosen levels
for level in pairs(get_levels(sink_config.levels or logging_levels)) do
logger.add_level_sink(level, sink);
end
end
-- Search for all rules using a particular sink type, and apply
-- them. Called automatically when a new sink type is added to
-- the log_sink_types table.
function apply_sink_rules(sink_type)
if type(logging_config) == "table" then
for _, level in ipairs(logging_levels) do
if type(logging_config[level]) == "string" then
local value = logging_config[level];
if sink_type == "file" and not value:match("^%*") then
add_rule({
to = sink_type;
filename = value;
timestamps = true;
levels = { min = level };
});
elseif value == "*"..sink_type then
add_rule({
to = sink_type;
levels = { min = level };
});
end
end
end
for _, sink_config in ipairs(logging_config) do
if (type(sink_config) == "table" and sink_config.to == sink_type) then
add_rule(sink_config);
elseif (type(sink_config) == "string" and sink_config:match("^%*(.+)") == sink_type) then
add_rule({ levels = { min = "debug" }, to = sink_type });
end
end
elseif type(logging_config) == "string" and (not logging_config:match("^%*")) and sink_type == "file" then
-- User specified simply a filename, and the "file" sink type
-- was just added
for _, sink_config in pairs(default_file_logging) do
sink_config.filename = logging_config;
add_rule(sink_config);
sink_config.filename = nil;
end
elseif type(logging_config) == "string" and logging_config:match("^%*(.+)") == sink_type then
-- Log all levels (debug+) to this sink
add_rule({ levels = { min = "debug" }, to = sink_type });
end
end
--- Helper function to get a set of levels given a "criteria" table
function get_levels(criteria, set)
set = set or {};
if type(criteria) == "string" then
set[criteria] = true;
return set;
end
local min, max = criteria.min, criteria.max;
if min or max then
local in_range;
for _, level in ipairs(logging_levels) do
if min == level then
set[level] = true;
in_range = true;
elseif max == level then
set[level] = true;
return set;
elseif in_range then
set[level] = true;
end
end
end
for _, level in ipairs(criteria) do
set[level] = true;
end
return set;
end
-- Initialize config, etc. --
local function reload_logging()
local old_sink_types = {};
for name, sink_maker in pairs(log_sink_types) do
old_sink_types[name] = sink_maker;
log_sink_types[name] = nil;
end
logger.reset();
local debug_mode = config.get("*", "debug");
default_logging = { { to = "console" , levels = { min = (debug_mode and "debug") or "info" } } };
default_file_logging = {
{ to = "file", levels = { min = (debug_mode and "debug") or "info" }, timestamps = true }
};
logging_config = config.get("*", "log") or default_logging;
for name, sink_maker in pairs(old_sink_types) do
log_sink_types[name] = sink_maker;
end
end
--- Definition of built-in logging sinks ---
-- Null sink, must enter log_sink_types *first*
local function log_to_nowhere()
return function () return false; end;
end
log_sink_types.nowhere = log_to_nowhere;
local function log_to_file(sink_config, logfile)
logfile = logfile or io_open(sink_config.filename, "a+");
if not logfile then
return log_to_nowhere(sink_config);
end
local write = logfile.write;
local timestamps = sink_config.timestamps;
if timestamps == true or timestamps == nil then
timestamps = default_timestamp; -- Default format
elseif timestamps then
timestamps = timestamps .. " ";
end
if sink_config.buffer_mode ~= false then
logfile:setvbuf(sink_config.buffer_mode or "line");
end
-- Column width for "source" (used by stdout and console)
local sourcewidth = sink_config.source_width;
local filter = sink_config.filter or id;
if sourcewidth then
return function (name, level, message, ...)
sourcewidth = math_max(#name+2, sourcewidth);
write(logfile, timestamps and os_date(timestamps) or "", name, rep(" ", sourcewidth-#name), level, "\t", filter(format(message, ...)), "\n");
end
else
return function (name, level, message, ...)
write(logfile, timestamps and os_date(timestamps) or "", name, "\t", level, "\t", filter(format(message, ...)), "\n");
end
end
end
log_sink_types.file = log_to_file;
local function log_to_stdout(sink_config)
if not sink_config.timestamps then
sink_config.timestamps = false;
end
if sink_config.source_width == nil then
sink_config.source_width = 20;
end
return log_to_file(sink_config, stdout);
end
log_sink_types.stdout = log_to_stdout;
local do_pretty_printing = not have_pposix or pposix.isatty(stdout);
local logstyles, pretty;
if do_pretty_printing then
logstyles = {};
logstyles["info"] = getstyle("bold");
logstyles["warn"] = getstyle("bold", "yellow");
logstyles["error"] = getstyle("bold", "red");
pretty = st.pretty_print;
end
local function log_to_console(sink_config)
-- Really if we don't want pretty colours then just use plain stdout
-- FIXME refactor to allow console logging with colours on stderr
if not do_pretty_printing then
return log_to_stdout(sink_config);
end
sink_config.filter = pretty;
local logstdout = log_to_stdout(sink_config);
return function (name, level, message, ...)
local logstyle = logstyles[level];
if logstyle then
level = getstring(logstyle, level);
end
return logstdout(name, level, message, ...);
end
end
log_sink_types.console = log_to_console;
if have_pposix then
local syslog_opened;
local function log_to_syslog(sink_config) -- luacheck: ignore 212/sink_config
if not syslog_opened then
local facility = sink_config.syslog_facility or config.get("*", "syslog_facility");
pposix.syslog_open(sink_config.syslog_name or "prosody", facility);
syslog_opened = true;
end
local syslog = pposix.syslog_log;
return function (name, level, message, ...)
syslog(level, name, format(message, ...));
end;
end
log_sink_types.syslog = log_to_syslog;
end
local function register_sink_type(name, sink_maker)
local old_sink_maker = log_sink_types[name];
log_sink_types[name] = sink_maker;
return old_sink_maker;
end
return {
reload_logging = reload_logging;
register_sink_type = register_sink_type;
}