- Add dedicated API logger (api-logger.ts) with winston daily rotation - Create Fastify plugin (api-logging.plugin.ts) to intercept all API requests/responses - Log request start, body, response, errors, and summary with precise timestamps - Auto-mask sensitive data using existing sanitizeObject utility - Register API logging plugin in NestJS bootstrap (server/main.ts) - Add npm run dev script with cross-env for automatic DEBUG on Windows - Install cross-env as devDependency for cross-platform env vars
95 lines
2.5 KiB
TypeScript
95 lines
2.5 KiB
TypeScript
import fs from 'fs';
|
|
import path from 'path';
|
|
import winston from 'winston';
|
|
import DailyRotateFile from 'winston-daily-rotate-file';
|
|
import { getAgentDir } from '../../utils/paths';
|
|
import { sanitizeObject } from '../../utils/sensitiveDataMasking';
|
|
|
|
const LOG_RETENTION = '30d';
|
|
const LOG_MAX_SIZE = '50m';
|
|
|
|
const agentDir = getAgentDir();
|
|
|
|
if (!fs.existsSync(agentDir)) {
|
|
try {
|
|
fs.mkdirSync(agentDir, { recursive: true });
|
|
} catch (e) {
|
|
console.error('Failed to create agent directory for API logs', e);
|
|
}
|
|
}
|
|
|
|
const fileFormat = winston.format.combine(
|
|
winston.format.timestamp(),
|
|
winston.format.printf(({ timestamp, level, message, ...meta }) => {
|
|
const logEntry = {
|
|
timestamp,
|
|
level,
|
|
message,
|
|
...meta,
|
|
};
|
|
return JSON.stringify(logEntry);
|
|
}),
|
|
);
|
|
|
|
const consoleFormat = winston.format.combine(
|
|
winston.format.colorize({ all: true }),
|
|
winston.format.printf(({ timestamp, level, message, ...meta }) => {
|
|
const logEntry = {
|
|
timestamp,
|
|
level,
|
|
message,
|
|
...meta,
|
|
};
|
|
return JSON.stringify(logEntry);
|
|
}),
|
|
);
|
|
|
|
const rotateTransport = new DailyRotateFile({
|
|
filename: path.join(agentDir, 'api-%DATE%.log'),
|
|
datePattern: 'YYYY-MM-DD-HH',
|
|
maxSize: LOG_MAX_SIZE,
|
|
maxFiles: LOG_RETENTION,
|
|
zippedArchive: false,
|
|
auditFile: path.join(agentDir, '.api-log-audit.json'),
|
|
level: 'debug',
|
|
format: fileFormat,
|
|
});
|
|
|
|
rotateTransport.on('error', (error) => {
|
|
console.error('DailyRotateFile API transport error', error);
|
|
});
|
|
|
|
const consoleTransport = new winston.transports.Console({
|
|
level: 'debug',
|
|
format: consoleFormat,
|
|
});
|
|
|
|
consoleTransport.on('error', (error) => {
|
|
console.error('Console API transport error', error);
|
|
});
|
|
|
|
export const apiLogger = winston.createLogger({
|
|
level: 'debug',
|
|
transports: [consoleTransport, rotateTransport],
|
|
exitOnError: false,
|
|
});
|
|
|
|
/**
|
|
* Safely serialize a value for API logging.
|
|
* Truncates very large bodies to avoid excessive log sizes.
|
|
*/
|
|
export function safeApiLogValue(value: unknown, maxLength = 50_000): unknown {
|
|
const sanitized = sanitizeObject(value);
|
|
|
|
if (typeof sanitized === 'string' && sanitized.length > maxLength) {
|
|
return sanitized.substring(0, maxLength) + `... [truncated ${sanitized.length - maxLength} chars]`;
|
|
}
|
|
|
|
const stringified = JSON.stringify(sanitized);
|
|
if (stringified.length > maxLength) {
|
|
return stringified.substring(0, maxLength) + `... [truncated ${stringified.length - maxLength} chars]`;
|
|
}
|
|
|
|
return sanitized;
|
|
}
|