1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
|
local format, rep = string.format, string.rep;
local pcall = pcall;
local debug = debug;
local tostring, setmetatable, rawset, pairs, ipairs, type =
tostring, setmetatable, rawset, pairs, ipairs, type;
local io_open, io_write = io.open, io.write;
local math_max, rep = math.max, string.rep;
local os_getenv = os.getenv;
local getstyle, getstring = require "util.termcolours".getstyle, require "util.termcolours".getstring;
local config = require "core.configmanager";
local logger = require "util.logger";
_G.log = logger.init("general");
module "loggingmanager"
-- The log config used if none specified in the config file
local default_logging = { { to = "console" } };
-- The actual config loggingmanager is using
local logging_config = config.get("*", "core", "log") or default_logging;
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", "critical" }
local function add_rule(sink_config)
local sink_maker = log_sink_types[sink_config.to];
if sink_maker then
if sink_config.levels and not sink_config.source then
-- Create sink
local sink = sink_maker(sink_config);
-- Set sink for all chosen levels
for level in pairs(get_levels(sink_config.levels)) do
logger.add_level_sink(level, sink);
end
elseif sink_config.source and not sink_config.levels then
logger.add_name_sink(sink_config.source, sink_maker(sink_config));
elseif sink_config.source and sink_config.levels then
local levels = get_levels(sink_config.levels);
local sink = sink_maker(sink_config);
logger.add_name_sink(sink_config.source,
function (name, level, ...)
if levels[level] then
return sink(name, level, ...);
end
end);
else
-- All sources
-- Create sink
local sink = sink_maker(sink_config);
-- Set sink for all levels
for _, level in pairs(logging_levels) do
logger.add_level_sink(level, sink);
end
end
else
-- No such sink type
end
end
-- Search for all rules using a particular sink type,
-- and apply them
function apply_sink_rules(sink_type)
if type(logging_config) == "table" then
for _, sink_config in pairs(logging_config) do
if sink_config.to == sink_type then
add_rule(sink_config);
end
end
elseif type(logging_config) == "string" and sink_type == "file" then
-- User specified simply a filename, and the "file" sink type
-- was just added
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
--- Definition of built-in logging sinks ---
function log_sink_types.nowhere()
return function () return false; end;
end
-- Column width for "source" (used by stdout and console)
local sourcewidth = 20;
function log_sink_types.stdout()
return function (name, level, message, ...)
sourcewidth = math_max(#name+2, sourcewidth);
local namelen = #name;
if ... then
io_write(name, rep(" ", sourcewidth-namelen), level, "\t", format(message, ...), "\n");
else
io_write(name, rep(" ", sourcewidth-namelen), level, "\t", message, "\n");
end
end
end
do
local do_pretty_printing = not os_getenv("WINDIR");
local logstyles = {};
if do_pretty_printing then
logstyles["info"] = getstyle("bold");
logstyles["warn"] = getstyle("bold", "yellow");
logstyles["error"] = getstyle("bold", "red");
end
function log_sink_types.console(config)
-- Really if we don't want pretty colours then just use plain stdout
if not do_pretty_printing then
return log_sink_types.stdout(config);
end
return function (name, level, message, ...)
sourcewidth = math_max(#name+2, sourcewidth);
local namelen = #name;
if ... then
io_write(name, rep(" ", sourcewidth-namelen), getstring(logstyles[level], level), "\t", format(message, ...), "\n");
else
io_write(name, rep(" ", sourcewidth-namelen), getstring(logstyles[level], level), "\t", message, "\n");
end
end
end
end
function log_sink_types.file(config)
local log = config.filename;
local logfile = io_open(log, "a+");
if not logfile then
return function () end
end
local write, format, flush = logfile.write, format, logfile.flush;
return function (name, level, message, ...)
if ... then
write(logfile, name, "\t", level, "\t", format(message, ...), "\n");
else
write(logfile, name, "\t" , level, "\t", message, "\n");
end
flush(logfile);
end;
end
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 _M;
|