| 'use strict'; |
| |
| const dateFormat = require('date-format'); |
| const os = require('os'); |
| const util = require('util'); |
| const path = require('path'); |
| |
| const eol = os.EOL || '\n'; |
| |
| 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: |
| * <pre> |
| * [startTime] [logLevel] categoryName - message\n |
| * </pre> |
| * |
| * @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 |
| * 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{<tokenname>} add dynamic tokens to your log. Tokens are specified in the tokens parameter |
| * - %X{<tokenname>} 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 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 || ''; |
| } |
| |
| function columnNumber(loggingEvent) { |
| return 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); |
| return toTruncate.substring(0, 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: function () { |
| return messagePassThroughLayout; |
| }, |
| basic: function () { |
| return basicLayout; |
| }, |
| colored: function () { |
| return colouredLayout; |
| }, |
| coloured: function () { |
| return colouredLayout; |
| }, |
| pattern: function (config) { |
| return patternLayout(config && config.pattern, config && config.tokens); |
| }, |
| dummy: function () { |
| return dummyLayout; |
| } |
| }; |
| |
| module.exports = { |
| basicLayout, |
| messagePassThroughLayout, |
| patternLayout, |
| colouredLayout, |
| coloredLayout: colouredLayout, |
| dummyLayout, |
| addLayout: function (name, serializerGenerator) { |
| layoutMakers[name] = serializerGenerator; |
| }, |
| layout: function (name, config) { |
| return layoutMakers[name] && layoutMakers[name](config); |
| } |
| }; |