1 |
|
2 |
|
3 |
|
4 |
|
5 | 'use strict';
|
6 |
|
7 | var util = require('util'),
|
8 | vmDebug = require('debug'),
|
9 | streamSpy = require('./stream-spy');
|
10 |
|
11 | exports = module.exports = debugLogger;
|
12 | exports.debug = vmDebug;
|
13 | var debugInstances = exports.debugInstances = {};
|
14 |
|
15 | exports.config = function config(options) {
|
16 | options = options || {};
|
17 | if (options.ensureNewline) {
|
18 | ensureNewline();
|
19 | }
|
20 | if (options.inspectOptions) {
|
21 | exports.inspectOptions = options.inspectOptions;
|
22 | }
|
23 | if (options.levels) {
|
24 | merge(exports.levels, options.levels);
|
25 | }
|
26 | return debugLogger;
|
27 | };
|
28 |
|
29 | exports.inspectOptions = {};
|
30 |
|
31 | exports.colors = {
|
32 | black: 0,
|
33 | red: 1,
|
34 | green: 2,
|
35 | yellow: 3,
|
36 | blue: 4,
|
37 | magenta: 5,
|
38 | cyan: 6,
|
39 | white: 7,
|
40 | };
|
41 | exports.colorReset = '\x1b[0m';
|
42 |
|
43 | exports.levels = {
|
44 | trace: {
|
45 | color: exports.colors.cyan,
|
46 | prefix: '',
|
47 | namespaceSuffix: ':trace',
|
48 | level: 0,
|
49 | fd: 1,
|
50 | },
|
51 | debug: {
|
52 | color: exports.colors.blue,
|
53 | prefix: '',
|
54 | namespaceSuffix: ':debug',
|
55 | level: 1,
|
56 | fd: 1,
|
57 | },
|
58 | log: {
|
59 | color: '',
|
60 | prefix: ' ',
|
61 | namespaceSuffix: ':log',
|
62 | level: 2,
|
63 | fd: 1,
|
64 | },
|
65 | info: {
|
66 | color: exports.colors.green,
|
67 | prefix: ' ',
|
68 | namespaceSuffix: ':info',
|
69 | level: 3,
|
70 | fd: 1,
|
71 | },
|
72 | warn: {
|
73 | color: exports.colors.yellow,
|
74 | prefix: ' ',
|
75 | namespaceSuffix: ':warn',
|
76 | level: 4,
|
77 | },
|
78 | error: {
|
79 | color: exports.colors.red,
|
80 | prefix: '',
|
81 | namespaceSuffix: ':error',
|
82 | level: 5,
|
83 | },
|
84 | };
|
85 |
|
86 | exports.styles = {
|
87 | underline: '\x1b[4m',
|
88 | };
|
89 |
|
90 |
|
91 | function time(label) {
|
92 | this.timeLabels[label] = process.hrtime();
|
93 | }
|
94 |
|
95 | function timeEnd(label, level) {
|
96 | level = level || 'log';
|
97 | var diff = process.hrtime(this.timeLabels[label]);
|
98 | var diffMs = (diff[0] * 1e9 + diff[1]) / 1e6;
|
99 | this[level](label + ':', diffMs + 'ms');
|
100 | return diffMs;
|
101 | }
|
102 |
|
103 | function dir(obj, options, level) {
|
104 | if (!level && this[options]) {
|
105 | level = options;
|
106 | options = undefined;
|
107 | }
|
108 | options = options || exports.inspectOptions;
|
109 | level = level || 'log';
|
110 | this[level](util.inspect(obj, options));
|
111 | }
|
112 |
|
113 | function assert(expression) {
|
114 | if (!expression) {
|
115 | var level = 'error';
|
116 | var arr = Array.prototype.slice.call(arguments, 1);
|
117 | if (this[arr[arr.length - 1]]) {
|
118 | level = arr[arr.length - 1];
|
119 | arr = arr.slice(0, -1);
|
120 | }
|
121 | var assrt = require('assert');
|
122 | var err = new assrt.AssertionError({
|
123 | message: util.format.apply(this, arr),
|
124 | actual: false,
|
125 | expected: true,
|
126 | operator: '==',
|
127 | stackStartFunction: assert,
|
128 | });
|
129 | this[level](err);
|
130 | throw err;
|
131 | }
|
132 | }
|
133 |
|
134 |
|
135 | var ensureNewlineEnabled = false;
|
136 | var fd = parseInt(process.env.DEBUG_FD, 10) || 2;
|
137 |
|
138 | function ensureNewline() {
|
139 | if (fd !== 1 && fd !== 2) {
|
140 | return;
|
141 | }
|
142 | streamSpy.enable();
|
143 | ensureNewlineEnabled = true;
|
144 | return debugLogger;
|
145 | }
|
146 |
|
147 | function merge(object, source) {
|
148 | Object.keys(source).forEach(function(key) {
|
149 | var val = source[key];
|
150 |
|
151 | if (!object[key] || !isObject(val)) {
|
152 | object[key] = val;
|
153 | return;
|
154 | }
|
155 | Object.keys(val).forEach(function(idx) {
|
156 | object[key][idx] = val[idx];
|
157 | });
|
158 | });
|
159 | }
|
160 |
|
161 | function getLogLevel(namespace) {
|
162 | if (!process.env.DEBUG_LEVEL) {
|
163 | return 0;
|
164 | }
|
165 | var debugLevel = process.env.DEBUG_LEVEL.toLowerCase();
|
166 | if (debugLevel.indexOf('*:') === 0) {
|
167 | return hasLogLevel(debugLevel.slice(2)) || 0;
|
168 | }
|
169 | var hasLevel = hasLogLevel(debugLevel);
|
170 | if (hasLevel !== null) {
|
171 | return hasLevel;
|
172 | }
|
173 | if (!namespace) {
|
174 | return 0;
|
175 | }
|
176 |
|
177 | var appNamespace = namespace.split(':')[0].toLowerCase();
|
178 |
|
179 | var debugLevelParts = debugLevel.split(',');
|
180 |
|
181 | var i;
|
182 | for (i = 0; i < debugLevelParts.length; i++) {
|
183 | var parts = debugLevelParts[i].split(':');
|
184 | if (appNamespace === parts[0]) {
|
185 | return hasLogLevel(parts[parts.length - 1]) || 0;
|
186 | }
|
187 | }
|
188 | return 0;
|
189 | }
|
190 |
|
191 | function hasLogLevel(level) {
|
192 | if (!level) {
|
193 | return null;
|
194 | }
|
195 | if (!isNaN(level)) {
|
196 | return level;
|
197 | } else if (isString(level) && exports.levels[level]) {
|
198 | return exports.levels[level].level || 0;
|
199 | }
|
200 | return null;
|
201 | }
|
202 |
|
203 | function isString(str) {
|
204 | return typeof str === 'string' || str instanceof String;
|
205 | }
|
206 |
|
207 | function isObject(obj) {
|
208 | return typeof obj === 'object' || obj instanceof Object;
|
209 | }
|
210 |
|
211 | function hasFormattingElements(str) {
|
212 | if (!str) {
|
213 | return false;
|
214 | }
|
215 | var res = false;
|
216 | ['%s', '%d', '%j', '%o'].forEach(function(elem) {
|
217 | if (str.indexOf(elem) >= 0) {
|
218 | res = true;
|
219 | }
|
220 | });
|
221 | return res;
|
222 | }
|
223 |
|
224 | function getErrorMessage(e) {
|
225 | var errorStrings = ['' + e];
|
226 |
|
227 | if (typeof e === 'undefined') {
|
228 | return errorStrings;
|
229 | }
|
230 | if (e === null) {
|
231 | return errorStrings;
|
232 | }
|
233 | if (e instanceof Date) {
|
234 | return errorStrings;
|
235 | }
|
236 | if (e instanceof Error) {
|
237 | errorStrings[0] = e.toString();
|
238 | if (e.stack) {
|
239 | errorStrings[1] = 'Stack trace';
|
240 | errorStrings[2] = e.stack;
|
241 | }
|
242 | return errorStrings;
|
243 | }
|
244 | if (isObject(e)) {
|
245 | var inspection = util.inspect(e, exports.inspectOptions);
|
246 | if (inspection.length < 55) {
|
247 | errorStrings[0] = inspection;
|
248 | return errorStrings;
|
249 | }
|
250 | if (typeof e.toString !== 'undefined') {
|
251 | errorStrings[0] = e.toString();
|
252 | }
|
253 | errorStrings[1] = 'Inspected object';
|
254 | errorStrings[2] = inspection;
|
255 | }
|
256 |
|
257 | return errorStrings;
|
258 | }
|
259 |
|
260 | function getForeColor(color) {
|
261 | if (!isNaN(color)) {
|
262 | return '\x1b[3' + color + 'm';
|
263 | } else if (exports.colors[color]) {
|
264 | return '\x1b[3' + exports.colors[color] + 'm';
|
265 | }
|
266 | return color;
|
267 | }
|
268 |
|
269 | function disableColors(loggerLevel, disable) {
|
270 | if (disable) {
|
271 | loggerLevel.color = '';
|
272 | loggerLevel.reset = '';
|
273 | loggerLevel.inspectionHighlight = '';
|
274 | }
|
275 | }
|
276 |
|
277 | function getDebugInstance(namespace, color, fd) {
|
278 | if (!debugInstances[namespace]) {
|
279 | debugInstances[namespace] = vmDebug(namespace);
|
280 | if (fd === 1 && isNaN(parseInt(process.env.DEBUG_FD))) {
|
281 | debugInstances[namespace].log = console.log.bind(console);
|
282 | }
|
283 | if (!isNaN(color)) {
|
284 | debugInstances[namespace].color = color;
|
285 | }
|
286 | }
|
287 | return debugInstances[namespace];
|
288 | }
|
289 |
|
290 |
|
291 | function debugLogger(namespace) {
|
292 | var levels = exports.levels;
|
293 | var debugLoggers = { 'default': getDebugInstance.bind(this, namespace, '') };
|
294 |
|
295 | var logger = function() {
|
296 | debugLoggers['default']().apply(this, arguments);
|
297 | };
|
298 | logger.logLevel = getLogLevel(namespace);
|
299 |
|
300 | logger.timeLabels = {};
|
301 | logger.time = time;
|
302 | logger.timeEnd = timeEnd;
|
303 | logger.dir = dir;
|
304 | logger.assert = assert;
|
305 |
|
306 | Object.keys(levels).forEach(function(levelName) {
|
307 | var loggerNamespaceSuffix = levels[levelName].namespaceSuffix ? levels[levelName].namespaceSuffix : 'default';
|
308 | if (!debugLoggers[loggerNamespaceSuffix]) {
|
309 | debugLoggers[loggerNamespaceSuffix] = getDebugInstance.bind(this, namespace + loggerNamespaceSuffix, levels[levelName].color, levels[levelName].fd);
|
310 | }
|
311 | var levelLogger = debugLoggers[loggerNamespaceSuffix];
|
312 |
|
313 | var initialized = false;
|
314 |
|
315 | function logFn() {
|
316 | if (logger.logLevel > logger[levelName].level) {
|
317 | return;
|
318 | }
|
319 |
|
320 | var levelLog = levelLogger();
|
321 | if (!levelLog.enabled) {
|
322 | return;
|
323 | }
|
324 |
|
325 | if (!initialized) {
|
326 | initialized = true;
|
327 | disableColors(logger[levelName], !levelLog.useColors);
|
328 | }
|
329 |
|
330 | if (isString(arguments[0]) && hasFormattingElements(arguments[0])) {
|
331 | arguments[0] = logger[levelName].color + levels[levelName].prefix + logger[levelName].reset + arguments[0];
|
332 | return levelLog.apply(this, arguments);
|
333 | }
|
334 |
|
335 | var selfArguments = arguments;
|
336 | var errorStrings = Object.keys(selfArguments).map(function(key) {
|
337 | return getErrorMessage(selfArguments[key]);
|
338 | });
|
339 | var message = '';
|
340 | var inspections = '';
|
341 |
|
342 | var i, param;
|
343 | var n = 1;
|
344 | for (i = 0; i < errorStrings.length; i++) {
|
345 | param = errorStrings[i];
|
346 | message += i === 0 ? param[0] : ' ' + param[0];
|
347 | if (param.length > 1) {
|
348 | var highlightStack = param[1].indexOf('Stack') >= 0 ? logger[levelName].color : '';
|
349 | inspections += '\n' +
|
350 | logger[levelName].inspectionHighlight + '___' + param[1] + ' #' + n++ + '___' + logger[levelName].reset + '\n' +
|
351 | highlightStack + param[2] + logger[levelName].reset;
|
352 | }
|
353 | }
|
354 | ;
|
355 |
|
356 | levelLog(logger[levelName].color + levels[levelName].prefix + logger[levelName].reset + message + inspections);
|
357 | };
|
358 |
|
359 | function logNewlineFn() {
|
360 | if (streamSpy.lastCharacter !== '\n') {
|
361 | vmDebug.log('');
|
362 | }
|
363 | logFn.apply(logFn, arguments);
|
364 | };
|
365 |
|
366 | logger[levelName] = ensureNewlineEnabled ? logNewlineFn : logFn;
|
367 | logger[levelName].level = levels[levelName].level;
|
368 | logger[levelName].logger = function() {
|
369 | return levelLogger();
|
370 | };
|
371 | logger[levelName].enabled = function() {
|
372 | return logger.logLevel <= logger[levelName].level && levelLogger().enabled;
|
373 | };
|
374 | logger[levelName].color = getForeColor(levels[levelName].color);
|
375 | logger[levelName].reset = exports.colorReset;
|
376 | logger[levelName].inspectionHighlight = exports.styles.underline;
|
377 | });
|
378 |
|
379 | return logger;
|
380 | }
|