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 9local format = require "util.format".format; 10local setmetatable, rawset, pairs, ipairs, type = 11 setmetatable, rawset, pairs, ipairs, type; 12local stdout = io.stdout; 13local io_open = io.open; 14local math_max, rep = math.max, string.rep; 15local os_date = os.date; 16local getstyle, getstring = require "util.termcolours".getstyle, require "util.termcolours".getstring; 17 18local config = require "core.configmanager"; 19local logger = require "util.logger"; 20 21local _ENV = nil; 22-- luacheck: std none 23 24-- The log config used if none specified in the config file (see reload_logging for initialization) 25local default_logging; 26local default_file_logging; 27local default_timestamp = "%b %d %H:%M:%S "; 28-- The actual config loggingmanager is using 29local logging_config; 30 31local apply_sink_rules; 32local log_sink_types = setmetatable({}, { __newindex = function (t, k, v) rawset(t, k, v); apply_sink_rules(k); end; }); 33local get_levels; 34local logging_levels = { "debug", "info", "warn", "error" } 35 36-- Put a rule into action. Requires that the sink type has already been registered. 37-- This function is called automatically when a new sink type is added [see apply_sink_rules()] 38local function add_rule(sink_config) 39 local sink_maker = log_sink_types[sink_config.to]; 40 if not sink_maker then 41 return; -- No such sink type 42 end 43 44 -- Create sink 45 local sink = sink_maker(sink_config); 46 47 -- Set sink for all chosen levels 48 for level in pairs(get_levels(sink_config.levels or logging_levels)) do 49 logger.add_level_sink(level, sink); 50 end 51end 52 53-- Search for all rules using a particular sink type, and apply 54-- them. Called automatically when a new sink type is added to 55-- the log_sink_types table. 56function apply_sink_rules(sink_type) 57 if type(logging_config) == "table" then 58 59 for _, level in ipairs(logging_levels) do 60 if type(logging_config[level]) == "string" then 61 local value = logging_config[level]; 62 if sink_type == "file" and not value:match("^%*") then 63 add_rule({ 64 to = sink_type; 65 filename = value; 66 timestamps = true; 67 levels = { min = level }; 68 }); 69 elseif value == "*"..sink_type then 70 add_rule({ 71 to = sink_type; 72 levels = { min = level }; 73 }); 74 end 75 end 76 end 77 78 for _, sink_config in ipairs(logging_config) do 79 if (type(sink_config) == "table" and sink_config.to == sink_type) then 80 add_rule(sink_config); 81 elseif (type(sink_config) == "string" and sink_config:match("^%*(.+)") == sink_type) then 82 add_rule({ levels = { min = "debug" }, to = sink_type }); 83 end 84 end 85 elseif type(logging_config) == "string" and (not logging_config:match("^%*")) and sink_type == "file" then 86 -- User specified simply a filename, and the "file" sink type 87 -- was just added 88 for _, sink_config in pairs(default_file_logging) do 89 sink_config.filename = logging_config; 90 add_rule(sink_config); 91 sink_config.filename = nil; 92 end 93 elseif type(logging_config) == "string" and logging_config:match("^%*(.+)") == sink_type then 94 -- Log all levels (debug+) to this sink 95 add_rule({ levels = { min = "debug" }, to = sink_type }); 96 end 97end 98 99 100 101--- Helper function to get a set of levels given a "criteria" table 102function get_levels(criteria, set) 103 set = set or {}; 104 if type(criteria) == "string" then 105 set[criteria] = true; 106 return set; 107 end 108 local min, max = criteria.min, criteria.max; 109 if min or max then 110 local in_range; 111 for _, level in ipairs(logging_levels) do 112 if min == level then 113 set[level] = true; 114 in_range = true; 115 elseif max == level then 116 set[level] = true; 117 return set; 118 elseif in_range then 119 set[level] = true; 120 end 121 end 122 end 123 124 for _, level in ipairs(criteria) do 125 set[level] = true; 126 end 127 return set; 128end 129 130-- Initialize config, etc. -- 131local function reload_logging() 132 local old_sink_types = {}; 133 134 for name, sink_maker in pairs(log_sink_types) do 135 old_sink_types[name] = sink_maker; 136 log_sink_types[name] = nil; 137 end 138 139 logger.reset(); 140 141 local debug_mode = config.get("*", "debug"); 142 143 default_logging = { { to = "console" , levels = { min = (debug_mode and "debug") or "info" } } }; 144 default_file_logging = { 145 { to = "file", levels = { min = (debug_mode and "debug") or "info" }, timestamps = true } 146 }; 147 148 logging_config = config.get("*", "log") or default_logging; 149 150 for name, sink_maker in pairs(old_sink_types) do 151 log_sink_types[name] = sink_maker; 152 end 153end 154 155--- Definition of built-in logging sinks --- 156 157-- Null sink, must enter log_sink_types *first* 158local function log_to_nowhere() 159 return function () return false; end; 160end 161log_sink_types.nowhere = log_to_nowhere; 162 163local function log_to_file(sink_config, logfile) 164 logfile = logfile or io_open(sink_config.filename, "a+"); 165 if not logfile then 166 return log_to_nowhere(sink_config); 167 end 168 local write = logfile.write; 169 170 local timestamps = sink_config.timestamps; 171 172 if timestamps == true or timestamps == nil then 173 timestamps = default_timestamp; -- Default format 174 elseif timestamps then 175 timestamps = timestamps .. " "; 176 end 177 178 if sink_config.buffer_mode ~= false then 179 logfile:setvbuf(sink_config.buffer_mode or "line"); 180 end 181 182 -- Column width for "source" (used by stdout and console) 183 local sourcewidth = sink_config.source_width; 184 185 if sourcewidth then 186 return function (name, level, message, ...) 187 sourcewidth = math_max(#name+2, sourcewidth); 188 write(logfile, timestamps and os_date(timestamps) or "", name, rep(" ", sourcewidth-#name), level, "\t", format(message, ...), "\n"); 189 end 190 else 191 return function (name, level, message, ...) 192 write(logfile, timestamps and os_date(timestamps) or "", name, "\t", level, "\t", format(message, ...), "\n"); 193 end 194 end 195end 196log_sink_types.file = log_to_file; 197 198local function log_to_stdout(sink_config) 199 if not sink_config.timestamps then 200 sink_config.timestamps = false; 201 end 202 if sink_config.source_width == nil then 203 sink_config.source_width = 20; 204 end 205 return log_to_file(sink_config, stdout); 206end 207log_sink_types.stdout = log_to_stdout; 208 209local do_pretty_printing = true; 210 211local logstyles; 212if do_pretty_printing then 213 logstyles = {}; 214 logstyles["info"] = getstyle("bold"); 215 logstyles["warn"] = getstyle("bold", "yellow"); 216 logstyles["error"] = getstyle("bold", "red"); 217end 218 219local function log_to_console(sink_config) 220 -- Really if we don't want pretty colours then just use plain stdout 221 local logstdout = log_to_stdout(sink_config); 222 if not do_pretty_printing then 223 return logstdout; 224 end 225 return function (name, level, message, ...) 226 local logstyle = logstyles[level]; 227 if logstyle then 228 level = getstring(logstyle, level); 229 end 230 return logstdout(name, level, message, ...); 231 end 232end 233log_sink_types.console = log_to_console; 234 235local function register_sink_type(name, sink_maker) 236 local old_sink_maker = log_sink_types[name]; 237 log_sink_types[name] = sink_maker; 238 return old_sink_maker; 239end 240 241return { 242 reload_logging = reload_logging; 243 register_sink_type = register_sink_type; 244} 245