Files
SerpentRace/SerpentRace_Backend/dist/Application/Services/LoggingService.js
T

363 lines
14 KiB
JavaScript

"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
var ownKeys = function(o) {
ownKeys = Object.getOwnPropertyNames || function (o) {
var ar = [];
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
return ar;
};
return ownKeys(o);
};
return function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
__setModuleDefault(result, mod);
return result;
};
})();
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.LoggingService = exports.LogLevel = void 0;
const fs_1 = __importDefault(require("fs"));
const path_1 = __importDefault(require("path"));
const Minio = __importStar(require("minio"));
var LogLevel;
(function (LogLevel) {
LogLevel["REQUEST"] = "REQUEST";
LogLevel["ERROR"] = "ERROR";
LogLevel["WARNING"] = "WARNING";
LogLevel["AUTH"] = "AUTH";
LogLevel["DATABASE"] = "DATABASE";
LogLevel["STARTUP"] = "STARTUP";
LogLevel["CONNECTION"] = "CONNECTION";
LogLevel["OTHER"] = "OTHER";
})(LogLevel || (exports.LogLevel = LogLevel = {}));
class LoggingService {
constructor() {
this.minioClient = null;
this.logBuffer = [];
this.currentLogFile = null;
this.logCount = 0;
this.maxLogsPerFile = parseInt(process.env.MAX_LOGS_PER_FILE || '10000');
this.logsDir = path_1.default.join(process.cwd(), 'logs');
this.bucketName = process.env.MINIO_BUCKET_NAME || 'serpentrace-logs';
this.uploadInterval = null;
this.initializeLogsDirectory();
this.initializeMinioClient();
this.createNewLogFile();
if (process.env.NODE_ENV !== 'test') {
this.startPeriodicUpload();
}
process.on('SIGTERM', () => this.shutdown());
process.on('SIGINT', () => this.shutdown());
process.on('beforeExit', () => this.shutdown());
}
static getInstance() {
if (!LoggingService.instance) {
LoggingService.instance = new LoggingService();
}
return LoggingService.instance;
}
initializeLogsDirectory() {
try {
if (!fs_1.default.existsSync(this.logsDir)) {
fs_1.default.mkdirSync(this.logsDir, { recursive: true });
}
// Create monthly subdirectory
const monthlyDir = this.getMonthlyDirectory();
if (!fs_1.default.existsSync(monthlyDir)) {
fs_1.default.mkdirSync(monthlyDir, { recursive: true });
}
}
catch (error) {
console.error('Failed to initialize logs directory:', error);
}
}
initializeMinioClient() {
try {
// Check if in production or development
if (process.env.NODE_ENV === 'production') {
if (process.env.MINIO_ENDPOINT && process.env.MINIO_ACCESS_KEY && process.env.MINIO_SECRET_KEY) {
this.minioClient = new Minio.Client({
endPoint: process.env.MINIO_ENDPOINT,
port: parseInt(process.env.MINIO_PORT || '9000'),
useSSL: process.env.MINIO_USE_SSL === 'true',
accessKey: process.env.MINIO_ACCESS_KEY,
secretKey: process.env.MINIO_SECRET_KEY
});
this.ensureBucketExists();
}
else {
console.warn('Minio configuration not found. Logs will only be stored locally and in console.');
}
}
else {
// Development-specific Minio configuration
this.minioClient = new Minio.Client({
endPoint: 'localhost',
port: 9000,
useSSL: false,
accessKey: 'serpentrace',
secretKey: 'serpentrace123!'
});
this.ensureBucketExists();
}
}
catch (error) {
console.error('Failed to initialize Minio client:', error);
this.minioClient = null;
}
}
async ensureBucketExists() {
if (!this.minioClient)
return;
try {
const exists = await this.minioClient.bucketExists(this.bucketName);
if (!exists) {
await this.minioClient.makeBucket(this.bucketName);
this.log(LogLevel.STARTUP, `Created Minio bucket: ${this.bucketName}`);
}
}
catch (error) {
console.error('Failed to ensure bucket exists:', error);
}
}
startPeriodicUpload() {
// Upload current log file to Minio every 2 minutes
this.uploadInterval = setInterval(async () => {
if (this.currentLogFile && this.minioClient) {
await this.uploadToMinio(this.currentLogFile);
}
}, 2 * 60 * 1000); // 2 minutes
}
getMonthlyDirectory() {
const now = new Date();
const year = now.getFullYear();
const month = String(now.getMonth() + 1).padStart(2, '0');
return path_1.default.join(this.logsDir, `${year}-${month}`);
}
getMonthlyMinioPrefix() {
const now = new Date();
const year = now.getFullYear();
const month = String(now.getMonth() + 1).padStart(2, '0');
return `${year}-${month}/`;
}
createNewLogFile() {
const now = new Date();
const timestamp = now.toISOString().replace(/[:.]/g, '-');
const fileName = `serpentrace-${timestamp}.log`;
this.currentLogFile = path_1.default.join(this.getMonthlyDirectory(), fileName);
this.logCount = 0;
// Write log file header
const header = `# SerpentRace Backend Logs\n# Started: ${now.toISOString()}\n# Max entries per file: ${this.maxLogsPerFile}\n\n`;
try {
fs_1.default.writeFileSync(this.currentLogFile, header);
}
catch (error) {
console.error('Failed to create log file:', error);
}
}
formatLogEntry(entry) {
const parts = [
entry.timestamp,
`[${entry.level}]`,
entry.message
];
if (entry.requestId)
parts.push(`ReqId:${entry.requestId}`);
if (entry.userId)
parts.push(`UserId:${entry.userId}`);
if (entry.ip)
parts.push(`IP:${entry.ip}`);
if (entry.method && entry.url)
parts.push(`${entry.method} ${entry.url}`);
if (entry.statusCode)
parts.push(`Status:${entry.statusCode}`);
if (entry.responseTime)
parts.push(`Time:${entry.responseTime}ms`);
if (entry.userAgent)
parts.push(`UA:${entry.userAgent.substring(0, 50)}`);
if (entry.metadata)
parts.push(`Meta:${JSON.stringify(entry.metadata)}`);
return parts.join(' | ');
}
async writeToLocalFile(entry) {
if (!this.currentLogFile)
return;
try {
const logLine = this.formatLogEntry(entry) + '\n';
fs_1.default.appendFileSync(this.currentLogFile, logLine);
this.logCount++;
// Check if we need to rotate the log file
if (this.logCount >= this.maxLogsPerFile) {
await this.rotateLogFile();
}
}
catch (error) {
console.error('Failed to write to log file:', error);
}
}
async rotateLogFile() {
if (!this.currentLogFile)
return;
try {
// Upload current file to Minio before rotating
await this.uploadToMinio(this.currentLogFile);
// Create new log file
this.createNewLogFile();
this.log(LogLevel.OTHER, 'Log file rotated due to size limit');
}
catch (error) {
console.error('Failed to rotate log file:', error);
}
}
async uploadToMinio(filePath) {
if (!this.minioClient) {
console.warn('Minio client not initialized, skipping upload');
return;
}
if (!fs_1.default.existsSync(filePath)) {
console.warn(`Log file does not exist: ${filePath}`);
return;
}
try {
const fileName = path_1.default.basename(filePath);
const objectName = this.getMonthlyMinioPrefix() + fileName;
console.log(`Attempting to upload log file to Minio: ${objectName}`);
await this.minioClient.fPutObject(this.bucketName, objectName, filePath);
console.log(`Successfully uploaded log file to Minio: ${objectName}`);
}
catch (error) {
console.error('Failed to upload to Minio:', error);
console.error('Minio config:', {
endpoint: this.minioClient ? 'configured' : 'not configured',
bucket: this.bucketName
});
}
}
logToConsole(entry) {
const formattedEntry = this.formatLogEntry(entry);
switch (entry.level) {
case LogLevel.ERROR:
console.error(formattedEntry);
break;
case LogLevel.WARNING:
console.warn(formattedEntry);
break;
case LogLevel.REQUEST:
case LogLevel.AUTH:
case LogLevel.DATABASE:
case LogLevel.CONNECTION:
console.info(formattedEntry);
break;
case LogLevel.STARTUP:
console.log(formattedEntry);
break;
default:
console.log(formattedEntry);
}
}
log(level, message, metadata, req, res, responseTime) {
const entry = {
timestamp: new Date().toISOString(),
level,
message,
metadata
};
// Add request context if available
if (req) {
entry.requestId = req.requestId || this.generateRequestId();
entry.userId = req.user?.userId;
entry.ip = req.ip || req.socket?.remoteAddress || 'unknown';
entry.userAgent = req.get ? req.get('User-Agent') : 'unknown';
entry.method = req.method;
entry.url = req.originalUrl || req.url;
}
if (res) {
entry.statusCode = res.statusCode;
}
if (responseTime !== undefined) {
entry.responseTime = responseTime;
}
// Log to all three destinations
this.logToConsole(entry);
this.writeToLocalFile(entry);
// Add to buffer for potential batch processing
this.logBuffer.push(entry);
// Limit buffer size
if (this.logBuffer.length > 1000) {
this.logBuffer = this.logBuffer.slice(-500);
}
}
generateRequestId() {
return Math.random().toString(36).substr(2, 9);
}
async shutdown() {
try {
// Clear the upload interval
if (this.uploadInterval) {
clearInterval(this.uploadInterval);
this.uploadInterval = null;
}
// Upload current log file to Minio
if (this.currentLogFile) {
await this.uploadToMinio(this.currentLogFile);
}
this.log(LogLevel.STARTUP, 'Logging service shutting down gracefully');
// Give time for final logs to be written
await new Promise(resolve => setTimeout(resolve, 1000));
}
catch (error) {
console.error('Error during logging service shutdown:', error);
}
}
// Middleware factory methods
requestLoggingMiddleware() {
return (req, res, next) => {
const startTime = Date.now();
// Generate request ID
req.requestId = this.generateRequestId();
// Log request start
this.log(LogLevel.REQUEST, `Incoming request`, undefined, req);
// Override res.end to log response
const originalEnd = res.end.bind(res);
res.end = (...args) => {
const responseTime = Date.now() - startTime;
LoggingService.getInstance().log(LogLevel.REQUEST, `Request completed`, undefined, req, res, responseTime);
return originalEnd(...args);
};
next();
};
}
errorLoggingMiddleware() {
return (error, req, res, next) => {
this.log(LogLevel.ERROR, `Unhandled error: ${error.message}`, {
stack: error.stack,
name: error.name
}, req, res);
next(error);
};
}
}
exports.LoggingService = LoggingService;
exports.default = LoggingService;
//# sourceMappingURL=LoggingService.js.map