@flavoai/fastfold
Version:
Flavo frontend package
168 lines • 5.79 kB
JavaScript
import fs from 'fs';
import path from 'path';
/**
* Request Logger for Fastfold
* Logs all incoming/outgoing HTTP requests to a file
*/
export class RequestLogger {
logFilePath;
enabled;
logRequests;
logResponses;
logRequestBody;
logResponseBody;
excludePaths;
constructor(config) {
this.enabled = config.enabled;
this.logFilePath = config.logFilePath || path.resolve(process.cwd(), 'request-logs.txt');
this.logRequests = config.logRequests ?? true;
this.logResponses = config.logResponses ?? true;
this.logRequestBody = config.logRequestBody ?? true;
this.logResponseBody = config.logResponseBody ?? false;
this.excludePaths = config.excludePaths || ['/health', '/docs'];
// Ensure log directory exists
const logDir = path.dirname(this.logFilePath);
if (!fs.existsSync(logDir)) {
fs.mkdirSync(logDir, { recursive: true });
}
// Always start fresh on server boot so logs reflect the current session only
const header = `========================================\nFASTFOLD REQUEST LOGS\nStarted: ${new Date().toISOString()}\n========================================\n\n`;
fs.writeFileSync(this.logFilePath, header);
}
/**
* Check if a path should be excluded from logging
*/
shouldExclude(path) {
return this.excludePaths.some(excluded => path.startsWith(excluded));
}
/**
* Append log entry to file
*/
appendLog(entry) {
if (!this.enabled)
return;
try {
fs.appendFileSync(this.logFilePath, entry + '\n');
}
catch (error) {
console.error('Failed to write to log file:', error);
}
}
/**
* Format log entry
*/
formatLogEntry(data) {
const lines = [];
lines.push(`[${data.timestamp}] ${data.source.toUpperCase()} ${data.direction.toUpperCase()} ${data.method} ${data.url}`);
if (data.headers && Object.keys(data.headers).length > 0) {
lines.push(` Headers: ${JSON.stringify(data.headers, null, 2).replace(/\n/g, '\n ')}`);
}
if (data.body && this.logRequestBody) {
const bodyStr = typeof data.body === 'string' ? data.body : JSON.stringify(data.body, null, 2);
lines.push(` Body: ${bodyStr.replace(/\n/g, '\n ')}`);
}
if (data.status !== undefined) {
lines.push(` Status: ${data.status}`);
}
if (data.duration !== undefined) {
lines.push(` Duration: ${data.duration}ms`);
}
if (data.error) {
lines.push(` Error: ${data.error}`);
}
lines.push(''); // Empty line for separation
return lines.join('\n');
}
/**
* Log backend incoming request
*/
logBackendRequest(req, startTime) {
if (!this.enabled || !this.logRequests)
return;
if (this.shouldExclude(req.path))
return;
const entry = this.formatLogEntry({
timestamp: new Date().toISOString(),
source: 'backend',
direction: 'incoming',
method: req.method,
url: req.originalUrl || req.url,
headers: {
'content-type': req.headers['content-type'],
'authorization': req.headers.authorization ? 'Bearer ***' : undefined,
'user-agent': req.headers['user-agent']
},
body: req.body
});
this.appendLog(entry);
}
/**
* Log backend response
*/
logBackendResponse(req, res, startTime, responseBody) {
if (!this.enabled || !this.logResponses)
return;
if (this.shouldExclude(req.path))
return;
const duration = Date.now() - startTime;
const entry = this.formatLogEntry({
timestamp: new Date().toISOString(),
source: 'backend',
direction: 'incoming',
method: req.method,
url: req.originalUrl || req.url,
status: res.statusCode,
duration,
body: this.logResponseBody ? responseBody : undefined
});
this.appendLog(entry);
}
/**
* Log frontend outgoing request (from /internal-logs endpoint)
*/
logFrontendRequest(data) {
if (!this.enabled)
return;
const entry = this.formatLogEntry({
timestamp: data.timestamp || new Date().toISOString(),
source: 'frontend',
direction: 'outgoing',
method: data.method,
url: data.url,
headers: data.headers,
body: data.body
});
this.appendLog(entry);
}
/**
* Get Express middleware for logging
*/
getMiddleware() {
return (req, res, next) => {
if (!this.enabled) {
return next();
}
const startTime = Date.now();
// Log request
this.logBackendRequest(req, startTime);
// Capture response
const originalSend = res.send;
const originalJson = res.json;
res.send = function (body) {
// Don't log response body by default (can be large)
res.send = originalSend;
return res.send(body);
};
res.json = function (body) {
res.json = originalJson;
return res.json(body);
};
// Log response when finished
res.on('finish', () => {
this.logBackendResponse(req, res, startTime);
});
next();
};
}
}
//# sourceMappingURL=logger.js.map