/**
* Logger utility for PartnerCore Proxy
*
* Security: This logger automatically masks sensitive data like API keys,
* passwords, and tokens to prevent accidental exposure in logs.
*/
import winston from 'winston';
let loggerInstance: winston.Logger | null = null;
/**
* Patterns that indicate sensitive data in log messages
*/
const SENSITIVE_PATTERNS = [
// API keys and tokens
/api[_-]?key[=:]\s*['"]?([a-zA-Z0-9_-]{20,})['"]?/gi,
/token[=:]\s*['"]?([a-zA-Z0-9_.-]{20,})['"]?/gi,
/bearer\s+([a-zA-Z0-9_.-]{20,})/gi,
/authorization[=:]\s*['"]?([a-zA-Z0-9_.-]{20,})['"]?/gi,
// Passwords
/password[=:]\s*['"]?([^\s'"]+)['"]?/gi,
/secret[=:]\s*['"]?([^\s'"]+)['"]?/gi,
];
/**
* Mask sensitive data in a string
*/
function maskSensitiveString(str: string): string {
let masked = str;
for (const pattern of SENSITIVE_PATTERNS) {
masked = masked.replace(pattern, (match: string, group: string | undefined) => {
const capturedGroup = group ?? '';
if (capturedGroup.length > 8) {
return match.replace(capturedGroup, `${capturedGroup.slice(0, 4)}****${capturedGroup.slice(-4)}`);
}
return match.replace(capturedGroup || match, '****');
});
}
return masked;
}
/**
* Format for masking sensitive data
*/
const maskSensitiveFormat = winston.format((info) => {
if (typeof info.message === 'string') {
info.message = maskSensitiveString(info.message);
}
return info;
});
/**
* Create and configure the logger
*/
export function createLogger(level: string = 'info'): winston.Logger {
if (loggerInstance) {
return loggerInstance;
}
loggerInstance = winston.createLogger({
level,
format: winston.format.combine(
maskSensitiveFormat(),
winston.format.timestamp({ format: 'YYYY-MM-DD HH:mm:ss' }),
winston.format.errors({ stack: true }),
winston.format.printf(({ level, message, timestamp, ...meta }) => {
// Mask sensitive data in metadata too
let metaStr = '';
if (Object.keys(meta).length) {
const maskedMeta = maskSensitiveString(JSON.stringify(meta));
metaStr = ` ${maskedMeta}`;
}
const ts = String(timestamp ?? '');
const msg = String(message ?? '');
return `[${ts}] ${level.toUpperCase()}: ${msg}${metaStr}`;
})
),
transports: [
new winston.transports.Console({
stderrLevels: ['error', 'warn'],
}),
],
});
return loggerInstance;
}
/**
* Get the logger instance (creates one if not exists)
*/
export function getLogger(): winston.Logger {
if (!loggerInstance) {
return createLogger();
}
return loggerInstance;
}
/**
* Set log level dynamically
*/
export function setLogLevel(level: string): void {
const logger = getLogger();
logger.level = level;
}