feat: новая функция
This commit is contained in:
@@ -1,21 +1,189 @@
|
||||
const winston = require('winston');
|
||||
const path = require('path');
|
||||
|
||||
const SENSITIVE_KEY_REGEX = /(address|wallet|signature|provider_id|private|secret|rpc|api(?:key)?|auth|token_address|contract)/i;
|
||||
const IPV4_REGEX = /\b(\d{1,3}\.){3}\d{1,3}\b/g;
|
||||
const IPV6_REGEX = /([a-f0-9]{1,4}:){1,7}[a-f0-9]{1,4}/gi;
|
||||
const ETH_ADDRESS_REGEX = /0x[a-fA-F0-9]{40}/g;
|
||||
const ETH_TX_REGEX = /0x[a-fA-F0-9]{64}/g;
|
||||
const EMAIL_REGEX = /[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}/gi;
|
||||
|
||||
const maskEthereumAddress = (value) => {
|
||||
return value.replace(ETH_ADDRESS_REGEX, (match) => `${match.slice(0, 6)}...${match.slice(-4)}`);
|
||||
};
|
||||
|
||||
const maskEthereumHash = (value) => {
|
||||
return value.replace(ETH_TX_REGEX, (match) => `${match.slice(0, 10)}...${match.slice(-6)}`);
|
||||
};
|
||||
|
||||
const maskIpAddresses = (value) => {
|
||||
return value
|
||||
.replace(IPV4_REGEX, '***.***.***.***')
|
||||
.replace(IPV6_REGEX, '[REDACTED_IP]');
|
||||
};
|
||||
|
||||
const maskEmails = (value) => {
|
||||
return value.replace(EMAIL_REGEX, (match) => {
|
||||
const [local, domain] = match.split('@');
|
||||
if (!domain) {
|
||||
return '[REDACTED_EMAIL]';
|
||||
}
|
||||
const hiddenLocal = local.length <= 2 ? '**' : `${local.slice(0, 2)}***`;
|
||||
return `${hiddenLocal}@${domain}`;
|
||||
});
|
||||
};
|
||||
|
||||
const redactString = (value) => {
|
||||
if (!value) {
|
||||
return value;
|
||||
}
|
||||
let sanitized = value;
|
||||
sanitized = maskEthereumAddress(sanitized);
|
||||
sanitized = maskEthereumHash(sanitized);
|
||||
sanitized = maskIpAddresses(sanitized);
|
||||
sanitized = maskEmails(sanitized);
|
||||
return sanitized;
|
||||
};
|
||||
|
||||
const sanitizeValue = (value, keyPath = '', seen = new WeakSet()) => {
|
||||
if (typeof value === 'string') {
|
||||
if (SENSITIVE_KEY_REGEX.test(keyPath)) {
|
||||
return '[REDACTED]';
|
||||
}
|
||||
return redactString(value);
|
||||
}
|
||||
|
||||
if (typeof value === 'number') {
|
||||
if (SENSITIVE_KEY_REGEX.test(keyPath)) {
|
||||
return '[REDACTED]';
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
if (!value || typeof value !== 'object') {
|
||||
return value;
|
||||
}
|
||||
|
||||
if (seen.has(value)) {
|
||||
return '[REDACTED]';
|
||||
}
|
||||
seen.add(value);
|
||||
|
||||
if (value instanceof Error) {
|
||||
const sanitizedError = {};
|
||||
Object.getOwnPropertyNames(value).forEach((prop) => {
|
||||
sanitizedError[prop] = sanitizeValue(value[prop], `${keyPath}.${prop}`, seen);
|
||||
});
|
||||
return sanitizedError;
|
||||
}
|
||||
|
||||
if (Array.isArray(value)) {
|
||||
return value.map((item, index) => sanitizeValue(item, `${keyPath}[${index}]`, seen));
|
||||
}
|
||||
|
||||
const sanitizedObject = {};
|
||||
Object.keys(value).forEach((key) => {
|
||||
const nextPath = keyPath ? `${keyPath}.${key}` : key;
|
||||
if (SENSITIVE_KEY_REGEX.test(key)) {
|
||||
sanitizedObject[key] = '[REDACTED]';
|
||||
} else {
|
||||
sanitizedObject[key] = sanitizeValue(value[key], nextPath, seen);
|
||||
}
|
||||
});
|
||||
return sanitizedObject;
|
||||
};
|
||||
|
||||
const sanitizeInfo = (info) => {
|
||||
const sanitizedInfo = { ...info };
|
||||
sanitizedInfo.message = sanitizeValue(info.message, 'message');
|
||||
const splat = Symbol.for('splat');
|
||||
if (info[splat]) {
|
||||
sanitizedInfo[splat] = info[splat].map((entry, index) => sanitizeValue(entry, `splat[${index}]`));
|
||||
}
|
||||
|
||||
Object.keys(info).forEach((key) => {
|
||||
if (['level', 'message', 'timestamp'].includes(key)) {
|
||||
return;
|
||||
}
|
||||
sanitizedInfo[key] = sanitizeValue(info[key], key);
|
||||
});
|
||||
|
||||
return sanitizedInfo;
|
||||
};
|
||||
|
||||
const sanitizeFormat = winston.format((info) => sanitizeInfo(info));
|
||||
|
||||
const jsonFormat = winston.format.combine(
|
||||
sanitizeFormat(),
|
||||
winston.format.timestamp(),
|
||||
winston.format.json()
|
||||
);
|
||||
|
||||
const consoleFormat = winston.format.combine(
|
||||
sanitizeFormat(),
|
||||
winston.format.timestamp(),
|
||||
winston.format.colorize({ all: true }),
|
||||
winston.format.printf((info) => {
|
||||
const { timestamp, level, message, ...rest } = info;
|
||||
const metaParts = [];
|
||||
const splat = info[Symbol.for('splat')];
|
||||
if (splat && Array.isArray(splat)) {
|
||||
splat.forEach((entry) => {
|
||||
if (entry === undefined) {
|
||||
return;
|
||||
}
|
||||
if (typeof entry === 'string') {
|
||||
metaParts.push(entry);
|
||||
} else {
|
||||
metaParts.push(JSON.stringify(entry));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
Object.keys(rest)
|
||||
.filter((key) => !['level', 'message', 'timestamp'].includes(key))
|
||||
.forEach((key) => {
|
||||
if (key === Symbol.for('splat')) {
|
||||
return;
|
||||
}
|
||||
const value = rest[key];
|
||||
if (value !== undefined) {
|
||||
metaParts.push(`${key}=${JSON.stringify(value)}`);
|
||||
}
|
||||
});
|
||||
|
||||
const metaString = metaParts.length ? ` ${metaParts.join(' ')}` : '';
|
||||
return `${timestamp} ${level}: ${message}${metaString}`;
|
||||
})
|
||||
);
|
||||
|
||||
const logger = winston.createLogger({
|
||||
level: process.env.LOG_LEVEL || 'info', // Уровень по умолчанию 'info' для показа логов ботов
|
||||
format: winston.format.combine(winston.format.timestamp(), winston.format.json()),
|
||||
level: process.env.LOG_LEVEL || 'info',
|
||||
format: jsonFormat,
|
||||
transports: [
|
||||
new winston.transports.Console({
|
||||
format: winston.format.combine(winston.format.colorize(), winston.format.simple()),
|
||||
format: consoleFormat,
|
||||
}),
|
||||
new winston.transports.File({
|
||||
filename: path.join(__dirname, '../logs/error.log'),
|
||||
level: 'error',
|
||||
format: jsonFormat,
|
||||
}),
|
||||
new winston.transports.File({
|
||||
filename: path.join(__dirname, '../logs/combined.log'),
|
||||
format: jsonFormat,
|
||||
}),
|
||||
],
|
||||
});
|
||||
|
||||
const wrapConsoleMethod = (methodName) => {
|
||||
const original = console[methodName].bind(console);
|
||||
console[methodName] = (...args) => {
|
||||
const sanitizedArgs = args.map((arg, index) => sanitizeValue(arg, `${methodName}[${index}]`));
|
||||
original(...sanitizedArgs);
|
||||
};
|
||||
};
|
||||
|
||||
['log', 'info', 'warn', 'error', 'debug'].forEach(wrapConsoleMethod);
|
||||
|
||||
module.exports = logger;
|
||||
|
||||
Reference in New Issue
Block a user