-
Notifications
You must be signed in to change notification settings - Fork 5
/
palin.js
196 lines (166 loc) · 6.45 KB
/
palin.js
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
187
188
189
190
191
192
193
194
195
196
'use strict';
const util = require('util'); // node's util
const chalk = require('chalk');
const check = require('check-types');
// shortens the file name to exclude any path before the project folder name
// @param path: file path string. e.g. /home/mark/myproj/run.js
// @param rootFolderName: root folder for the project. e.g. "myproj"
// @return the shortened file path string
function truncFilename(path, rootFolderName) {
// bail if a string wasn't provided
if (typeof path !== 'string') {
return path;
}
var index = path.indexOf(rootFolderName);
if (index > 0) {
return path.substring(index + rootFolderName.length + 1);
} else {
return path;
}
}
// retruns the colorized timestamp string
function getTimestampString(date) {
// all this nasty code is faster (~30k ops/sec) than doing "moment(date).format('HH:mm:ss:SSS')" and means 0 dependencies
var hour = '0' + date.getHours();
hour = hour.slice(hour.length - 2);
var minute = '0' + date.getMinutes();
minute = minute.slice(minute.length - 2);
var second = '0' + date.getSeconds();
second = second.slice(second.length - 2);
var ms = '' + date.getMilliseconds();
// https://github.com/MarkHerhold/palin/issues/6
// this is faster than using an actual left-pad algorithm
if (ms.length === 1) {
ms = '00' + ms;
} else if (ms.length === 2) {
ms = '0' + ms;
} // no modifications for 3 or more digits
return chalk.dim(`${hour}:${minute}:${second}:${ms}`);
}
const severityMap = {
error: 'bgRed',
warn: 'bgYellow',
info: 'white',
debug: 'white',
trace: 'white'
};
// returns the colorized text for the given severity level
function getColorSeverity(severity) {
// get the color associated with the severity level
const color = severityMap[severity] || 'white';
return chalk[color].bold(severity.toUpperCase());
}
var prevColor = 0; // keep track of the previous scope color
const colors = [/*'black',*/ 'red', 'green', 'yellow', 'blue', 'magenta', 'cyan', 'white', 'gray'];
var scopeColorMap = {};
function getScopeColor(scope) {
if (scopeColorMap[scope]) {
return scopeColorMap[scope];
} else {
var color = colors[prevColor++ % colors.length];
scopeColorMap[scope] = color;
return color;
}
}
// returns the colorized scope string
function getScopeString(scope) {
return chalk[getScopeColor(scope)].bold(scope);
}
// default indentation string. Lives here so it is not recomputed every time formatter() is called
const defaultIndent = chalk.gray('\n → ');
// the formatter to export
const formatter = function formatter(options, severity, date, elems) {
/*
OPTIONS
*/
const indent = options.indent || defaultIndent;
const objectDepth = options.objectDepth;
const source = options.hasOwnProperty('source') ? options.source : true;
const timestamp = (function () {
if (check.function(options.timestamp)) {
return options.timestamp; // user-provided timestamp generating function
} else if (options.timestamp === false) {
return false; // no timestamp
} else {
return getTimestampString; // default timestamp generating function
}
})();
const rootFolderName = options.rootFolderName;
/*
LOGIC
*/
// the last element is an aggregate object of all of the additional passed in elements
const aggObj = elems[elems.length - 1];
// initial log string
let build = ' ';
// add the date
if (timestamp !== false) {
// otherwise, use the default timestamp generator function
build += ' ' + timestamp(date);
}
build += ' ' + getColorSeverity(severity) + ' ';
// add the component if provided
if (aggObj.scope) {
build += getScopeString(aggObj.scope) + ' ';
delete aggObj.scope;
}
// errors are a special case that we absolutely need to keep track of and log the entire stack
const errors = [];
for (let i = 0; i < elems.length - 1; i++) { // iterate through all elements in the array except the last (obj map of options)
const element = elems[i];
// Attempt to determine an appropriate title given the first element
if (i === 0) {
let elementConsumed = false;
if (check.string(element)) {
// string is obviously the title
build += chalk.blue(element);
elementConsumed = true;
} else if (check.instance(element, Error)) {
// title is the error text representation
build += chalk.blue(element.message || '[no message]');
// also store error stacktrace in the aggregate object
errors.push(element);
elementConsumed = true;
}
// add on the file and line number, which always go after the title, inline
if (aggObj.file && aggObj.line) {
if (source){
aggObj.file = truncFilename(aggObj.file, rootFolderName);
build += chalk.dim(` (${aggObj.file}:${aggObj.line})`);
}
delete aggObj.file;
delete aggObj.line;
}
// do not add element 0 to the 'extra' data section
if (elementConsumed) {
continue;
}
}
// add the element to the errors array if it's an error
if (check.instance(element, Error)) {
errors.push(element);
// the error will be concatinated later so continue to the next element
continue;
}
const objString = '\n' + util.inspect(element, { colors: true, depth: objectDepth, compact: true });
build += objString.replace(/\n/g, indent);
}
if (Object.keys(aggObj).length > 0) {
const objString = '\n' + util.inspect(aggObj, { colors: true, depth: objectDepth, compact: true });
build += objString.replace(/\n/g, indent);
}
// iterate through the top-level object keys looking for Errors as well
for (const o of Object.keys(aggObj)) {
if (check.instance(o, Error)) {
errors.push(o);
}
}
// iterate through all the Error objects and print the stacks
for (const e of errors) {
build += indent + e.stack.replace(/\n/g, indent);
}
return build;
};
module.exports = formatter;
// further exports for testing
formatter._getTimestampString = getTimestampString;