|
@@ -2,6 +2,9 @@
|
|
|
* Logger module for webhook events
|
|
* Logger module for webhook events
|
|
|
*/
|
|
*/
|
|
|
|
|
|
|
|
|
|
+import fs from 'fs';
|
|
|
|
|
+import path from 'path';
|
|
|
|
|
+
|
|
|
// Log levels
|
|
// Log levels
|
|
|
const LOG_LEVELS = {
|
|
const LOG_LEVELS = {
|
|
|
DEBUG: 0,
|
|
DEBUG: 0,
|
|
@@ -25,6 +28,25 @@ export class Logger {
|
|
|
const levelStr = (options.logLevel || process.env.LOG_LEVEL || 'INFO').toUpperCase();
|
|
const levelStr = (options.logLevel || process.env.LOG_LEVEL || 'INFO').toUpperCase();
|
|
|
this.logLevel = LOG_LEVELS[levelStr] ?? LOG_LEVELS.INFO;
|
|
this.logLevel = LOG_LEVELS[levelStr] ?? LOG_LEVELS.INFO;
|
|
|
|
|
|
|
|
|
|
+ // File logging configuration
|
|
|
|
|
+ this.fileLoggingEnabled = options.fileLoggingEnabled !== undefined
|
|
|
|
|
+ ? options.fileLoggingEnabled
|
|
|
|
|
+ : (process.env.FILE_LOGGING_ENABLED === 'true' || isSystemd);
|
|
|
|
|
+ this.logFilePath = options.logFilePath ||
|
|
|
|
|
+ process.env.LOG_FILE_PATH ||
|
|
|
|
|
+ '/var/log/agent-manager.log';
|
|
|
|
|
+ this.maxLogSize = options.maxLogSize ||
|
|
|
|
|
+ parseInt(process.env.MAX_LOG_SIZE || '10485760', 10); // 10MB default
|
|
|
|
|
+ this.maxLogFiles = options.maxLogFiles ||
|
|
|
|
|
+ parseInt(process.env.MAX_LOG_FILES || '5', 10); // Keep 5 rotated files
|
|
|
|
|
+
|
|
|
|
|
+ // Initialize file logging
|
|
|
|
|
+ this.fileStream = null;
|
|
|
|
|
+ this.currentLogSize = 0;
|
|
|
|
|
+ if (this.fileLoggingEnabled) {
|
|
|
|
|
+ this._initFileLogging();
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
// Track metrics for queue health reporting
|
|
// Track metrics for queue health reporting
|
|
|
this.metrics = {
|
|
this.metrics = {
|
|
|
jobsProcessed24h: 0,
|
|
jobsProcessed24h: 0,
|
|
@@ -38,6 +60,113 @@ export class Logger {
|
|
|
this._startMetricsReset();
|
|
this._startMetricsReset();
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+ _initFileLogging() {
|
|
|
|
|
+ try {
|
|
|
|
|
+ // Ensure log directory exists
|
|
|
|
|
+ const logDir = path.dirname(this.logFilePath);
|
|
|
|
|
+ if (!fs.existsSync(logDir)) {
|
|
|
|
|
+ fs.mkdirSync(logDir, { recursive: true, mode: 0o755 });
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // Check if log file exists and get its size
|
|
|
|
|
+ if (fs.existsSync(this.logFilePath)) {
|
|
|
|
|
+ const stats = fs.statSync(this.logFilePath);
|
|
|
|
|
+ this.currentLogSize = stats.size;
|
|
|
|
|
+
|
|
|
|
|
+ // Check if rotation is needed immediately
|
|
|
|
|
+ if (this.currentLogSize >= this.maxLogSize) {
|
|
|
|
|
+ this._rotateLogFile();
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // Open file stream in append mode
|
|
|
|
|
+ this.fileStream = fs.createWriteStream(this.logFilePath, {
|
|
|
|
|
+ flags: 'a',
|
|
|
|
|
+ encoding: 'utf8'
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ this.fileStream.on('error', (err) => {
|
|
|
|
|
+ console.error('File logging error:', err.message);
|
|
|
|
|
+ this.fileLoggingEnabled = false;
|
|
|
|
|
+ this.fileStream = null;
|
|
|
|
|
+ });
|
|
|
|
|
+ } catch (err) {
|
|
|
|
|
+ console.error('Failed to initialize file logging:', err.message);
|
|
|
|
|
+ this.fileLoggingEnabled = false;
|
|
|
|
|
+ this.fileStream = null;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ _rotateLogFile() {
|
|
|
|
|
+ try {
|
|
|
|
|
+ // Close current stream
|
|
|
|
|
+ if (this.fileStream) {
|
|
|
|
|
+ this.fileStream.end();
|
|
|
|
|
+ this.fileStream = null;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // Rotate existing log files
|
|
|
|
|
+ for (let i = this.maxLogFiles - 1; i >= 1; i--) {
|
|
|
|
|
+ const oldPath = `${this.logFilePath}.${i}`;
|
|
|
|
|
+ const newPath = `${this.logFilePath}.${i + 1}`;
|
|
|
|
|
+
|
|
|
|
|
+ if (fs.existsSync(oldPath)) {
|
|
|
|
|
+ if (i + 1 > this.maxLogFiles) {
|
|
|
|
|
+ // Delete oldest log file
|
|
|
|
|
+ fs.unlinkSync(oldPath);
|
|
|
|
|
+ } else {
|
|
|
|
|
+ fs.renameSync(oldPath, newPath);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // Rotate current log file
|
|
|
|
|
+ if (fs.existsSync(this.logFilePath)) {
|
|
|
|
|
+ fs.renameSync(this.logFilePath, `${this.logFilePath}.1`);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // Reset size counter and create new file
|
|
|
|
|
+ this.currentLogSize = 0;
|
|
|
|
|
+ this.fileStream = fs.createWriteStream(this.logFilePath, {
|
|
|
|
|
+ flags: 'a',
|
|
|
|
|
+ encoding: 'utf8'
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ this.fileStream.on('error', (err) => {
|
|
|
|
|
+ console.error('File logging error:', err.message);
|
|
|
|
|
+ this.fileLoggingEnabled = false;
|
|
|
|
|
+ this.fileStream = null;
|
|
|
|
|
+ });
|
|
|
|
|
+ } catch (err) {
|
|
|
|
|
+ console.error('Log rotation failed:', err.message);
|
|
|
|
|
+ this.fileLoggingEnabled = false;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ _writeToFile(message) {
|
|
|
|
|
+ if (!this.fileLoggingEnabled || !this.fileStream) {
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ try {
|
|
|
|
|
+ // Always include timestamp in file logs (even if disabled for console)
|
|
|
|
|
+ const timestamp = `[${new Date().toISOString()}]`;
|
|
|
|
|
+ // Strip ANSI color codes from message for file output
|
|
|
|
|
+ const cleanMessage = message.replace(/\x1b\[[0-9;]*m/g, '');
|
|
|
|
|
+ const logLine = `${timestamp} ${cleanMessage}\n`;
|
|
|
|
|
+
|
|
|
|
|
+ this.fileStream.write(logLine);
|
|
|
|
|
+ this.currentLogSize += Buffer.byteLength(logLine);
|
|
|
|
|
+
|
|
|
|
|
+ // Check if rotation is needed
|
|
|
|
|
+ if (this.currentLogSize >= this.maxLogSize) {
|
|
|
|
|
+ this._rotateLogFile();
|
|
|
|
|
+ }
|
|
|
|
|
+ } catch (err) {
|
|
|
|
|
+ console.error('Failed to write to log file:', err.message);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
_startMetricsReset() {
|
|
_startMetricsReset() {
|
|
|
// Reset metrics every 24 hours
|
|
// Reset metrics every 24 hours
|
|
|
setInterval(() => {
|
|
setInterval(() => {
|
|
@@ -73,36 +202,52 @@ export class Logger {
|
|
|
debug(message, data = null) {
|
|
debug(message, data = null) {
|
|
|
if (!this._shouldLog('DEBUG')) return;
|
|
if (!this._shouldLog('DEBUG')) return;
|
|
|
const timestamp = this._getTimestamp();
|
|
const timestamp = this._getTimestamp();
|
|
|
|
|
+ const logMessage = `${timestamp} DEBUG: ${message}`;
|
|
|
console.log(`${timestamp} ${this._colorize('DEBUG', '90')}:`, message);
|
|
console.log(`${timestamp} ${this._colorize('DEBUG', '90')}:`, message);
|
|
|
|
|
+ this._writeToFile(logMessage);
|
|
|
if (data) {
|
|
if (data) {
|
|
|
- console.log(JSON.stringify(data, null, 2));
|
|
|
|
|
|
|
+ const dataStr = JSON.stringify(data, null, 2);
|
|
|
|
|
+ console.log(dataStr);
|
|
|
|
|
+ this._writeToFile(dataStr);
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
info(message, data = null) {
|
|
info(message, data = null) {
|
|
|
if (!this._shouldLog('INFO')) return;
|
|
if (!this._shouldLog('INFO')) return;
|
|
|
const timestamp = this._getTimestamp();
|
|
const timestamp = this._getTimestamp();
|
|
|
|
|
+ const logMessage = `${timestamp} INFO: ${message}`;
|
|
|
console.log(`${timestamp} ${this._colorize('INFO', '36')}:`, message);
|
|
console.log(`${timestamp} ${this._colorize('INFO', '36')}:`, message);
|
|
|
|
|
+ this._writeToFile(logMessage);
|
|
|
if (data) {
|
|
if (data) {
|
|
|
- console.log(JSON.stringify(data, null, 2));
|
|
|
|
|
|
|
+ const dataStr = JSON.stringify(data, null, 2);
|
|
|
|
|
+ console.log(dataStr);
|
|
|
|
|
+ this._writeToFile(dataStr);
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
warn(message, data = null) {
|
|
warn(message, data = null) {
|
|
|
if (!this._shouldLog('WARN')) return;
|
|
if (!this._shouldLog('WARN')) return;
|
|
|
const timestamp = this._getTimestamp();
|
|
const timestamp = this._getTimestamp();
|
|
|
|
|
+ const logMessage = `${timestamp} WARN: ${message}`;
|
|
|
console.warn(`${timestamp} ${this._colorize('WARN', '33')}:`, message);
|
|
console.warn(`${timestamp} ${this._colorize('WARN', '33')}:`, message);
|
|
|
|
|
+ this._writeToFile(logMessage);
|
|
|
if (data) {
|
|
if (data) {
|
|
|
- console.warn(JSON.stringify(data, null, 2));
|
|
|
|
|
|
|
+ const dataStr = JSON.stringify(data, null, 2);
|
|
|
|
|
+ console.warn(dataStr);
|
|
|
|
|
+ this._writeToFile(dataStr);
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
error(message, error = null) {
|
|
error(message, error = null) {
|
|
|
if (!this._shouldLog('ERROR')) return;
|
|
if (!this._shouldLog('ERROR')) return;
|
|
|
const timestamp = this._getTimestamp();
|
|
const timestamp = this._getTimestamp();
|
|
|
|
|
+ const logMessage = `${timestamp} ERROR: ${message}`;
|
|
|
console.error(`${timestamp} ${this._colorize('ERROR', '31')}:`, message);
|
|
console.error(`${timestamp} ${this._colorize('ERROR', '31')}:`, message);
|
|
|
|
|
+ this._writeToFile(logMessage);
|
|
|
if (error) {
|
|
if (error) {
|
|
|
|
|
+ const errorStr = error.stack || error.toString();
|
|
|
console.error(error);
|
|
console.error(error);
|
|
|
|
|
+ this._writeToFile(errorStr);
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
@@ -114,6 +259,13 @@ export class Logger {
|
|
|
console.log(`${this._colorize('-'.repeat(80), '33')}`);
|
|
console.log(`${this._colorize('-'.repeat(80), '33')}`);
|
|
|
console.log(JSON.stringify(payload, null, 2));
|
|
console.log(JSON.stringify(payload, null, 2));
|
|
|
console.log(`${this._colorize('='.repeat(80), '33')}\n`);
|
|
console.log(`${this._colorize('='.repeat(80), '33')}\n`);
|
|
|
|
|
+
|
|
|
|
|
+ // Write to file
|
|
|
|
|
+ this._writeToFile(`${'='.repeat(80)}`);
|
|
|
|
|
+ this._writeToFile(`${timestamp} WEBHOOK - Event: ${event}`);
|
|
|
|
|
+ this._writeToFile(`${'-'.repeat(80)}`);
|
|
|
|
|
+ this._writeToFile(JSON.stringify(payload, null, 2));
|
|
|
|
|
+ this._writeToFile(`${'='.repeat(80)}`);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
// Job lifecycle logging methods
|
|
// Job lifecycle logging methods
|
|
@@ -121,21 +273,27 @@ export class Logger {
|
|
|
if (!this._shouldLog('INFO')) return;
|
|
if (!this._shouldLog('INFO')) return;
|
|
|
const timestamp = this._getTimestamp();
|
|
const timestamp = this._getTimestamp();
|
|
|
const issueInfo = issueNumber ? ` - Issue #${issueNumber}` : '';
|
|
const issueInfo = issueNumber ? ` - Issue #${issueNumber}` : '';
|
|
|
|
|
+ const logMessage = `${timestamp} JOB QUEUED: Job #${jobId} (${eventType}${issueInfo}) - ${commandName}`;
|
|
|
console.log(`${timestamp} ${this._colorize('JOB QUEUED', '36')}: Job #${jobId} (${eventType}${issueInfo}) - ${commandName}`);
|
|
console.log(`${timestamp} ${this._colorize('JOB QUEUED', '36')}: Job #${jobId} (${eventType}${issueInfo}) - ${commandName}`);
|
|
|
|
|
+ this._writeToFile(logMessage);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
jobSkipped(jobId, eventType, issueNumber, reason) {
|
|
jobSkipped(jobId, eventType, issueNumber, reason) {
|
|
|
if (!this._shouldLog('INFO')) return;
|
|
if (!this._shouldLog('INFO')) return;
|
|
|
const timestamp = this._getTimestamp();
|
|
const timestamp = this._getTimestamp();
|
|
|
const issueInfo = issueNumber ? ` - Issue #${issueNumber}` : '';
|
|
const issueInfo = issueNumber ? ` - Issue #${issueNumber}` : '';
|
|
|
|
|
+ const logMessage = `${timestamp} JOB SKIPPED: Job #${jobId} (${eventType}${issueInfo}) - Reason: ${reason}`;
|
|
|
console.log(`${timestamp} ${this._colorize('JOB SKIPPED', '33')}: Job #${jobId} (${eventType}${issueInfo}) - Reason: ${reason}`);
|
|
console.log(`${timestamp} ${this._colorize('JOB SKIPPED', '33')}: Job #${jobId} (${eventType}${issueInfo}) - Reason: ${reason}`);
|
|
|
|
|
+ this._writeToFile(logMessage);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
jobStarted(jobId, eventType, issueNumber, commandName) {
|
|
jobStarted(jobId, eventType, issueNumber, commandName) {
|
|
|
if (!this._shouldLog('INFO')) return;
|
|
if (!this._shouldLog('INFO')) return;
|
|
|
const timestamp = this._getTimestamp();
|
|
const timestamp = this._getTimestamp();
|
|
|
const issueInfo = issueNumber ? ` - Issue #${issueNumber}` : '';
|
|
const issueInfo = issueNumber ? ` - Issue #${issueNumber}` : '';
|
|
|
|
|
+ const logMessage = `${timestamp} JOB STARTED: Job #${jobId} (${eventType}${issueInfo}) - ${commandName}`;
|
|
|
console.log(`${timestamp} ${this._colorize('JOB STARTED', '32')}: Job #${jobId} (${eventType}${issueInfo}) - ${commandName}`);
|
|
console.log(`${timestamp} ${this._colorize('JOB STARTED', '32')}: Job #${jobId} (${eventType}${issueInfo}) - ${commandName}`);
|
|
|
|
|
+ this._writeToFile(logMessage);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
jobCompleted(jobId, eventType, issueNumber, durationMs) {
|
|
jobCompleted(jobId, eventType, issueNumber, durationMs) {
|
|
@@ -143,7 +301,9 @@ export class Logger {
|
|
|
const timestamp = this._getTimestamp();
|
|
const timestamp = this._getTimestamp();
|
|
|
const issueInfo = issueNumber ? ` - Issue #${issueNumber}` : '';
|
|
const issueInfo = issueNumber ? ` - Issue #${issueNumber}` : '';
|
|
|
const duration = this._formatDuration(durationMs);
|
|
const duration = this._formatDuration(durationMs);
|
|
|
|
|
+ const logMessage = `${timestamp} JOB COMPLETED: Job #${jobId} (${eventType}${issueInfo}) - Duration: ${duration}`;
|
|
|
console.log(`${timestamp} ${this._colorize('JOB COMPLETED', '32')}: Job #${jobId} (${eventType}${issueInfo}) - Duration: ${duration}`);
|
|
console.log(`${timestamp} ${this._colorize('JOB COMPLETED', '32')}: Job #${jobId} (${eventType}${issueInfo}) - Duration: ${duration}`);
|
|
|
|
|
+ this._writeToFile(logMessage);
|
|
|
|
|
|
|
|
// Track metrics
|
|
// Track metrics
|
|
|
this.metrics.jobsProcessed24h++;
|
|
this.metrics.jobsProcessed24h++;
|
|
@@ -158,7 +318,9 @@ export class Logger {
|
|
|
if (!this._shouldLog('ERROR')) return;
|
|
if (!this._shouldLog('ERROR')) return;
|
|
|
const timestamp = this._getTimestamp();
|
|
const timestamp = this._getTimestamp();
|
|
|
const issueInfo = issueNumber ? ` - Issue #${issueNumber}` : '';
|
|
const issueInfo = issueNumber ? ` - Issue #${issueNumber}` : '';
|
|
|
|
|
+ const logMessage = `${timestamp} JOB FAILED: Job #${jobId} (${eventType}${issueInfo}) - Error: ${error}`;
|
|
|
console.error(`${timestamp} ${this._colorize('JOB FAILED', '31')}: Job #${jobId} (${eventType}${issueInfo}) - Error: ${error}`);
|
|
console.error(`${timestamp} ${this._colorize('JOB FAILED', '31')}: Job #${jobId} (${eventType}${issueInfo}) - Error: ${error}`);
|
|
|
|
|
+ this._writeToFile(logMessage);
|
|
|
|
|
|
|
|
// Track metrics
|
|
// Track metrics
|
|
|
this.metrics.jobsFailed24h++;
|
|
this.metrics.jobsFailed24h++;
|
|
@@ -169,7 +331,9 @@ export class Logger {
|
|
|
if (!this._shouldLog('INFO')) return;
|
|
if (!this._shouldLog('INFO')) return;
|
|
|
const timestamp = this._getTimestamp();
|
|
const timestamp = this._getTimestamp();
|
|
|
const timeSince = this._formatDuration(timeSinceLastMs);
|
|
const timeSince = this._formatDuration(timeSinceLastMs);
|
|
|
|
|
+ const logMessage = `${timestamp} DUPLICATE: ${eventType} for Issue #${issueNumber} (last processed ${timeSince} ago)`;
|
|
|
console.log(`${timestamp} ${this._colorize('DUPLICATE', '33')}: ${eventType} for Issue #${issueNumber} (last processed ${timeSince} ago)`);
|
|
console.log(`${timestamp} ${this._colorize('DUPLICATE', '33')}: ${eventType} for Issue #${issueNumber} (last processed ${timeSince} ago)`);
|
|
|
|
|
+ this._writeToFile(logMessage);
|
|
|
|
|
|
|
|
// Track metrics
|
|
// Track metrics
|
|
|
this.metrics.jobsDuplicated24h++;
|
|
this.metrics.jobsDuplicated24h++;
|
|
@@ -178,22 +342,30 @@ export class Logger {
|
|
|
deduplicationCleanup(entriesRemoved) {
|
|
deduplicationCleanup(entriesRemoved) {
|
|
|
if (!this._shouldLog('DEBUG')) return;
|
|
if (!this._shouldLog('DEBUG')) return;
|
|
|
const timestamp = this._getTimestamp();
|
|
const timestamp = this._getTimestamp();
|
|
|
|
|
+ const logMessage = `${timestamp} CLEANUP: Removed ${entriesRemoved} expired deduplication entries`;
|
|
|
console.log(`${timestamp} ${this._colorize('CLEANUP', '90')}: Removed ${entriesRemoved} expired deduplication entries`);
|
|
console.log(`${timestamp} ${this._colorize('CLEANUP', '90')}: Removed ${entriesRemoved} expired deduplication entries`);
|
|
|
|
|
+ this._writeToFile(logMessage);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
// Branch filtering logging
|
|
// Branch filtering logging
|
|
|
branchFiltered(branch, filterBranch, commandName) {
|
|
branchFiltered(branch, filterBranch, commandName) {
|
|
|
if (!this._shouldLog('DEBUG')) return;
|
|
if (!this._shouldLog('DEBUG')) return;
|
|
|
const timestamp = this._getTimestamp();
|
|
const timestamp = this._getTimestamp();
|
|
|
|
|
+ const logMessage = `${timestamp} FILTERED: Command '${commandName}' skipped - branch '${branch}' != '${filterBranch}'`;
|
|
|
console.log(`${timestamp} ${this._colorize('FILTERED', '90')}: Command '${commandName}' skipped - branch '${branch}' != '${filterBranch}'`);
|
|
console.log(`${timestamp} ${this._colorize('FILTERED', '90')}: Command '${commandName}' skipped - branch '${branch}' != '${filterBranch}'`);
|
|
|
|
|
+ this._writeToFile(logMessage);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
// Variable substitution logging (debug level)
|
|
// Variable substitution logging (debug level)
|
|
|
variablesExtracted(eventType, variables) {
|
|
variablesExtracted(eventType, variables) {
|
|
|
if (!this._shouldLog('DEBUG')) return;
|
|
if (!this._shouldLog('DEBUG')) return;
|
|
|
const timestamp = this._getTimestamp();
|
|
const timestamp = this._getTimestamp();
|
|
|
|
|
+ const logMessage = `${timestamp} VARIABLES: Extracted from ${eventType}`;
|
|
|
console.log(`${timestamp} ${this._colorize('VARIABLES', '90')}: Extracted from ${eventType}`);
|
|
console.log(`${timestamp} ${this._colorize('VARIABLES', '90')}: Extracted from ${eventType}`);
|
|
|
- console.log(JSON.stringify(variables, null, 2));
|
|
|
|
|
|
|
+ this._writeToFile(logMessage);
|
|
|
|
|
+ const dataStr = JSON.stringify(variables, null, 2);
|
|
|
|
|
+ console.log(dataStr);
|
|
|
|
|
+ this._writeToFile(dataStr);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
// Queue health metrics
|
|
// Queue health metrics
|
|
@@ -221,6 +393,21 @@ export class Logger {
|
|
|
console.log(` Current job: #${stats.currentJob.id} (${stats.currentJob.eventType})`);
|
|
console.log(` Current job: #${stats.currentJob.id} (${stats.currentJob.eventType})`);
|
|
|
}
|
|
}
|
|
|
console.log(`${this._colorize('='.repeat(80), '36')}\n`);
|
|
console.log(`${this._colorize('='.repeat(80), '36')}\n`);
|
|
|
|
|
+
|
|
|
|
|
+ // Write to file
|
|
|
|
|
+ this._writeToFile(`${'='.repeat(80)}`);
|
|
|
|
|
+ this._writeToFile(`${timestamp} QUEUE STATS`);
|
|
|
|
|
+ this._writeToFile(`${'-'.repeat(80)}`);
|
|
|
|
|
+ this._writeToFile(` Pending jobs: ${stats.pending}`);
|
|
|
|
|
+ this._writeToFile(` Running jobs: ${stats.running}`);
|
|
|
|
|
+ this._writeToFile(` Processed (24h): ${this.metrics.jobsProcessed24h}`);
|
|
|
|
|
+ this._writeToFile(` Failed (24h): ${this.metrics.jobsFailed24h}`);
|
|
|
|
|
+ this._writeToFile(` Deduplicated (24h): ${this.metrics.jobsDuplicated24h}`);
|
|
|
|
|
+ this._writeToFile(` Avg processing time: ${this._formatDuration(avgProcessingTime)}`);
|
|
|
|
|
+ if (stats.currentJob) {
|
|
|
|
|
+ this._writeToFile(` Current job: #${stats.currentJob.id} (${stats.currentJob.eventType})`);
|
|
|
|
|
+ }
|
|
|
|
|
+ this._writeToFile(`${'='.repeat(80)}`);
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|