| 1 | -- Prosody IM |
| 2 | -- Copyright (C) 2008-2010 Matthew Wild |
| 3 | -- Copyright (C) 2008-2010 Waqas Hussain |
| 4 | -- |
| 5 | -- This project is MIT/X11 licensed. Please see the |
| 6 | -- COPYING file in the source package for more information. |
| 7 | -- |
| 8 | |
| 9 | |
| 10 | local format, rep = string.format, string.rep; |
| 11 | local pcall = pcall; |
| 12 | local debug = debug; |
| 13 | local tostring, setmetatable, rawset, pairs, ipairs, type = |
| 14 | tostring, setmetatable, rawset, pairs, ipairs, type; |
| 15 | local io_open, io_write = io.open, io.write; |
| 16 | local math_max, rep = math.max, string.rep; |
| 17 | local os_date, os_getenv = os.date, os.getenv; |
| 18 | local getstyle, setstyle = require "util.termcolours".getstyle, require "util.termcolours".setstyle; |
| 19 | |
| 20 | if os.getenv("__FLUSH_LOG") then |
| 21 | local io_flush = io.flush; |
| 22 | local _io_write = io_write; |
| 23 | io_write = function(...) _io_write(...); io_flush(); end |
| 24 | end |
| 25 | |
| 26 | local config = require "core.configmanager"; |
| 27 | local logger = require "util.logger"; |
| 28 | local prosody = prosody; |
| 29 | |
| 30 | local debug_mode = config.get("*", "core", "debug"); |
| 31 | |
| 32 | _G.log = logger.init("general"); |
| 33 | |
| 34 | module "loggingmanager" |
| 35 | |
| 36 | -- The log config used if none specified in the config file (see reload_logging for initialization) |
| 37 | local default_logging; |
| 38 | local default_file_logging; |
| 39 | local default_timestamp = "%b %d %H:%M:%S"; |
| 40 | -- The actual config loggingmanager is using |
| 41 | local logging_config; |
| 42 | |
| 43 | local apply_sink_rules; |
| 44 | local log_sink_types = setmetatable({}, { __newindex = function (t, k, v) rawset(t, k, v); apply_sink_rules(k); end; }); |
| 45 | local get_levels; |
| 46 | local logging_levels = { "debug", "info", "warn", "error", "critical" } |
| 47 | |
| 48 | -- Put a rule into action. Requires that the sink type has already been registered. |
| 49 | -- This function is called automatically when a new sink type is added [see apply_sink_rules()] |
| 50 | local function add_rule(sink_config) |
| 51 | local sink_maker = log_sink_types[sink_config.to]; |
| 52 | if sink_maker then |
| 53 | if sink_config.levels and not sink_config.source then |
| 54 | -- Create sink |
| 55 | local sink = sink_maker(sink_config); |
| 56 | |
| 57 | -- Set sink for all chosen levels |
| 58 | for level in pairs(get_levels(sink_config.levels)) do |
| 59 | logger.add_level_sink(level, sink); |
| 60 | end |
| 61 | elseif sink_config.source and not sink_config.levels then |
| 62 | logger.add_name_sink(sink_config.source, sink_maker(sink_config)); |
| 63 | elseif sink_config.source and sink_config.levels then |
| 64 | local levels = get_levels(sink_config.levels); |
| 65 | local sink = sink_maker(sink_config); |
| 66 | logger.add_name_sink(sink_config.source, |
| 67 | function (name, level, ...) |
| 68 | if levels[level] then |
| 69 | return sink(name, level, ...); |
| 70 | end |
| 71 | end); |
| 72 | else |
| 73 | -- All sources |
| 74 | -- Create sink |
| 75 | local sink = sink_maker(sink_config); |
| 76 | |
| 77 | -- Set sink for all levels |
| 78 | for _, level in pairs(logging_levels) do |
| 79 | logger.add_level_sink(level, sink); |
| 80 | end |
| 81 | end |
| 82 | else |
| 83 | -- No such sink type |
| 84 | end |
| 85 | end |
| 86 | |
| 87 | -- Search for all rules using a particular sink type, and apply |
| 88 | -- them. Called automatically when a new sink type is added to |
| 89 | -- the log_sink_types table. |
| 90 | function apply_sink_rules(sink_type) |
| 91 | if type(logging_config) == "table" then |
| 92 | |
| 93 | if sink_type == "file" then |
| 94 | for _, level in ipairs(logging_levels) do |
| 95 | if type(logging_config[level]) == "string" then |
| 96 | add_rule({ |
| 97 | to = "file", |
| 98 | filename = logging_config[level], |
| 99 | timestamps = true, |
| 100 | levels = { min = level }, |
| 101 | }); |
| 102 | end |
| 103 | end |
| 104 | end |
| 105 | |
| 106 | for _, sink_config in pairs(logging_config) do |
| 107 | if (type(sink_config) == "table" and sink_config.to == sink_type) then |
| 108 | add_rule(sink_config); |
| 109 | elseif (type(sink_config) == "string" and sink_config:match("^%*(.+)") == sink_type) then |
| 110 | add_rule({ levels = { min = "debug" }, to = sink_type }); |
| 111 | end |
| 112 | end |
| 113 | elseif type(logging_config) == "string" and (not logging_config:match("^%*")) and sink_type == "file" then |
| 114 | -- User specified simply a filename, and the "file" sink type |
| 115 | -- was just added |
| 116 | for _, sink_config in pairs(default_file_logging) do |
| 117 | sink_config.filename = logging_config; |
| 118 | add_rule(sink_config); |
| 119 | sink_config.filename = nil; |
| 120 | end |
| 121 | elseif type(logging_config) == "string" and logging_config:match("^%*(.+)") == sink_type then |
| 122 | -- Log all levels (debug+) to this sink |
| 123 | add_rule({ levels = { min = "debug" }, to = sink_type }); |
| 124 | end |
| 125 | end |
| 126 | |
| 127 | |
| 128 | |
| 129 | --- Helper function to get a set of levels given a "criteria" table |
| 130 | function get_levels(criteria, set) |
| 131 | set = set or {}; |
| 132 | if type(criteria) == "string" then |
| 133 | set[criteria] = true; |
| 134 | return set; |
| 135 | end |
| 136 | local min, max = criteria.min, criteria.max; |
| 137 | if min or max then |
| 138 | local in_range; |
| 139 | for _, level in ipairs(logging_levels) do |
| 140 | if min == level then |
| 141 | set[level] = true; |
| 142 | in_range = true; |
| 143 | elseif max == level then |
| 144 | set[level] = true; |
| 145 | return set; |
| 146 | elseif in_range then |
| 147 | set[level] = true; |
| 148 | end |
| 149 | end |
| 150 | end |
| 151 | |
| 152 | for _, level in ipairs(criteria) do |
| 153 | set[level] = true; |
| 154 | end |
| 155 | return set; |
| 156 | end |
| 157 | |
| 158 | -- Initialize config, etc. -- |
| 159 | function reload_logging() |
| 160 | local old_sink_types = {}; |
| 161 | |
| 162 | for name, sink_maker in pairs(log_sink_types) do |
| 163 | old_sink_types[name] = sink_maker; |
| 164 | log_sink_types[name] = nil; |
| 165 | end |
| 166 | |
| 167 | logger.reset(); |
| 168 | |
| 169 | default_logging = { { to = "console" , levels = { min = (debug_mode and "debug") or "info" } } }; |
| 170 | default_file_logging = { |
| 171 | { to = "file", levels = { min = (debug_mode and "debug") or "info" }, timestamps = true } |
| 172 | }; |
| 173 | default_timestamp = "%b %d %H:%M:%S"; |
| 174 | |
| 175 | logging_config = config.get("*", "core", "log") or default_logging; |
| 176 | |
| 177 | |
| 178 | for name, sink_maker in pairs(old_sink_types) do |
| 179 | log_sink_types[name] = sink_maker; |
| 180 | end |
| 181 | |
| 182 | prosody.events.fire_event("logging-reloaded"); |
| 183 | end |
| 184 | |
| 185 | reload_logging(); |
| 186 | prosody.events.add_handler("config-reloaded", reload_logging); |
| 187 | |
| 188 | --- Definition of built-in logging sinks --- |
| 189 | |
| 190 | -- Null sink, must enter log_sink_types *first* |
| 191 | function log_sink_types.nowhere() |
| 192 | return function () return false; end; |
| 193 | end |
| 194 | |
| 195 | -- Column width for "source" (used by stdout and console) |
| 196 | local sourcewidth = 20; |
| 197 | |
| 198 | function log_sink_types.stdout() |
| 199 | local timestamps = config.timestamps; |
| 200 | |
| 201 | if timestamps == true then |
| 202 | timestamps = default_timestamp; -- Default format |
| 203 | end |
| 204 | |
| 205 | return function (name, level, message, ...) |
| 206 | sourcewidth = math_max(#name+2, sourcewidth); |
| 207 | local namelen = #name; |
| 208 | if timestamps then |
| 209 | io_write(os_date(timestamps), " "); |
| 210 | end |
| 211 | if ... then |
| 212 | io_write(name, rep(" ", sourcewidth-namelen), level, "\t", format(message, ...), "\n"); |
| 213 | else |
| 214 | io_write(name, rep(" ", sourcewidth-namelen), level, "\t", message, "\n"); |
| 215 | end |
| 216 | end |
| 217 | end |
| 218 | |
| 219 | do |
| 220 | local do_pretty_printing = true; |
| 221 | |
| 222 | local logstyles = {}; |
| 223 | if do_pretty_printing then |
| 224 | logstyles["info"] = getstyle("bold"); |
| 225 | logstyles["warn"] = getstyle("bold", "yellow"); |
| 226 | logstyles["error"] = getstyle("bold", "red"); |
| 227 | end |
| 228 | function log_sink_types.console(config) |
| 229 | -- Really if we don't want pretty colours then just use plain stdout |
| 230 | if not do_pretty_printing then |
| 231 | return log_sink_types.stdout(config); |
| 232 | end |
| 233 | |
| 234 | local timestamps = config.timestamps; |
| 235 | |
| 236 | if timestamps == true then |
| 237 | timestamps = default_timestamp; -- Default format |
| 238 | end |
| 239 | |
| 240 | return function (name, level, message, ...) |
| 241 | sourcewidth = math_max(#name+2, sourcewidth); |
| 242 | local namelen = #name; |
| 243 | |
| 244 | if timestamps then |
| 245 | io_write(os_date(timestamps), " "); |
| 246 | end |
| 247 | io_write(name, rep(" ", sourcewidth-namelen)); |
| 248 | setstyle(logstyles[level]); |
| 249 | io_write(level); |
| 250 | setstyle(); |
| 251 | if ... then |
| 252 | io_write("\t", format(message, ...), "\n"); |
| 253 | else |
| 254 | io_write("\t", message, "\n"); |
| 255 | end |
| 256 | end |
| 257 | end |
| 258 | end |
| 259 | |
| 260 | local empty_function = function () end; |
| 261 | function log_sink_types.file(config) |
| 262 | local log = config.filename; |
| 263 | local logfile = io_open(log, "a+"); |
| 264 | if not logfile then |
| 265 | return empty_function; |
| 266 | end |
| 267 | local write, flush = logfile.write, logfile.flush; |
| 268 | |
| 269 | prosody.events.add_handler("logging-reloading", function () |
| 270 | if logfile then |
| 271 | logfile:close(); |
| 272 | end |
| 273 | end); |
| 274 | |
| 275 | local timestamps = config.timestamps; |
| 276 | |
| 277 | if timestamps == nil or timestamps == true then |
| 278 | timestamps = default_timestamp; -- Default format |
| 279 | end |
| 280 | |
| 281 | return function (name, level, message, ...) |
| 282 | if timestamps then |
| 283 | write(logfile, os_date(timestamps), " "); |
| 284 | end |
| 285 | if ... then |
| 286 | write(logfile, name, "\t", level, "\t", format(message, ...), "\n"); |
| 287 | else |
| 288 | write(logfile, name, "\t" , level, "\t", message, "\n"); |
| 289 | end |
| 290 | flush(logfile); |
| 291 | end; |
| 292 | end |
| 293 | |
| 294 | function register_sink_type(name, sink_maker) |
| 295 | local old_sink_maker = log_sink_types[name]; |
| 296 | log_sink_types[name] = sink_maker; |
| 297 | return old_sink_maker; |
| 298 | end |
| 299 | |
| 300 | return _M; |