const dateFormat = require('date-format'); const os = require('os'); const util = require('util'); const path = require('path'); const styles = { // styles bold: [1, 22], italic: [3, 23], underline: [4, 24], inverse: [7, 27], // grayscale white: [37, 39], grey: [90, 39], black: [90, 39], // colors blue: [34, 39], cyan: [36, 39], green: [32, 39], magenta: [35, 39], red: [91, 39], yellow: [33, 39] }; function colorizeStart(style) { return style ? `\x1B[${styles[style][0]}m` : ''; } function colorizeEnd(style) { return style ? `\x1B[${styles[style][1]}m` : ''; } /** * Taken from masylum's fork (https://github.com/masylum/log4js-node) */ function colorize(str, style) { return colorizeStart(style) + str + colorizeEnd(style); } function timestampLevelAndCategory(loggingEvent, colour) { return colorize( util.format( '[%s] [%s] %s - ', dateFormat.asString(loggingEvent.startTime), loggingEvent.level.toString(), loggingEvent.categoryName ), colour ); } /** * BasicLayout is a simple layout for storing the logs. The logs are stored * in following format: *
 * [startTime] [logLevel] categoryName - message\n
 * 
* * @author Stephan Strittmatter */ function basicLayout(loggingEvent) { return timestampLevelAndCategory(loggingEvent) + util.format(...loggingEvent.data); } /** * colouredLayout - taken from masylum's fork. * same as basicLayout, but with colours. */ function colouredLayout(loggingEvent) { return timestampLevelAndCategory(loggingEvent, loggingEvent.level.colour) + util.format(...loggingEvent.data); } function messagePassThroughLayout(loggingEvent) { return util.format(...loggingEvent.data); } function dummyLayout(loggingEvent) { return loggingEvent.data[0]; } /** * PatternLayout * Format for specifiers is %[padding].[truncation][field]{[format]} * e.g. %5.10p - left pad the log level by 5 characters, up to a max of 10 * both padding and truncation can be negative. * Negative truncation = trunc from end of string * Positive truncation = trunc from start of string * Negative padding = pad right * Positive padding = pad left * * Fields can be any of: * - %r time in toLocaleTimeString format * - %p log level * - %c log category * - %h hostname * - %m log data * - %d date in constious formats * - %% % * - %n newline * - %z pid * - %f filename * - %l line number * - %o column postion * - %s call stack * - %x{} add dynamic tokens to your log. Tokens are specified in the tokens parameter * - %X{} add dynamic tokens to your log. Tokens are specified in logger context * You can use %[ and %] to define a colored block. * * Tokens are specified as simple key:value objects. * The key represents the token name whereas the value can be a string or function * which is called to extract the value to put in the log message. If token is not * found, it doesn't replace the field. * * A sample token would be: { 'pid' : function() { return process.pid; } } * * Takes a pattern string, array of tokens and returns a layout function. * @return {Function} * @param pattern * @param tokens * @param timezoneOffset * * @authors ['Stephan Strittmatter', 'Jan Schmidle'] */ function patternLayout(pattern, tokens) { const TTCC_CONVERSION_PATTERN = '%r %p %c - %m%n'; const regex = /%(-?[0-9]+)?(\.?-?[0-9]+)?([[\]cdhmnprzxXyflos%])(\{([^}]+)\})?|([^%]+)/; pattern = pattern || TTCC_CONVERSION_PATTERN; function categoryName(loggingEvent, specifier) { let loggerName = loggingEvent.categoryName; if (specifier) { const precision = parseInt(specifier, 10); const loggerNameBits = loggerName.split('.'); if (precision < loggerNameBits.length) { loggerName = loggerNameBits.slice(loggerNameBits.length - precision).join('.'); } } return loggerName; } function formatAsDate(loggingEvent, specifier) { let format = dateFormat.ISO8601_FORMAT; if (specifier) { format = specifier; // Pick up special cases if (format === 'ISO8601') { format = dateFormat.ISO8601_FORMAT; } else if (format === 'ISO8601_WITH_TZ_OFFSET') { format = dateFormat.ISO8601_WITH_TZ_OFFSET_FORMAT; } else if (format === 'ABSOLUTE') { format = dateFormat.ABSOLUTETIME_FORMAT; } else if (format === 'DATE') { format = dateFormat.DATETIME_FORMAT; } } // Format the date return dateFormat.asString(format, loggingEvent.startTime); } function hostname() { return os.hostname().toString(); } function formatMessage(loggingEvent) { return util.format(...loggingEvent.data); } function endOfLine() { return os.EOL; } function logLevel(loggingEvent) { return loggingEvent.level.toString(); } function startTime(loggingEvent) { return dateFormat.asString('hh:mm:ss', loggingEvent.startTime); } function startColour(loggingEvent) { return colorizeStart(loggingEvent.level.colour); } function endColour(loggingEvent) { return colorizeEnd(loggingEvent.level.colour); } function percent() { return '%'; } function pid(loggingEvent) { return loggingEvent && loggingEvent.pid ? loggingEvent.pid.toString() : process.pid.toString(); } function clusterInfo() { // this used to try to return the master and worker pids, // but it would never have worked because master pid is not available to workers // leaving this here to maintain compatibility for patterns return pid(); } function userDefined(loggingEvent, specifier) { if (typeof tokens[specifier] !== 'undefined') { return typeof tokens[specifier] === 'function' ? tokens[specifier](loggingEvent) : tokens[specifier]; } return null; } function contextDefined(loggingEvent, specifier) { const resolver = loggingEvent.context[specifier]; if (typeof resolver !== 'undefined') { return typeof resolver === 'function' ? resolver(loggingEvent) : resolver; } return null; } function fileName(loggingEvent, specifier) { let filename = loggingEvent.fileName || ''; if (specifier) { const fileDepth = parseInt(specifier, 10); const fileList = filename.split(path.sep); if (fileList.length > fileDepth) { filename = fileList.slice(-fileDepth).join(path.sep); } } return filename; } function lineNumber(loggingEvent) { return loggingEvent.lineNumber ? `${loggingEvent.lineNumber}` : ''; } function columnNumber(loggingEvent) { return loggingEvent.columnNumber ? `${loggingEvent.columnNumber}` : ''; } function callStack(loggingEvent) { return loggingEvent.callStack || ''; } /* eslint quote-props:0 */ const replacers = { c: categoryName, d: formatAsDate, h: hostname, m: formatMessage, n: endOfLine, p: logLevel, r: startTime, '[': startColour, ']': endColour, y: clusterInfo, z: pid, '%': percent, x: userDefined, X: contextDefined, f: fileName, l: lineNumber, o: columnNumber, s: callStack }; function replaceToken(conversionCharacter, loggingEvent, specifier) { return replacers[conversionCharacter](loggingEvent, specifier); } function truncate(truncation, toTruncate) { let len; if (truncation) { len = parseInt(truncation.substr(1), 10); // negative truncate length means truncate from end of string return len > 0 ? toTruncate.slice(0, len) : toTruncate.slice(len); } return toTruncate; } function pad(padding, toPad) { let len; if (padding) { if (padding.charAt(0) === '-') { len = parseInt(padding.substr(1), 10); // Right pad with spaces while (toPad.length < len) { toPad += ' '; } } else { len = parseInt(padding, 10); // Left pad with spaces while (toPad.length < len) { toPad = ` ${toPad}`; } } } return toPad; } function truncateAndPad(toTruncAndPad, truncation, padding) { let replacement = toTruncAndPad; replacement = truncate(truncation, replacement); replacement = pad(padding, replacement); return replacement; } return function (loggingEvent) { let formattedString = ''; let result; let searchString = pattern; /* eslint no-cond-assign:0 */ while ((result = regex.exec(searchString)) !== null) { // const matchedString = result[0]; const padding = result[1]; const truncation = result[2]; const conversionCharacter = result[3]; const specifier = result[5]; const text = result[6]; // Check if the pattern matched was just normal text if (text) { formattedString += text.toString(); } else { // Create a raw replacement string based on the conversion // character and specifier const replacement = replaceToken(conversionCharacter, loggingEvent, specifier); formattedString += truncateAndPad(replacement, truncation, padding); } searchString = searchString.substr(result.index + result[0].length); } return formattedString; }; } const layoutMakers = { messagePassThrough () { return messagePassThroughLayout; }, basic () { return basicLayout; }, colored () { return colouredLayout; }, coloured () { return colouredLayout; }, pattern (config) { return patternLayout(config && config.pattern, config && config.tokens); }, dummy () { return dummyLayout; } }; module.exports = { basicLayout, messagePassThroughLayout, patternLayout, colouredLayout, coloredLayout: colouredLayout, dummyLayout, addLayout (name, serializerGenerator) { layoutMakers[name] = serializerGenerator; }, layout (name, config) { return layoutMakers[name] && layoutMakers[name](config); } };