@nestjs/common
Version:
Nest - modern, fast, powerful node.js web framework (@common)
452 lines (451 loc) • 15.9 kB
JavaScript
var ConsoleLogger_1;
import { __decorate, __metadata, __param } from "tslib";
import { inspect } from 'util';
import { Injectable, Optional } from '../decorators/core/index.js';
import { clc, yellow, isColorAllowed } from '../utils/cli-colors.util.js';
import { isFunction, isPlainObject, isString, isUndefined, } from '../utils/shared.utils.js';
import { isLogLevelEnabled } from './utils/is-log-level-enabled.util.js';
const DEFAULT_DEPTH = 5;
const DEFAULT_LOG_LEVELS = [
'log',
'error',
'warn',
'debug',
'verbose',
'fatal',
];
const dateTimeFormatter = new Intl.DateTimeFormat(undefined, {
year: 'numeric',
hour: 'numeric',
minute: 'numeric',
second: 'numeric',
day: '2-digit',
month: '2-digit',
});
/**
* @publicApi
*/
let ConsoleLogger = class ConsoleLogger {
static { ConsoleLogger_1 = this; }
/**
* The options of the logger.
*/
options;
/**
* The context of the logger (can be set manually or automatically inferred).
*/
context;
/**
* The original context of the logger (set in the constructor).
*/
originalContext;
/**
* The options used for the "inspect" method.
*/
inspectOptions;
/**
* The last timestamp at which the log message was printed.
*/
static lastTimestampAt;
constructor(contextOrOptions, options) {
// eslint-disable-next-line prefer-const
let [context, opts] = isString(contextOrOptions)
? [contextOrOptions, options]
: options
? [undefined, options]
: [contextOrOptions?.context, contextOrOptions];
opts = opts ?? {};
opts.logLevels ??= DEFAULT_LOG_LEVELS;
opts.colors ??= opts.colors ?? (opts.json ? false : isColorAllowed());
opts.prefix ??= 'Nest';
this.options = opts;
this.inspectOptions = this.getInspectOptions();
if (context) {
this.context = context;
this.originalContext = context;
}
}
log(message, ...optionalParams) {
if (!this.isLevelEnabled('log')) {
return;
}
const { messages, context, params } = this.getContextAndMessagesToPrint([
message,
...optionalParams,
]);
this.printMessages(messages, context, 'log', 'stdout', undefined, params);
}
error(message, ...optionalParams) {
if (!this.isLevelEnabled('error')) {
return;
}
const { messages, context, stack, params } = this.getContextAndStackAndMessagesToPrint([message, ...optionalParams]);
this.printMessages(messages, context, 'error', 'stderr', stack, params);
this.printStackTrace(stack);
}
warn(message, ...optionalParams) {
if (!this.isLevelEnabled('warn')) {
return;
}
const { messages, context, params } = this.getContextAndMessagesToPrint([
message,
...optionalParams,
]);
this.printMessages(messages, context, 'warn', 'stdout', undefined, params);
}
debug(message, ...optionalParams) {
if (!this.isLevelEnabled('debug')) {
return;
}
const { messages, context, params } = this.getContextAndMessagesToPrint([
message,
...optionalParams,
]);
this.printMessages(messages, context, 'debug', 'stdout', undefined, params);
}
verbose(message, ...optionalParams) {
if (!this.isLevelEnabled('verbose')) {
return;
}
const { messages, context, params } = this.getContextAndMessagesToPrint([
message,
...optionalParams,
]);
this.printMessages(messages, context, 'verbose', 'stdout', undefined, params);
}
fatal(message, ...optionalParams) {
if (!this.isLevelEnabled('fatal')) {
return;
}
const { messages, context, params } = this.getContextAndMessagesToPrint([
message,
...optionalParams,
]);
this.printMessages(messages, context, 'fatal', 'stdout', undefined, params);
}
/**
* Set log levels
* @param levels log levels
*/
setLogLevels(levels) {
if (!this.options) {
this.options = {};
}
this.options.logLevels = levels;
}
/**
* Set logger context
* @param context context
*/
setContext(context) {
this.context = context;
}
/**
* Resets the logger context to the value that was passed in the constructor.
*/
resetContext() {
this.context = this.originalContext;
}
isLevelEnabled(level) {
const logLevels = this.options?.logLevels;
return isLogLevelEnabled(level, logLevels);
}
getTimestamp() {
return dateTimeFormatter.format(Date.now());
}
printMessages(messages, context = '', logLevel = 'log', writeStreamType, errorStack, params) {
messages.forEach(message => {
if (this.options.json) {
this.printAsJson(message, {
context,
logLevel,
writeStreamType,
errorStack,
params,
});
return;
}
const pidMessage = this.formatPid(process.pid);
const contextMessage = this.formatContext(context);
const timestampDiff = this.updateAndGetTimestampDiff();
const formattedLogLevel = logLevel.toUpperCase().padStart(7, ' ');
const formattedMessage = this.formatMessage(logLevel, message, pidMessage, formattedLogLevel, contextMessage, timestampDiff, params);
if (this.options.forceConsole) {
if (writeStreamType === 'stderr') {
console.error(formattedMessage.trim());
}
else {
console.log(formattedMessage.trim());
}
}
else {
process[writeStreamType ?? 'stdout'].write(formattedMessage);
}
});
}
printAsJson(message, options) {
const logObject = this.getJsonLogObject(message, options);
const formattedMessage = !this.options.colors && this.inspectOptions.compact === true
? JSON.stringify(logObject, this.stringifyReplacer)
: inspect(logObject, this.inspectOptions);
if (this.options.forceConsole) {
if (options.writeStreamType === 'stderr') {
console.error(formattedMessage);
}
else {
console.log(formattedMessage);
}
}
else {
process[options.writeStreamType ?? 'stdout'].write(`${formattedMessage}\n`);
}
}
getJsonLogObject(message, options) {
const logObject = {
level: options.logLevel,
pid: process.pid,
timestamp: Date.now(),
message,
};
if (options.context) {
logObject.context = options.context;
}
if (options.errorStack) {
logObject.stack = options.errorStack;
}
if (options.params) {
if (this.options.flattenParams) {
// Framework fields win on key collisions: a param named "message" or
// "level" must not clobber the actual log message or severity.
for (const [key, value] of Object.entries(options.params)) {
if (!(key in logObject)) {
logObject[key] = value;
}
}
}
else {
logObject.params = options.params;
}
}
return logObject;
}
formatPid(pid) {
return `[${this.options.prefix}] ${pid} - `;
}
formatContext(context) {
if (!context) {
return '';
}
context = `[${context}] `;
return this.options.colors ? yellow(context) : context;
}
formatMessage(logLevel, message, pidMessage, formattedLogLevel, contextMessage, timestampDiff, params) {
const output = this.stringifyMessage(message, logLevel);
pidMessage = this.colorize(pidMessage, logLevel);
formattedLogLevel = this.colorize(formattedLogLevel, logLevel);
const paramsOutput = params ? ` ${this.stringifyParams(params)}` : '';
return `${pidMessage}${this.getTimestamp()} ${formattedLogLevel} ${contextMessage}${output}${paramsOutput}${timestampDiff}\n`;
}
stringifyParams(params) {
return inspect(params, {
...this.inspectOptions,
compact: true,
breakLength: Infinity,
});
}
stringifyMessage(message, logLevel) {
if (isFunction(message)) {
const messageAsStr = Function.prototype.toString.call(message);
const isClass = messageAsStr.startsWith('class ');
if (isClass) {
// If the message is a class, we will display the class name.
return this.stringifyMessage(message.name, logLevel);
}
// If the message is a non-class function, call it and re-resolve its value.
return this.stringifyMessage(message(), logLevel);
}
if (typeof message === 'string') {
return this.colorize(message, logLevel);
}
const outputText = inspect(message, this.inspectOptions);
if (isPlainObject(message)) {
return `Object(${Object.keys(message).length}) ${outputText}`;
}
if (Array.isArray(message)) {
return `Array(${message.length}) ${outputText}`;
}
return outputText;
}
colorize(message, logLevel) {
if (!this.options.colors || this.options.json) {
return message;
}
const color = this.getColorByLogLevel(logLevel);
return color(message);
}
printStackTrace(stack) {
if (!stack || this.options.json) {
return;
}
if (this.options.forceConsole) {
console.error(stack);
}
else {
process.stderr.write(`${stack}\n`);
}
}
updateAndGetTimestampDiff() {
const includeTimestamp = ConsoleLogger_1.lastTimestampAt && this.options?.timestamp;
const result = includeTimestamp
? this.formatTimestampDiff(Date.now() - ConsoleLogger_1.lastTimestampAt)
: '';
ConsoleLogger_1.lastTimestampAt = Date.now();
return result;
}
formatTimestampDiff(timestampDiff) {
const formattedDiff = ` +${timestampDiff}ms`;
return this.options.colors ? yellow(formattedDiff) : formattedDiff;
}
getInspectOptions() {
let breakLength = this.options.breakLength;
if (typeof breakLength === 'undefined') {
breakLength = this.options.colors
? this.options.compact
? Infinity
: undefined
: this.options.compact === false
? undefined
: Infinity; // default breakLength to Infinity if inline is not set and colors is false
}
const inspectOptions = {
depth: this.options.depth ?? DEFAULT_DEPTH,
sorted: this.options.sorted,
showHidden: this.options.showHidden,
compact: this.options.compact ?? (this.options.json ? true : false),
colors: this.options.colors,
breakLength,
};
if (typeof this.options.maxArrayLength !== 'undefined') {
inspectOptions.maxArrayLength = this.options.maxArrayLength;
}
if (typeof this.options.maxStringLength !== 'undefined') {
inspectOptions.maxStringLength = this.options.maxStringLength;
}
return inspectOptions;
}
stringifyReplacer(key, value) {
// Mimic util.inspect behavior for JSON logger with compact on and colors off
if (typeof value === 'bigint') {
return value.toString();
}
if (typeof value === 'symbol') {
return value.toString();
}
if (value instanceof Map ||
value instanceof Set ||
value instanceof Error) {
return `${inspect(value, this.inspectOptions)}`;
}
return value;
}
getContextAndMessagesToPrint(args) {
if (args?.length <= 1) {
return { messages: args, context: this.context };
}
const lastElement = args[args.length - 1];
const isContext = isString(lastElement);
let context;
let remainingArgs;
if (isContext) {
context = lastElement;
remainingArgs = args.slice(0, args.length - 1);
}
else {
context = this.context;
remainingArgs = args;
}
if (this.options.structuredParams === false) {
return { messages: remainingArgs, context };
}
// Extract plain objects (excluding the first arg which is always the message) as params
const messages = [remainingArgs[0]];
const paramObjects = [];
for (let i = 1; i < remainingArgs.length; i++) {
if (isPlainObject(remainingArgs[i])) {
paramObjects.push(remainingArgs[i]);
}
else {
messages.push(remainingArgs[i]);
}
}
const params = paramObjects.length > 0 ? Object.assign({}, ...paramObjects) : undefined;
return { messages, context, params };
}
getContextAndStackAndMessagesToPrint(args) {
if (args.length === 2) {
if (this.isStackFormat(args[1])) {
return {
messages: [args[0]],
stack: args[1],
context: this.context,
};
}
return { ...this.getContextAndMessagesToPrint(args) };
}
const trailingArg = args[args.length - 1];
if (this.isStackFormat(trailingArg)) {
const { messages, context, params } = this.getContextAndMessagesToPrint(args.slice(0, -1));
return {
messages,
context,
stack: trailingArg,
params,
};
}
const { messages, context, params } = this.getContextAndMessagesToPrint(args);
if (messages?.length <= 1) {
return { messages, context, params };
}
const lastElement = messages[messages.length - 1];
const isStack = isString(lastElement);
// https://github.com/nestjs/nest/issues/11074#issuecomment-1421680060
if (!isStack && !isUndefined(lastElement)) {
return { messages, context, params };
}
return {
stack: lastElement,
messages: messages.slice(0, messages.length - 1),
context,
params,
};
}
isStackFormat(stack) {
if (!isString(stack) && !isUndefined(stack)) {
return false;
}
return /^(.)+\n\s+at .+:\d+:\d+/.test(stack);
}
getColorByLogLevel(level) {
switch (level) {
case 'debug':
return clc.magentaBright;
case 'warn':
return clc.yellow;
case 'error':
return clc.red;
case 'verbose':
return clc.cyanBright;
case 'fatal':
return clc.bold;
default:
return clc.green;
}
}
};
ConsoleLogger = ConsoleLogger_1 = __decorate([
Injectable(),
__param(0, Optional()),
__param(1, Optional()),
__metadata("design:paramtypes", [Object, Object])
], ConsoleLogger);
export { ConsoleLogger };