1 |
|
2 |
|
3 |
|
4 | 'use strict';
|
5 |
|
6 | const async = require('async');
|
7 | const brUtil = require('./util');
|
8 | const cc = brUtil.config.main.computer();
|
9 | const cluster = require('cluster');
|
10 | const config = require('./config');
|
11 | const cycle = require('cycle');
|
12 | const fs = require('fs');
|
13 | const mkdirp = require('mkdirp');
|
14 | const path = require('path');
|
15 | const util = require('util');
|
16 | const winston = require('winston');
|
17 | const WinstonMail = require('winston-mail').Mail;
|
18 |
|
19 |
|
20 | let posix;
|
21 | try {
|
22 | posix = require('posix');
|
23 | } catch(e) {
|
24 | posix = null;
|
25 | }
|
26 |
|
27 |
|
28 |
|
29 | cc({
|
30 | 'loggers.app.filename': () => path.join(config.paths.log, 'app.log'),
|
31 | 'loggers.access.filename': () => path.join(config.paths.log, 'access.log'),
|
32 | 'loggers.error.filename': () => path.join(config.paths.log, 'error.log')
|
33 | });
|
34 |
|
35 |
|
36 | const levels = {
|
37 | silly: 0,
|
38 | verbose: 1,
|
39 | debug: 2,
|
40 | info: 3,
|
41 | warning: 4,
|
42 | error: 5,
|
43 | critical: 6
|
44 | };
|
45 | const colors = {
|
46 | silly: 'cyan',
|
47 | verbose: 'blue',
|
48 | debug: 'blue',
|
49 | info: 'green',
|
50 | warning: 'yellow',
|
51 | error: 'red',
|
52 | critical: 'red'
|
53 | };
|
54 |
|
55 |
|
56 | const container = new winston.Container();
|
57 |
|
58 |
|
59 |
|
60 | container._get = container.get;
|
61 | container._add = container.add;
|
62 | container.get = container.add = function(id) {
|
63 | const existing = container.loggers[id];
|
64 | let logger = container._get.apply(container, arguments);
|
65 | if(!existing) {
|
66 | const wrapper = Object.create(logger);
|
67 | wrapper.log = function(level, msg, meta) {
|
68 |
|
69 | meta = Object.assign({}, this.meta, meta);
|
70 | return Object.getPrototypeOf(wrapper).log.apply(
|
71 | wrapper, [level, msg, meta]);
|
72 | };
|
73 | wrapper.child = function(childMeta) {
|
74 |
|
75 | if(typeof childMeta === 'string') {
|
76 | childMeta = {module: childMeta};
|
77 | }
|
78 |
|
79 | const child = Object.create(this);
|
80 | child.meta = Object.assign({}, this.meta, childMeta);
|
81 | child.setLevels(levels);
|
82 | return child;
|
83 | };
|
84 | logger = container.loggers[id] = wrapper;
|
85 | }
|
86 | return logger;
|
87 | };
|
88 | module.exports = container;
|
89 |
|
90 | if(cluster.isMaster) {
|
91 |
|
92 | container.transports = {
|
93 | console: true,
|
94 | app: true,
|
95 | access: true,
|
96 | error: true,
|
97 | email: true
|
98 | };
|
99 | }
|
100 |
|
101 | function chown(filename, callback) {
|
102 | if(config.core.running.userId) {
|
103 | let uid = config.core.running.userId;
|
104 | if(typeof uid !== 'number') {
|
105 | let user = null;
|
106 | if(posix) {
|
107 | user = posix.getpwnam(uid);
|
108 | } else if(process.platform === 'win32') {
|
109 |
|
110 | user = {uid: process.getuid()};
|
111 | }
|
112 | if(!user && !posix && typeof uid !== 'number') {
|
113 | return callback(new Error(
|
114 | '"posix" package not available to convert user "' + uid + '" ' +
|
115 | 'to a number. Try using a uid number instead.'));
|
116 | }
|
117 | if(!user) {
|
118 | return callback(new Error('No system user id for "' + uid + '".'));
|
119 | }
|
120 | uid = user.uid;
|
121 | }
|
122 | if(process.getgid) {
|
123 | return fs.chown(filename, uid, process.getgid(), callback);
|
124 | }
|
125 | }
|
126 | callback();
|
127 | }
|
128 |
|
129 |
|
130 | class ModuleConsoleTransport extends winston.transports.Console {
|
131 | constructor(config) {
|
132 | super(config);
|
133 | this.modulePrefix = config.bedrock.modulePrefix;
|
134 | this.onlyModules = config.bedrock.onlyModules;
|
135 | this.excludeModules = config.bedrock.excludeModules;
|
136 | }
|
137 | log(level, msg, meta, callback) {
|
138 |
|
139 | if(this.onlyModules && this.modulePrefix &&
|
140 | 'module' in meta && !this.onlyModules.includes(meta.module)) {
|
141 | return;
|
142 | }
|
143 |
|
144 | if(this.excludeModules && this.modulePrefix &&
|
145 | 'module' in meta && this.excludeModules.includes(meta.module)) {
|
146 | return;
|
147 | }
|
148 | if(this.modulePrefix && 'module' in meta) {
|
149 |
|
150 | msg = '[' + meta.module + '] ' + msg;
|
151 |
|
152 | meta = Object.assign({}, meta);
|
153 |
|
154 | delete meta.module;
|
155 | }
|
156 | super.log(level, msg, meta, callback);
|
157 | }
|
158 | }
|
159 |
|
160 |
|
161 | class ModuleFileTransport extends winston.transports.File {
|
162 | constructor(config) {
|
163 | super(config);
|
164 | this.modulePrefix = config.bedrock.modulePrefix;
|
165 | }
|
166 | log(level, msg, meta, callback) {
|
167 | if(this.modulePrefix && 'module' in meta) {
|
168 |
|
169 | msg = '[' + meta.module + '] ' + msg;
|
170 |
|
171 | meta = Object.assign({}, meta);
|
172 |
|
173 | delete meta.module;
|
174 | }
|
175 | super.log(level, msg, meta, callback);
|
176 | }
|
177 | }
|
178 |
|
179 |
|
180 |
|
181 |
|
182 |
|
183 |
|
184 | container.init = function(callback) {
|
185 | if(cluster.isMaster) {
|
186 |
|
187 | const transports = container.transports;
|
188 | transports.console = new ModuleConsoleTransport(config.loggers.console);
|
189 | transports.app = new ModuleFileTransport(config.loggers.app);
|
190 | transports.access = new ModuleFileTransport(config.loggers.access);
|
191 | transports.error = new ModuleFileTransport(config.loggers.error);
|
192 | transports.email = new WinstonMail(config.loggers.email);
|
193 |
|
194 |
|
195 | transports.app.name = 'app';
|
196 | transports.access.name = 'access';
|
197 | transports.error.name = 'error';
|
198 |
|
199 | async.waterfall([
|
200 | function(callback) {
|
201 |
|
202 |
|
203 | const fileLoggers = Object.keys(config.loggers).filter(function(name) {
|
204 | const logger = config.loggers[name];
|
205 | return (brUtil.isObject(logger) && 'filename' in logger);
|
206 | }).map(function(name) {
|
207 | return config.loggers[name];
|
208 | });
|
209 | async.each(fileLoggers, function(fileLogger, callback) {
|
210 | const dirname = path.dirname(fileLogger.filename);
|
211 | async.waterfall([
|
212 |
|
213 | async.apply(mkdirp.mkdirp, dirname),
|
214 | function(made, callback) {
|
215 | if('bedrock' in fileLogger && fileLogger.bedrock.enableChownDir) {
|
216 | return chown(dirname, callback);
|
217 | }
|
218 | callback();
|
219 | },
|
220 |
|
221 | async.apply(fs.open, fileLogger.filename, 'a'),
|
222 | async.apply(fs.close),
|
223 |
|
224 | async.apply(chown, fileLogger.filename)
|
225 | ], callback);
|
226 | }, callback);
|
227 | },
|
228 | function(callback) {
|
229 |
|
230 | for(const cat in config.loggers.categories) {
|
231 | const transportNames = config.loggers.categories[cat];
|
232 | const options = {transports: []};
|
233 | transportNames.forEach(function(name) {
|
234 | options.transports.push(transports[name]);
|
235 | });
|
236 | const logger = new winston.Logger(options);
|
237 | logger.setLevels(levels);
|
238 | if(container.loggers[cat]) {
|
239 | container.loggers[cat].__proto__ = logger;
|
240 | } else {
|
241 | const wrapper = {};
|
242 | wrapper.__proto__ = logger;
|
243 | container.loggers[cat] = wrapper;
|
244 | }
|
245 | }
|
246 |
|
247 |
|
248 | winston.addColors(colors);
|
249 |
|
250 | |
251 |
|
252 |
|
253 |
|
254 |
|
255 |
|
256 | container.attach = function(worker) {
|
257 |
|
258 | worker.on('message', function(msg) {
|
259 | if(typeof msg === 'object' && msg.type === 'bedrock.logger') {
|
260 | container.get(msg.category).log(msg.level, msg.msg, msg.meta);
|
261 | }
|
262 | });
|
263 | };
|
264 |
|
265 | callback();
|
266 | }
|
267 | ], callback);
|
268 | return;
|
269 | }
|
270 |
|
271 |
|
272 |
|
273 |
|
274 | const WorkerTransport = function(options) {
|
275 | winston.Transport.call(this, options);
|
276 | this.category = options.category;
|
277 | };
|
278 | util.inherits(WorkerTransport, winston.Transport);
|
279 | WorkerTransport.prototype.name = 'worker';
|
280 | WorkerTransport.prototype.log = function(level, msg, meta, callback) {
|
281 | if(this.silent) {
|
282 | return callback(null, true);
|
283 | }
|
284 |
|
285 |
|
286 |
|
287 |
|
288 |
|
289 | meta = meta || {};
|
290 | let preformatted = null;
|
291 | const metaIsObject = brUtil.isObject(meta);
|
292 | let module = null;
|
293 | if(metaIsObject) {
|
294 | if('preformatted' in meta) {
|
295 | preformatted = meta.preformatted;
|
296 | delete meta.preformatted;
|
297 | }
|
298 | if('module' in meta) {
|
299 | module = meta.module;
|
300 | delete meta.module;
|
301 | }
|
302 | }
|
303 |
|
304 | let json;
|
305 | try {
|
306 | json = JSON.stringify(meta, null, 2);
|
307 | } catch(e) {
|
308 | json = JSON.stringify(cycle.decycle(meta), null, 2);
|
309 | }
|
310 | let error;
|
311 | if(meta instanceof Error) {
|
312 | error = ('stack' in meta) ? meta.stack : meta;
|
313 | meta = {error, workerPid: process.pid};
|
314 | } else if(metaIsObject && 'error' in meta) {
|
315 | error = ('stack' in meta.error) ? meta.error.stack : meta.error;
|
316 | meta = {error, workerPid: process.pid};
|
317 | } else {
|
318 | meta = {workerPid: process.pid};
|
319 | }
|
320 |
|
321 |
|
322 | if(json !== '{}') {
|
323 | meta.details = json;
|
324 | }
|
325 |
|
326 |
|
327 | if(preformatted) {
|
328 | meta.preformatted = preformatted;
|
329 | }
|
330 |
|
331 |
|
332 | if(module) {
|
333 | meta.module = module;
|
334 | }
|
335 |
|
336 |
|
337 | process.send({
|
338 | type: 'bedrock.logger',
|
339 | level,
|
340 | msg,
|
341 | meta,
|
342 | category: this.category
|
343 | });
|
344 | this.emit('logged');
|
345 | callback(null, true);
|
346 | };
|
347 |
|
348 |
|
349 | const lowestLevel = Object.keys(levels)[0];
|
350 | for(const cat in config.loggers.categories) {
|
351 | const logger = new winston.Logger({
|
352 | transports: [new WorkerTransport({level: lowestLevel, category: cat})]
|
353 | });
|
354 | logger.setLevels(levels);
|
355 | if(container.loggers[cat]) {
|
356 | container.loggers[cat].__proto__ = logger;
|
357 | } else {
|
358 | const wrapper = {};
|
359 | wrapper.__proto__ = logger;
|
360 | container.loggers[cat] = wrapper;
|
361 | }
|
362 | }
|
363 |
|
364 |
|
365 | winston.addColors(colors);
|
366 |
|
367 | callback();
|
368 | };
|
369 |
|
370 |
|
371 |
|
372 |
|
373 |
|
374 |
|
375 |
|
376 |
|
377 |
|
378 | container.addTransport = function(name, transport) {
|
379 | if(!cluster.isMaster) {
|
380 | return;
|
381 | }
|
382 | if(name in container.transports) {
|
383 | throw new Error(
|
384 | 'Cannot add logger transport; the transport name "' + name +
|
385 | '" is already used.');
|
386 | }
|
387 | if(!('name' in transport)) {
|
388 | transport.name = name;
|
389 | }
|
390 | container.transports[name] = transport;
|
391 | };
|