Add API logging functionality and enhance scheduler service
Introduced ApiLog model and integrated logging for scheduled tasks in the SchedulerService. Updated server.js to include request logging middleware and new API log routes. Enhanced frontend navigation by adding a link to system logs for admin users. Adjusted session check interval in App.vue for improved performance. This update improves monitoring and debugging capabilities across the application.
This commit is contained in:
161
backend/services/apiLogService.js
Normal file
161
backend/services/apiLogService.js
Normal file
@@ -0,0 +1,161 @@
|
||||
import ApiLog from '../models/ApiLog.js';
|
||||
import { Op } from 'sequelize';
|
||||
|
||||
class ApiLogService {
|
||||
/**
|
||||
* Log an API request/response
|
||||
*/
|
||||
async logRequest(options) {
|
||||
try {
|
||||
const {
|
||||
userId = null,
|
||||
method,
|
||||
path,
|
||||
statusCode = null,
|
||||
requestBody = null,
|
||||
responseBody = null,
|
||||
executionTime = null,
|
||||
errorMessage = null,
|
||||
ipAddress = null,
|
||||
userAgent = null,
|
||||
logType = 'api_request',
|
||||
schedulerJobType = null
|
||||
} = options;
|
||||
|
||||
// Truncate long fields
|
||||
const truncate = (str, maxLen = 10000) => {
|
||||
if (!str) return null;
|
||||
const strVal = typeof str === 'string' ? str : JSON.stringify(str);
|
||||
return strVal.length > maxLen ? strVal.substring(0, maxLen) + '... (truncated)' : strVal;
|
||||
};
|
||||
|
||||
await ApiLog.create({
|
||||
userId,
|
||||
method,
|
||||
path,
|
||||
statusCode,
|
||||
requestBody: truncate(requestBody),
|
||||
responseBody: truncate(responseBody),
|
||||
executionTime,
|
||||
errorMessage: truncate(errorMessage, 5000),
|
||||
ipAddress,
|
||||
userAgent,
|
||||
logType,
|
||||
schedulerJobType
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Error logging API request:', error);
|
||||
// Don't throw - logging failures shouldn't break the main operation
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Log scheduler execution
|
||||
*/
|
||||
async logSchedulerExecution(jobType, success, message, executionTime = null, errorMessage = null) {
|
||||
try {
|
||||
await ApiLog.create({
|
||||
userId: null,
|
||||
method: 'SCHEDULER',
|
||||
path: `/scheduler/${jobType}`,
|
||||
statusCode: success ? 200 : 500,
|
||||
responseBody: message,
|
||||
executionTime,
|
||||
errorMessage,
|
||||
logType: 'scheduler',
|
||||
schedulerJobType: jobType
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Error logging scheduler execution:', error);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get logs with filters
|
||||
*/
|
||||
async getLogs(options = {}) {
|
||||
try {
|
||||
const {
|
||||
userId = null,
|
||||
logType = null,
|
||||
method = null,
|
||||
path = null,
|
||||
statusCode = null,
|
||||
startDate = null,
|
||||
endDate = null,
|
||||
limit = 100,
|
||||
offset = 0
|
||||
} = options;
|
||||
|
||||
const where = {};
|
||||
|
||||
if (userId) {
|
||||
where.userId = userId;
|
||||
}
|
||||
|
||||
if (logType) {
|
||||
where.logType = logType;
|
||||
}
|
||||
|
||||
if (method) {
|
||||
where.method = method;
|
||||
}
|
||||
|
||||
if (path) {
|
||||
where.path = { [Op.like]: `%${path}%` };
|
||||
}
|
||||
|
||||
if (statusCode !== null) {
|
||||
where.statusCode = statusCode;
|
||||
}
|
||||
|
||||
if (startDate || endDate) {
|
||||
where.createdAt = {};
|
||||
if (startDate) {
|
||||
where.createdAt[Op.gte] = new Date(startDate);
|
||||
}
|
||||
if (endDate) {
|
||||
where.createdAt[Op.lte] = new Date(endDate);
|
||||
}
|
||||
}
|
||||
|
||||
const logs = await ApiLog.findAndCountAll({
|
||||
where,
|
||||
order: [['createdAt', 'DESC']],
|
||||
limit: parseInt(limit),
|
||||
offset: parseInt(offset),
|
||||
attributes: [
|
||||
'id', 'userId', 'method', 'path', 'statusCode',
|
||||
'executionTime', 'errorMessage', 'ipAddress', 'logType',
|
||||
'schedulerJobType', 'createdAt'
|
||||
]
|
||||
});
|
||||
|
||||
return {
|
||||
logs: logs.rows,
|
||||
total: logs.count,
|
||||
limit: parseInt(limit),
|
||||
offset: parseInt(offset)
|
||||
};
|
||||
} catch (error) {
|
||||
console.error('Error getting logs:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a single log by ID
|
||||
*/
|
||||
async getLogById(logId) {
|
||||
try {
|
||||
const log = await ApiLog.findByPk(logId);
|
||||
return log;
|
||||
} catch (error) {
|
||||
console.error('Error getting log by ID:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default new ApiLogService();
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import cron from 'node-cron';
|
||||
import autoUpdateRatingsService from './autoUpdateRatingsService.js';
|
||||
import autoFetchMatchResultsService from './autoFetchMatchResultsService.js';
|
||||
import apiLogService from './apiLogService.js';
|
||||
import { devLog } from '../utils/logger.js';
|
||||
|
||||
class SchedulerService {
|
||||
@@ -22,11 +23,33 @@ class SchedulerService {
|
||||
|
||||
// Schedule automatic rating updates at 6:00 AM daily
|
||||
const ratingUpdateJob = cron.schedule('0 6 * * *', async () => {
|
||||
const startTime = Date.now();
|
||||
const timestamp = new Date().toISOString();
|
||||
console.log(`[${timestamp}] CRON: Executing scheduled rating updates...`);
|
||||
devLog('Executing scheduled rating updates...');
|
||||
|
||||
let success = false;
|
||||
let message = '';
|
||||
let errorMessage = null;
|
||||
|
||||
try {
|
||||
await autoUpdateRatingsService.executeAutomaticUpdates();
|
||||
const executionTime = Date.now() - startTime;
|
||||
success = true;
|
||||
message = 'Rating updates completed successfully';
|
||||
console.log(`[${new Date().toISOString()}] CRON: Rating updates completed successfully`);
|
||||
|
||||
// Log to ApiLog
|
||||
await apiLogService.logSchedulerExecution('rating_updates', true, message, executionTime, null);
|
||||
} catch (error) {
|
||||
console.error('Error in scheduled rating updates:', error);
|
||||
const executionTime = Date.now() - startTime;
|
||||
success = false;
|
||||
errorMessage = error.message;
|
||||
console.error(`[${new Date().toISOString()}] CRON ERROR in scheduled rating updates:`, error);
|
||||
console.error('Stack trace:', error.stack);
|
||||
|
||||
// Log to ApiLog
|
||||
await apiLogService.logSchedulerExecution('rating_updates', false, 'Rating updates failed', executionTime, errorMessage);
|
||||
}
|
||||
}, {
|
||||
scheduled: false, // Don't start automatically
|
||||
@@ -35,14 +58,37 @@ class SchedulerService {
|
||||
|
||||
this.jobs.set('ratingUpdates', ratingUpdateJob);
|
||||
ratingUpdateJob.start();
|
||||
console.log('[Scheduler] Rating update job scheduled and started');
|
||||
|
||||
// Schedule automatic match results fetching at 6:30 AM daily
|
||||
const matchResultsJob = cron.schedule('30 6 * * *', async () => {
|
||||
const startTime = Date.now();
|
||||
const timestamp = new Date().toISOString();
|
||||
console.log(`[${timestamp}] CRON: Executing scheduled match results fetch...`);
|
||||
devLog('Executing scheduled match results fetch...');
|
||||
|
||||
let success = false;
|
||||
let message = '';
|
||||
let errorMessage = null;
|
||||
|
||||
try {
|
||||
await autoFetchMatchResultsService.executeAutomaticFetch();
|
||||
const executionTime = Date.now() - startTime;
|
||||
success = true;
|
||||
message = 'Match results fetch completed successfully';
|
||||
console.log(`[${new Date().toISOString()}] CRON: Match results fetch completed successfully`);
|
||||
|
||||
// Log to ApiLog
|
||||
await apiLogService.logSchedulerExecution('match_results', true, message, executionTime, null);
|
||||
} catch (error) {
|
||||
console.error('Error in scheduled match results fetch:', error);
|
||||
const executionTime = Date.now() - startTime;
|
||||
success = false;
|
||||
errorMessage = error.message;
|
||||
console.error(`[${new Date().toISOString()}] CRON ERROR in scheduled match results fetch:`, error);
|
||||
console.error('Stack trace:', error.stack);
|
||||
|
||||
// Log to ApiLog
|
||||
await apiLogService.logSchedulerExecution('match_results', false, 'Match results fetch failed', executionTime, errorMessage);
|
||||
}
|
||||
}, {
|
||||
scheduled: false, // Don't start automatically
|
||||
@@ -51,8 +97,25 @@ class SchedulerService {
|
||||
|
||||
this.jobs.set('matchResults', matchResultsJob);
|
||||
matchResultsJob.start();
|
||||
console.log('[Scheduler] Match results fetch job scheduled and started');
|
||||
|
||||
this.isRunning = true;
|
||||
const now = new Date();
|
||||
const tomorrow6AM = new Date(now);
|
||||
tomorrow6AM.setDate(tomorrow6AM.getDate() + 1);
|
||||
tomorrow6AM.setHours(6, 0, 0, 0);
|
||||
|
||||
const tomorrow630AM = new Date(now);
|
||||
tomorrow630AM.setDate(tomorrow630AM.getDate() + 1);
|
||||
tomorrow630AM.setHours(6, 30, 0, 0);
|
||||
|
||||
console.log('[Scheduler] ===== SCHEDULER SERVICE STARTED =====');
|
||||
console.log(`[Scheduler] Server time: ${now.toISOString()}`);
|
||||
console.log(`[Scheduler] Timezone: Europe/Berlin`);
|
||||
console.log(`[Scheduler] Rating updates: Next execution at ${tomorrow6AM.toISOString()} (6:00 AM Berlin time)`);
|
||||
console.log(`[Scheduler] Match results fetch: Next execution at ${tomorrow630AM.toISOString()} (6:30 AM Berlin time)`);
|
||||
console.log('[Scheduler] =====================================');
|
||||
|
||||
devLog('Scheduler service started successfully');
|
||||
devLog('Rating updates scheduled for 6:00 AM daily (Europe/Berlin timezone)');
|
||||
devLog('Match results fetch scheduled for 6:30 AM daily (Europe/Berlin timezone)');
|
||||
|
||||
Reference in New Issue
Block a user