1 | /*
|
2 | * Licensed under the Apache License, Version 2.0 (the "License");
|
3 | * you may not use this file except in compliance with the License.
|
4 | * You may obtain a copy of the License at
|
5 | *
|
6 | * http://www.apache.org/licenses/LICENSE-2.0
|
7 | *
|
8 | * Unless required by applicable law or agreed to in writing, software
|
9 | * distributed under the License is distributed on an "AS IS" BASIS,
|
10 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
11 | * See the License for the specific language governing permissions and
|
12 | * limitations under the License.
|
13 | */
|
14 |
|
15 | ;
|
16 |
|
17 | /* eslint-disable no-console */
|
18 | /* eslint-disable no-use-before-define */
|
19 |
|
20 | const colors = require('colors/safe');
|
21 | const jsonColorize = require('json-colorizer');
|
22 |
|
23 | /**
|
24 | * Default levels for the npm configuration.
|
25 | * @type {Object}
|
26 | */
|
27 | const levels = {
|
28 | error: 0,
|
29 | warn: 1,
|
30 | info: 2,
|
31 | http: 3,
|
32 | verbose: 4,
|
33 | debug: 5,
|
34 | silly: 6
|
35 | };
|
36 |
|
37 | /**
|
38 | * Default levels for the npm configuration.
|
39 | * @type {Object}
|
40 | */
|
41 | const colorMap = {
|
42 | error: 'red',
|
43 | warn: 'yellow',
|
44 | info: 'green',
|
45 | verbose: 'cyan',
|
46 | debug: 'blue',
|
47 | silly: 'magenta'
|
48 | };
|
49 |
|
50 | const timestamp = () => (new Date()).toLocaleTimeString();
|
51 | const colorize = level => colors[colorMap[level]](level.toUpperCase());
|
52 |
|
53 | /**
|
54 | * Helper function to test if a string is a stringified version of a JSON object
|
55 | * @param {string} str - the input string to test
|
56 | * @returns {boolean} - true iff the string can be parsed as JSON
|
57 | * @private
|
58 | */
|
59 | const isJson = (str) => {
|
60 | try {
|
61 | return (JSON.parse(str) && !!str);
|
62 | } catch (e) {
|
63 | return false;
|
64 | }
|
65 | };
|
66 |
|
67 | /**
|
68 | * Helper function to color and format JSON objects
|
69 | * @param {any} obj - the input obj to prettify
|
70 | * @returns {any} - the prettified object
|
71 | * @private
|
72 | */
|
73 | const prettifyJson = (obj) => {
|
74 | if(typeof obj === 'object' || isJson(obj)) {
|
75 | return jsonColorize(obj, { pretty: true, colors });
|
76 | }
|
77 | return obj;
|
78 | };
|
79 |
|
80 | /**
|
81 | * The default transport for logging at multiple levels to the console
|
82 | * @param {string} level - the required log level. e.g. error, warn, info, debug, etc.
|
83 | * @param {any} obj - the input obj to prettify
|
84 | * @returns {void} -
|
85 | * @private
|
86 | */
|
87 | const defaultTransportShim = (level, ...args) => {
|
88 | let mutatedLevel = level;
|
89 | let data = args;
|
90 | let first = data.shift();
|
91 |
|
92 | // Flatten log object
|
93 | if(first && typeof first === 'object' && first.level && first.message){
|
94 | const padding = first.padding && first.padding[first.level];
|
95 | if (first.level === 'error' && first.stack) {
|
96 | mutatedLevel = 'error';
|
97 | first = `${first.message}\n${first.stack}`;
|
98 | } else if (Object.keys(levels).includes(first.level)) {
|
99 | mutatedLevel = first.level;
|
100 | first = first.message;
|
101 | }
|
102 | first = padding ? `${padding} ${first}` : first;
|
103 | }
|
104 |
|
105 | data.unshift(first);
|
106 |
|
107 | const stream = ['error', 'warn'].includes(mutatedLevel) ? console.error : console.log;
|
108 |
|
109 | stream(
|
110 | `${timestamp()} - ${colorize(mutatedLevel)}:`,
|
111 | ...data
|
112 | .map(obj => obj instanceof Error ? `${obj.message}\n${obj.stack}`: obj)
|
113 | .map(prettifyJson)
|
114 | );
|
115 | };
|
116 | const defaultTransport = {};
|
117 | Object.keys(levels).forEach(level => {
|
118 | defaultTransport[level] = (...args) => defaultTransportShim(level, ...args);
|
119 | });
|
120 |
|
121 | /**
|
122 | * A utility class with static function that print to the console
|
123 | * @private
|
124 | */
|
125 | class Logger {
|
126 | /**
|
127 | * A reusable function for logging at multiple levels
|
128 | * @param {string} level - the required log level. e.g. error, warn, info, debug, etc.
|
129 | * @param {any} obj - the input obj to prettify
|
130 | * @returns {void} -
|
131 | * @private
|
132 | */
|
133 | static dispatch(level, ...args) {
|
134 | if (levels[level] > levels[this.level]){
|
135 | return;
|
136 | }
|
137 |
|
138 | this.transports.forEach(t => {
|
139 | if(t[level]){
|
140 | t[level](...args);
|
141 | }
|
142 | });
|
143 | }
|
144 |
|
145 | /**
|
146 | * Add a custom transport for logging
|
147 | * @param {Object} transport - The transport object should have function for the usual logging operations e.g. error, warn, info, debug, etc.
|
148 | * @returns {void} -
|
149 | * @private
|
150 | */
|
151 | static add(transport) {
|
152 | this.transports.push(transport);
|
153 | }
|
154 |
|
155 | /**
|
156 | * Write an error statement to the console.
|
157 | *
|
158 | * Prints to `stderr` with newline.
|
159 | * @param {any|object} data - if this is an object with properties `level` and `message` it will be flattened first
|
160 | * @param {any} args -
|
161 | * @returns {void} -
|
162 | * @private
|
163 | */
|
164 | static error(...args){ return this.dispatch('error', ...args); }
|
165 |
|
166 | /**
|
167 | * Write a warning statement to the console.
|
168 | *
|
169 | * Prints to `stderr` with newline.
|
170 | * @param {any|object} data - if this is an object with properties `level` and `message` it will be flattened first
|
171 | * @param {any} args -
|
172 | * @returns {void} -
|
173 | * @private
|
174 | */
|
175 | static warn(...args){ return this.dispatch('warn', ...args); }
|
176 |
|
177 | /**
|
178 | * Write an info statement to the console.
|
179 | *
|
180 | * Prints to `stdout` with newline.
|
181 | * @param {any|object} data - if this is an object with properties `level` and `message` it will be flattened first
|
182 | * @param {any} args -
|
183 | * @returns {void} -
|
184 | * @private
|
185 | */
|
186 | static info(...args){ return this.dispatch('info', ...args); }
|
187 |
|
188 | /**
|
189 | * Write an info statement to the console. Alias for `logger.log`
|
190 | *
|
191 | * Prints to `stdout` with newline.
|
192 | * @param {any|object} data - if this is an object with properties `level` and `message` it will be flattened first
|
193 | * @param {any} args -
|
194 | * @returns {void} -
|
195 | * @private
|
196 | */
|
197 | static log(...args){ return this.info(...args); }
|
198 |
|
199 | /**
|
200 | * Write an http statement to the console.
|
201 | *
|
202 | * Prints to `stdout` with newline.
|
203 | * @param {any|object} data - if this is an object with properties `level` and `message` it will be flattened first
|
204 | * @param {any} args -
|
205 | * @returns {void} -
|
206 | * @private
|
207 | */
|
208 | static http(...args){ return this.dispatch('http', ...args); }
|
209 |
|
210 | /**
|
211 | * Write a verbose log statement to the console.
|
212 | *
|
213 | * Prints to `stdout` with newline.
|
214 | * @param {any|object} data - if this is an object with properties `level` and `message` it will be flattened first
|
215 | * @param {any} args -
|
216 | * @returns {void} -
|
217 | * @private
|
218 | */
|
219 | static verbose(...args){ return this.dispatch('verbose', ...args); }
|
220 |
|
221 | /**
|
222 | * Write a debug statement to the console.
|
223 | *
|
224 | * Prints to `stdout` with newline.
|
225 | * @param {any|object} data - if this is an object with properties `level` and `message` it will be flattened first
|
226 | * @param {any} args -
|
227 | * @returns {void} -
|
228 | * @private
|
229 | */
|
230 | static debug(...args){ return this.dispatch('debug', ...args); }
|
231 |
|
232 | /**
|
233 | * Write a silly level statement to the console.
|
234 | *
|
235 | * Prints to `stdout` with newline.
|
236 | * @param {any|object} data - if this is an object with properties `level` and `message` it will be flattened first
|
237 | * @param {any} args -
|
238 | * @returns {void} -
|
239 | * @private
|
240 | */
|
241 | static silly(...args){ return this.dispatch('silly', ...args); }
|
242 | }
|
243 |
|
244 | // Set the default logging level
|
245 | Logger.level = 'info';
|
246 |
|
247 | // A list of user-provided logging tranports
|
248 | Logger.transports = [ defaultTransport ];
|
249 |
|
250 | module.exports = Logger;
|