UNPKG

9.17 kBJavaScriptView Raw
1/*
2 * https://github.com/appscot/debug-logger
3 */
4
5'use strict';
6
7var util = require('util'),
8 vmDebug = require('debug'),
9 streamSpy = require('./stream-spy');
10
11exports = module.exports = debugLogger;
12exports.debug = vmDebug;
13var debugInstances = exports.debugInstances = {};
14
15exports.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
29exports.inspectOptions = {};
30
31exports.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};
41exports.colorReset = '\x1b[0m';
42
43exports.levels = {
44 trace: {
45 color: exports.colors.cyan,
46 prefix: '',
47 namespaceSuffix: ':trace',
48 level: 0,
49 fd: 1, // currently only 1 (stdout) is supported. stderr is debug's standard
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
86exports.styles = {
87 underline: '\x1b[4m',
88};
89
90
91function time(label) {
92 this.timeLabels[label] = process.hrtime();
93}
94
95function 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
103function 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
113function 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
135var ensureNewlineEnabled = false;
136var fd = parseInt(process.env.DEBUG_FD, 10) || 2;
137
138function ensureNewline() {
139 if (fd !== 1 && fd !== 2) {
140 return;
141 }
142 streamSpy.enable();
143 ensureNewlineEnabled = true;
144 return debugLogger;
145}
146
147function 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
161function 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 //currently we will only process the first part of namespace
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
191function 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
203function isString(str) {
204 return typeof str === 'string' || str instanceof String;
205}
206
207function isObject(obj) {
208 return typeof obj === 'object' || obj instanceof Object;
209}
210
211function 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
224function 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
260function 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
269function disableColors(loggerLevel, disable) {
270 if (disable) {
271 loggerLevel.color = '';
272 loggerLevel.reset = '';
273 loggerLevel.inspectionHighlight = '';
274 }
275}
276
277function 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
291function 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}