2023-12-14 07:49:27 -05:00
|
|
|
const winston = require('winston');
|
|
|
|
const traverse = require('traverse');
|
|
|
|
const { klona } = require('klona/full');
|
|
|
|
|
2023-12-16 20:45:27 -05:00
|
|
|
const SPLAT_SYMBOL = Symbol.for('splat');
|
|
|
|
const MESSAGE_SYMBOL = Symbol.for('message');
|
|
|
|
|
|
|
|
const sensitiveKeys = [/^(sk-)[^\s]+/, /(Bearer )[^\s]+/, /(api-key:? )[^\s]+/, /(key=)[^\s]+/];
|
2023-12-14 07:49:27 -05:00
|
|
|
|
|
|
|
/**
|
2023-12-16 20:45:27 -05:00
|
|
|
* Determines if a given value string is sensitive and returns matching regex patterns.
|
2023-12-14 07:49:27 -05:00
|
|
|
*
|
2023-12-16 20:45:27 -05:00
|
|
|
* @param {string} valueStr - The value string to check.
|
|
|
|
* @returns {Array<RegExp>} An array of regex patterns that match the value string.
|
2023-12-14 07:49:27 -05:00
|
|
|
*/
|
2023-12-16 20:45:27 -05:00
|
|
|
function getMatchingSensitivePatterns(valueStr) {
|
|
|
|
if (valueStr) {
|
|
|
|
// Filter and return all regex patterns that match the value string
|
|
|
|
return sensitiveKeys.filter((regex) => regex.test(valueStr));
|
2023-12-14 07:49:27 -05:00
|
|
|
}
|
2023-12-16 20:45:27 -05:00
|
|
|
return [];
|
2023-12-14 07:49:27 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2023-12-16 20:45:27 -05:00
|
|
|
* Redacts sensitive information from a console message.
|
2023-12-14 07:49:27 -05:00
|
|
|
*
|
2023-12-16 20:45:27 -05:00
|
|
|
* @param {string} str - The console message to be redacted.
|
|
|
|
* @returns {string} - The redacted console message.
|
2023-12-14 07:49:27 -05:00
|
|
|
*/
|
2023-12-16 20:45:27 -05:00
|
|
|
function redactMessage(str) {
|
|
|
|
const patterns = getMatchingSensitivePatterns(str);
|
|
|
|
|
|
|
|
if (patterns.length === 0) {
|
|
|
|
return str;
|
|
|
|
}
|
|
|
|
|
|
|
|
patterns.forEach((pattern) => {
|
|
|
|
str = str.replace(pattern, '$1[REDACTED]');
|
2023-12-14 07:49:27 -05:00
|
|
|
});
|
2023-12-16 20:45:27 -05:00
|
|
|
|
|
|
|
return str;
|
2023-12-14 07:49:27 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2023-12-16 20:45:27 -05:00
|
|
|
* Redacts sensitive information from log messages if the log level is 'error'.
|
|
|
|
* Note: Intentionally mutates the object.
|
|
|
|
* @param {Object} info - The log information object.
|
|
|
|
* @returns {Object} - The modified log information object.
|
2023-12-14 07:49:27 -05:00
|
|
|
*/
|
2023-12-16 20:45:27 -05:00
|
|
|
const redactFormat = winston.format((info) => {
|
|
|
|
if (info.level === 'error') {
|
|
|
|
info.message = redactMessage(info.message);
|
|
|
|
if (info[MESSAGE_SYMBOL]) {
|
|
|
|
info[MESSAGE_SYMBOL] = redactMessage(info[MESSAGE_SYMBOL]);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return info;
|
|
|
|
});
|
2023-12-14 07:49:27 -05:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Truncates long strings, especially base64 image data, within log messages.
|
|
|
|
*
|
|
|
|
* @param {any} value - The value to be inspected and potentially truncated.
|
2023-12-16 20:45:27 -05:00
|
|
|
* @param {number} [length] - The length at which to truncate the value. Default: 100.
|
2023-12-14 07:49:27 -05:00
|
|
|
* @returns {any} - The truncated or original value.
|
|
|
|
*/
|
2023-12-16 20:45:27 -05:00
|
|
|
const truncateLongStrings = (value, length = 100) => {
|
2023-12-14 07:49:27 -05:00
|
|
|
if (typeof value === 'string') {
|
2023-12-16 20:45:27 -05:00
|
|
|
return value.length > length ? value.substring(0, length) + '... [truncated]' : value;
|
2023-12-14 07:49:27 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
return value;
|
|
|
|
};
|
|
|
|
|
2023-12-16 20:45:27 -05:00
|
|
|
/**
|
|
|
|
* An array mapping function that truncates long strings (objects converted to JSON strings).
|
|
|
|
* @param {any} item - The item to be condensed.
|
|
|
|
* @returns {any} - The condensed item.
|
|
|
|
*/
|
|
|
|
const condenseArray = (item) => {
|
|
|
|
if (typeof item === 'string') {
|
|
|
|
return truncateLongStrings(JSON.stringify(item));
|
|
|
|
} else if (typeof item === 'object') {
|
|
|
|
return truncateLongStrings(JSON.stringify(item));
|
|
|
|
}
|
|
|
|
return item;
|
|
|
|
};
|
2023-12-14 07:49:27 -05:00
|
|
|
|
|
|
|
/**
|
2023-12-16 20:45:27 -05:00
|
|
|
* Formats log messages for debugging purposes.
|
|
|
|
* - Truncates long strings within log messages.
|
|
|
|
* - Condenses arrays by truncating long strings and objects as strings within array items.
|
|
|
|
* - Redacts sensitive information from log messages if the log level is 'error'.
|
|
|
|
* - Converts log information object to a formatted string.
|
2023-12-14 07:49:27 -05:00
|
|
|
*
|
2023-12-16 20:45:27 -05:00
|
|
|
* @param {Object} options - The options for formatting log messages.
|
|
|
|
* @param {string} options.level - The log level.
|
|
|
|
* @param {string} options.message - The log message.
|
|
|
|
* @param {string} options.timestamp - The timestamp of the log message.
|
|
|
|
* @param {Object} options.metadata - Additional metadata associated with the log message.
|
2023-12-14 07:49:27 -05:00
|
|
|
* @returns {string} - The formatted log message.
|
|
|
|
*/
|
2023-12-16 20:45:27 -05:00
|
|
|
const debugTraverse = winston.format.printf(({ level, message, timestamp, ...metadata }) => {
|
|
|
|
let msg = `${timestamp} ${level}: ${truncateLongStrings(message?.trim(), 150)}`;
|
|
|
|
|
|
|
|
if (level !== 'debug') {
|
|
|
|
return msg;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!metadata) {
|
|
|
|
return msg;
|
2023-12-14 07:49:27 -05:00
|
|
|
}
|
|
|
|
|
2023-12-16 20:45:27 -05:00
|
|
|
const debugValue = metadata[SPLAT_SYMBOL]?.[0];
|
|
|
|
|
|
|
|
if (!debugValue) {
|
|
|
|
return msg;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (debugValue && Array.isArray(debugValue)) {
|
|
|
|
msg += `\n${JSON.stringify(debugValue.map(condenseArray))}`;
|
|
|
|
return msg;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (typeof debugValue !== 'object') {
|
|
|
|
return (msg += ` ${debugValue}`);
|
|
|
|
}
|
|
|
|
|
|
|
|
msg += '\n{';
|
|
|
|
|
|
|
|
const copy = klona(metadata);
|
|
|
|
traverse(copy).forEach(function (value) {
|
|
|
|
const parent = this.parent;
|
|
|
|
const parentKey = `${parent && parent.notRoot ? parent.key + '.' : ''}`;
|
|
|
|
const tabs = `${parent && parent.notRoot ? '\t\t' : '\t'}`;
|
|
|
|
if (this.isLeaf && typeof value === 'string') {
|
|
|
|
const truncatedText = truncateLongStrings(value);
|
|
|
|
msg += `\n${tabs}${parentKey}${this.key}: ${JSON.stringify(truncatedText)},`;
|
|
|
|
} else if (this.notLeaf && Array.isArray(value) && value.length > 0) {
|
|
|
|
const currentMessage = `\n${tabs}// ${value.length} ${this.key.replace(/s$/, '')}(s)`;
|
|
|
|
this.update(currentMessage, true);
|
|
|
|
msg += currentMessage;
|
|
|
|
const stringifiedArray = value.map(condenseArray);
|
|
|
|
msg += `\n${tabs}${parentKey}${this.key}: [${stringifiedArray}],`;
|
|
|
|
} else if (this.isLeaf && typeof value === 'function') {
|
|
|
|
msg += `\n${tabs}${parentKey}${this.key}: function,`;
|
|
|
|
} else if (this.isLeaf) {
|
|
|
|
msg += `\n${tabs}${parentKey}${this.key}: ${value},`;
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
msg += '\n}';
|
2023-12-14 07:49:27 -05:00
|
|
|
return msg;
|
|
|
|
});
|
|
|
|
|
|
|
|
module.exports = {
|
2023-12-16 20:45:27 -05:00
|
|
|
redactFormat,
|
|
|
|
redactMessage,
|
|
|
|
debugTraverse,
|
2023-12-14 07:49:27 -05:00
|
|
|
};
|