1 | const dateFormat = require('date-format');
|
---|
2 | const os = require('os');
|
---|
3 | const util = require('util');
|
---|
4 | const path = require('path');
|
---|
5 |
|
---|
6 | const styles = {
|
---|
7 | // styles
|
---|
8 | bold: [1, 22],
|
---|
9 | italic: [3, 23],
|
---|
10 | underline: [4, 24],
|
---|
11 | inverse: [7, 27],
|
---|
12 | // grayscale
|
---|
13 | white: [37, 39],
|
---|
14 | grey: [90, 39],
|
---|
15 | black: [90, 39],
|
---|
16 | // colors
|
---|
17 | blue: [34, 39],
|
---|
18 | cyan: [36, 39],
|
---|
19 | green: [32, 39],
|
---|
20 | magenta: [35, 39],
|
---|
21 | red: [91, 39],
|
---|
22 | yellow: [33, 39]
|
---|
23 | };
|
---|
24 |
|
---|
25 | function colorizeStart(style) {
|
---|
26 | return style ? `\x1B[${styles[style][0]}m` : '';
|
---|
27 | }
|
---|
28 |
|
---|
29 | function colorizeEnd(style) {
|
---|
30 | return style ? `\x1B[${styles[style][1]}m` : '';
|
---|
31 | }
|
---|
32 |
|
---|
33 | /**
|
---|
34 | * Taken from masylum's fork (https://github.com/masylum/log4js-node)
|
---|
35 | */
|
---|
36 | function colorize(str, style) {
|
---|
37 | return colorizeStart(style) + str + colorizeEnd(style);
|
---|
38 | }
|
---|
39 |
|
---|
40 | function timestampLevelAndCategory(loggingEvent, colour) {
|
---|
41 | return colorize(
|
---|
42 | util.format(
|
---|
43 | '[%s] [%s] %s - ',
|
---|
44 | dateFormat.asString(loggingEvent.startTime),
|
---|
45 | loggingEvent.level.toString(),
|
---|
46 | loggingEvent.categoryName
|
---|
47 | ),
|
---|
48 | colour
|
---|
49 | );
|
---|
50 | }
|
---|
51 |
|
---|
52 | /**
|
---|
53 | * BasicLayout is a simple layout for storing the logs. The logs are stored
|
---|
54 | * in following format:
|
---|
55 | * <pre>
|
---|
56 | * [startTime] [logLevel] categoryName - message\n
|
---|
57 | * </pre>
|
---|
58 | *
|
---|
59 | * @author Stephan Strittmatter
|
---|
60 | */
|
---|
61 | function basicLayout(loggingEvent) {
|
---|
62 | return timestampLevelAndCategory(loggingEvent) + util.format(...loggingEvent.data);
|
---|
63 | }
|
---|
64 |
|
---|
65 | /**
|
---|
66 | * colouredLayout - taken from masylum's fork.
|
---|
67 | * same as basicLayout, but with colours.
|
---|
68 | */
|
---|
69 | function colouredLayout(loggingEvent) {
|
---|
70 | return timestampLevelAndCategory(loggingEvent, loggingEvent.level.colour) + util.format(...loggingEvent.data);
|
---|
71 | }
|
---|
72 |
|
---|
73 | function messagePassThroughLayout(loggingEvent) {
|
---|
74 | return util.format(...loggingEvent.data);
|
---|
75 | }
|
---|
76 |
|
---|
77 | function dummyLayout(loggingEvent) {
|
---|
78 | return loggingEvent.data[0];
|
---|
79 | }
|
---|
80 |
|
---|
81 | /**
|
---|
82 | * PatternLayout
|
---|
83 | * Format for specifiers is %[padding].[truncation][field]{[format]}
|
---|
84 | * e.g. %5.10p - left pad the log level by 5 characters, up to a max of 10
|
---|
85 | * both padding and truncation can be negative.
|
---|
86 | * Negative truncation = trunc from end of string
|
---|
87 | * Positive truncation = trunc from start of string
|
---|
88 | * Negative padding = pad right
|
---|
89 | * Positive padding = pad left
|
---|
90 | *
|
---|
91 | * Fields can be any of:
|
---|
92 | * - %r time in toLocaleTimeString format
|
---|
93 | * - %p log level
|
---|
94 | * - %c log category
|
---|
95 | * - %h hostname
|
---|
96 | * - %m log data
|
---|
97 | * - %d date in constious formats
|
---|
98 | * - %% %
|
---|
99 | * - %n newline
|
---|
100 | * - %z pid
|
---|
101 | * - %f filename
|
---|
102 | * - %l line number
|
---|
103 | * - %o column postion
|
---|
104 | * - %s call stack
|
---|
105 | * - %x{<tokenname>} add dynamic tokens to your log. Tokens are specified in the tokens parameter
|
---|
106 | * - %X{<tokenname>} add dynamic tokens to your log. Tokens are specified in logger context
|
---|
107 | * You can use %[ and %] to define a colored block.
|
---|
108 | *
|
---|
109 | * Tokens are specified as simple key:value objects.
|
---|
110 | * The key represents the token name whereas the value can be a string or function
|
---|
111 | * which is called to extract the value to put in the log message. If token is not
|
---|
112 | * found, it doesn't replace the field.
|
---|
113 | *
|
---|
114 | * A sample token would be: { 'pid' : function() { return process.pid; } }
|
---|
115 | *
|
---|
116 | * Takes a pattern string, array of tokens and returns a layout function.
|
---|
117 | * @return {Function}
|
---|
118 | * @param pattern
|
---|
119 | * @param tokens
|
---|
120 | * @param timezoneOffset
|
---|
121 | *
|
---|
122 | * @authors ['Stephan Strittmatter', 'Jan Schmidle']
|
---|
123 | */
|
---|
124 | function patternLayout(pattern, tokens) {
|
---|
125 | const TTCC_CONVERSION_PATTERN = '%r %p %c - %m%n';
|
---|
126 | const regex = /%(-?[0-9]+)?(\.?-?[0-9]+)?([[\]cdhmnprzxXyflos%])(\{([^}]+)\})?|([^%]+)/;
|
---|
127 |
|
---|
128 | pattern = pattern || TTCC_CONVERSION_PATTERN;
|
---|
129 |
|
---|
130 | function categoryName(loggingEvent, specifier) {
|
---|
131 | let loggerName = loggingEvent.categoryName;
|
---|
132 | if (specifier) {
|
---|
133 | const precision = parseInt(specifier, 10);
|
---|
134 | const loggerNameBits = loggerName.split('.');
|
---|
135 | if (precision < loggerNameBits.length) {
|
---|
136 | loggerName = loggerNameBits.slice(loggerNameBits.length - precision).join('.');
|
---|
137 | }
|
---|
138 | }
|
---|
139 | return loggerName;
|
---|
140 | }
|
---|
141 |
|
---|
142 | function formatAsDate(loggingEvent, specifier) {
|
---|
143 | let format = dateFormat.ISO8601_FORMAT;
|
---|
144 | if (specifier) {
|
---|
145 | format = specifier;
|
---|
146 | // Pick up special cases
|
---|
147 | if (format === 'ISO8601') {
|
---|
148 | format = dateFormat.ISO8601_FORMAT;
|
---|
149 | } else if (format === 'ISO8601_WITH_TZ_OFFSET') {
|
---|
150 | format = dateFormat.ISO8601_WITH_TZ_OFFSET_FORMAT;
|
---|
151 | } else if (format === 'ABSOLUTE') {
|
---|
152 | format = dateFormat.ABSOLUTETIME_FORMAT;
|
---|
153 | } else if (format === 'DATE') {
|
---|
154 | format = dateFormat.DATETIME_FORMAT;
|
---|
155 | }
|
---|
156 | }
|
---|
157 | // Format the date
|
---|
158 | return dateFormat.asString(format, loggingEvent.startTime);
|
---|
159 | }
|
---|
160 |
|
---|
161 | function hostname() {
|
---|
162 | return os.hostname().toString();
|
---|
163 | }
|
---|
164 |
|
---|
165 | function formatMessage(loggingEvent) {
|
---|
166 | return util.format(...loggingEvent.data);
|
---|
167 | }
|
---|
168 |
|
---|
169 | function endOfLine() {
|
---|
170 | return os.EOL;
|
---|
171 | }
|
---|
172 |
|
---|
173 | function logLevel(loggingEvent) {
|
---|
174 | return loggingEvent.level.toString();
|
---|
175 | }
|
---|
176 |
|
---|
177 | function startTime(loggingEvent) {
|
---|
178 | return dateFormat.asString('hh:mm:ss', loggingEvent.startTime);
|
---|
179 | }
|
---|
180 |
|
---|
181 | function startColour(loggingEvent) {
|
---|
182 | return colorizeStart(loggingEvent.level.colour);
|
---|
183 | }
|
---|
184 |
|
---|
185 | function endColour(loggingEvent) {
|
---|
186 | return colorizeEnd(loggingEvent.level.colour);
|
---|
187 | }
|
---|
188 |
|
---|
189 | function percent() {
|
---|
190 | return '%';
|
---|
191 | }
|
---|
192 |
|
---|
193 | function pid(loggingEvent) {
|
---|
194 | return loggingEvent && loggingEvent.pid ? loggingEvent.pid.toString() : process.pid.toString();
|
---|
195 | }
|
---|
196 |
|
---|
197 | function clusterInfo() {
|
---|
198 | // this used to try to return the master and worker pids,
|
---|
199 | // but it would never have worked because master pid is not available to workers
|
---|
200 | // leaving this here to maintain compatibility for patterns
|
---|
201 | return pid();
|
---|
202 | }
|
---|
203 |
|
---|
204 | function userDefined(loggingEvent, specifier) {
|
---|
205 | if (typeof tokens[specifier] !== 'undefined') {
|
---|
206 | return typeof tokens[specifier] === 'function' ? tokens[specifier](loggingEvent) : tokens[specifier];
|
---|
207 | }
|
---|
208 |
|
---|
209 | return null;
|
---|
210 | }
|
---|
211 |
|
---|
212 | function contextDefined(loggingEvent, specifier) {
|
---|
213 | const resolver = loggingEvent.context[specifier];
|
---|
214 |
|
---|
215 | if (typeof resolver !== 'undefined') {
|
---|
216 | return typeof resolver === 'function' ? resolver(loggingEvent) : resolver;
|
---|
217 | }
|
---|
218 |
|
---|
219 | return null;
|
---|
220 | }
|
---|
221 |
|
---|
222 | function fileName(loggingEvent, specifier) {
|
---|
223 | let filename = loggingEvent.fileName || '';
|
---|
224 | if (specifier) {
|
---|
225 | const fileDepth = parseInt(specifier, 10);
|
---|
226 | const fileList = filename.split(path.sep);
|
---|
227 | if (fileList.length > fileDepth) {
|
---|
228 | filename = fileList.slice(-fileDepth).join(path.sep);
|
---|
229 | }
|
---|
230 | }
|
---|
231 |
|
---|
232 | return filename;
|
---|
233 | }
|
---|
234 |
|
---|
235 | function lineNumber(loggingEvent) {
|
---|
236 | return loggingEvent.lineNumber ? `${loggingEvent.lineNumber}` : '';
|
---|
237 | }
|
---|
238 |
|
---|
239 | function columnNumber(loggingEvent) {
|
---|
240 | return loggingEvent.columnNumber ? `${loggingEvent.columnNumber}` : '';
|
---|
241 | }
|
---|
242 |
|
---|
243 | function callStack(loggingEvent) {
|
---|
244 | return loggingEvent.callStack || '';
|
---|
245 | }
|
---|
246 |
|
---|
247 | /* eslint quote-props:0 */
|
---|
248 | const replacers = {
|
---|
249 | c: categoryName,
|
---|
250 | d: formatAsDate,
|
---|
251 | h: hostname,
|
---|
252 | m: formatMessage,
|
---|
253 | n: endOfLine,
|
---|
254 | p: logLevel,
|
---|
255 | r: startTime,
|
---|
256 | '[': startColour,
|
---|
257 | ']': endColour,
|
---|
258 | y: clusterInfo,
|
---|
259 | z: pid,
|
---|
260 | '%': percent,
|
---|
261 | x: userDefined,
|
---|
262 | X: contextDefined,
|
---|
263 | f: fileName,
|
---|
264 | l: lineNumber,
|
---|
265 | o: columnNumber,
|
---|
266 | s: callStack
|
---|
267 | };
|
---|
268 |
|
---|
269 | function replaceToken(conversionCharacter, loggingEvent, specifier) {
|
---|
270 | return replacers[conversionCharacter](loggingEvent, specifier);
|
---|
271 | }
|
---|
272 |
|
---|
273 | function truncate(truncation, toTruncate) {
|
---|
274 | let len;
|
---|
275 | if (truncation) {
|
---|
276 | len = parseInt(truncation.substr(1), 10);
|
---|
277 | // negative truncate length means truncate from end of string
|
---|
278 | return len > 0 ? toTruncate.slice(0, len) : toTruncate.slice(len);
|
---|
279 | }
|
---|
280 |
|
---|
281 | return toTruncate;
|
---|
282 | }
|
---|
283 |
|
---|
284 | function pad(padding, toPad) {
|
---|
285 | let len;
|
---|
286 | if (padding) {
|
---|
287 | if (padding.charAt(0) === '-') {
|
---|
288 | len = parseInt(padding.substr(1), 10);
|
---|
289 | // Right pad with spaces
|
---|
290 | while (toPad.length < len) {
|
---|
291 | toPad += ' ';
|
---|
292 | }
|
---|
293 | } else {
|
---|
294 | len = parseInt(padding, 10);
|
---|
295 | // Left pad with spaces
|
---|
296 | while (toPad.length < len) {
|
---|
297 | toPad = ` ${toPad}`;
|
---|
298 | }
|
---|
299 | }
|
---|
300 | }
|
---|
301 | return toPad;
|
---|
302 | }
|
---|
303 |
|
---|
304 | function truncateAndPad(toTruncAndPad, truncation, padding) {
|
---|
305 | let replacement = toTruncAndPad;
|
---|
306 | replacement = truncate(truncation, replacement);
|
---|
307 | replacement = pad(padding, replacement);
|
---|
308 | return replacement;
|
---|
309 | }
|
---|
310 |
|
---|
311 | return function (loggingEvent) {
|
---|
312 | let formattedString = '';
|
---|
313 | let result;
|
---|
314 | let searchString = pattern;
|
---|
315 |
|
---|
316 | /* eslint no-cond-assign:0 */
|
---|
317 | while ((result = regex.exec(searchString)) !== null) {
|
---|
318 | // const matchedString = result[0];
|
---|
319 | const padding = result[1];
|
---|
320 | const truncation = result[2];
|
---|
321 | const conversionCharacter = result[3];
|
---|
322 | const specifier = result[5];
|
---|
323 | const text = result[6];
|
---|
324 |
|
---|
325 | // Check if the pattern matched was just normal text
|
---|
326 | if (text) {
|
---|
327 | formattedString += text.toString();
|
---|
328 | } else {
|
---|
329 | // Create a raw replacement string based on the conversion
|
---|
330 | // character and specifier
|
---|
331 | const replacement = replaceToken(conversionCharacter, loggingEvent, specifier);
|
---|
332 | formattedString += truncateAndPad(replacement, truncation, padding);
|
---|
333 | }
|
---|
334 | searchString = searchString.substr(result.index + result[0].length);
|
---|
335 | }
|
---|
336 | return formattedString;
|
---|
337 | };
|
---|
338 | }
|
---|
339 |
|
---|
340 | const layoutMakers = {
|
---|
341 | messagePassThrough () {
|
---|
342 | return messagePassThroughLayout;
|
---|
343 | },
|
---|
344 | basic () {
|
---|
345 | return basicLayout;
|
---|
346 | },
|
---|
347 | colored () {
|
---|
348 | return colouredLayout;
|
---|
349 | },
|
---|
350 | coloured () {
|
---|
351 | return colouredLayout;
|
---|
352 | },
|
---|
353 | pattern (config) {
|
---|
354 | return patternLayout(config && config.pattern, config && config.tokens);
|
---|
355 | },
|
---|
356 | dummy () {
|
---|
357 | return dummyLayout;
|
---|
358 | }
|
---|
359 | };
|
---|
360 |
|
---|
361 | module.exports = {
|
---|
362 | basicLayout,
|
---|
363 | messagePassThroughLayout,
|
---|
364 | patternLayout,
|
---|
365 | colouredLayout,
|
---|
366 | coloredLayout: colouredLayout,
|
---|
367 | dummyLayout,
|
---|
368 | addLayout (name, serializerGenerator) {
|
---|
369 | layoutMakers[name] = serializerGenerator;
|
---|
370 | },
|
---|
371 | layout (name, config) {
|
---|
372 | return layoutMakers[name] && layoutMakers[name](config);
|
---|
373 | }
|
---|
374 | };
|
---|