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