core/loggingmanager.lua

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;