chore: logger helper function to convert ISO string to user's local datetime

This commit is contained in:
Rim 2025-04-02 07:33:09 -04:00
parent 7ccc0be712
commit 952aa7a3b5
4 changed files with 62 additions and 41 deletions

75
app.js
View File

@ -2,10 +2,11 @@ const express = require('express');
const path = require('path'); const path = require('path');
const bodyParser = require('body-parser'); const bodyParser = require('body-parser');
const API = require('./src/js/index.js'); const API = require('./src/js/index.js');
const { logger } = require('./src/js/logger'); const { logger } = require('./src/js/logger.js');
const favicon = require('serve-favicon'); const favicon = require('serve-favicon');
const app = express(); const app = express();
const port = process.env.PORT || 3512; const port = process.env.PORT || 3512;
require('./src/js/utils.js')
app.set('trust proxy', true); app.set('trust proxy', true);
@ -98,7 +99,7 @@ const sanitizeJsonOutput = (data) => {
try { try {
return JSON.parse(sanitizedString); return JSON.parse(sanitizedString);
} catch (e) { } catch (e) {
console.error('Error parsing sanitized JSON:', e); logger.error('Error parsing sanitized JSON:', e);
return data; // Return original data if parsing fails return data; // Return original data if parsing fails
} }
}; };
@ -165,7 +166,7 @@ const ensureLogin = async (ssoToken) => {
]); ]);
logger.debug(`Login successful: ${JSON.stringify(loginResult)}`); logger.debug(`Login successful: ${JSON.stringify(loginResult)}`);
logger.debug(`Session created at: ${new Date().toISOString()}`); logger.debug(`Session created at: ${global.Utils.toIsoString(new Date())}`);
activeSessions.set(ssoToken, new Date()); activeSessions.set(ssoToken, new Date());
} else { } else {
logger.debug('Using existing session'); logger.debug('Using existing session');
@ -176,7 +177,7 @@ const ensureLogin = async (ssoToken) => {
const handleApiError = (error, res) => { const handleApiError = (error, res) => {
logger.error('API Error:', error); logger.error('API Error:', error);
logger.error(`Error Stack: ${error.stack}`); logger.error(`Error Stack: ${error.stack}`);
logger.error(`Error Time: ${new Date().toISOString()}`); logger.error(`Error Time: ${global.Utils.toIsoString(new Date())}`);
// Try to extract more useful information from the error // Try to extract more useful information from the error
let errorMessage = error.message || 'Unknown API error'; let errorMessage = error.message || 'Unknown API error';
@ -190,7 +191,7 @@ const handleApiError = (error, res) => {
message: message:
'Failed to parse API response. This usually means the SSO token is invalid or expired.', 'Failed to parse API response. This usually means the SSO token is invalid or expired.',
error_type: 'InvalidResponseError', error_type: 'InvalidResponseError',
timestamp: new Date().toISOString(), timestamp: global.Utils.toIsoString(new Date()),
}); });
} }
@ -199,7 +200,7 @@ const handleApiError = (error, res) => {
status: 'error', status: 'error',
message: errorMessage, message: errorMessage,
error_type: errorName, error_type: errorName,
timestamp: new Date().toISOString(), timestamp: global.Utils.toIsoString(new Date()),
}); });
}; };
@ -265,13 +266,13 @@ app.post('/api/stats', async (req, res) => {
try { try {
await ensureLogin(ssoToken); await ensureLogin(ssoToken);
} catch (loginError) { } catch (loginError) {
console.error('Login error:', loginError); logger.error('Login error:', loginError);
return res.status(200).json({ return res.status(200).json({
status: 'error', status: 'error',
error_type: 'LoginError', error_type: 'LoginError',
message: 'SSO token login failed', message: 'SSO token login failed',
details: loginError.message || 'Unknown login error', details: loginError.message || 'Unknown login error',
timestamp: new Date().toISOString(), timestamp: global.Utils.toIsoString(new Date()),
}); });
} }
@ -290,7 +291,7 @@ app.post('/api/stats', async (req, res) => {
return res.status(200).json({ return res.status(200).json({
status: 'error', status: 'error',
message: `${game} requires Uno ID (numerical ID)`, message: `${game} requires Uno ID (numerical ID)`,
timestamp: new Date().toISOString(), timestamp: global.Utils.toIsoString(new Date()),
}); });
} }
@ -347,7 +348,7 @@ app.post('/api/stats', async (req, res) => {
return res.status(200).json({ return res.status(200).json({
status: 'error', status: 'error',
message: 'Invalid game selected', message: 'Invalid game selected',
timestamp: new Date().toISOString(), timestamp: global.Utils.toIsoString(new Date()),
}); });
} }
} else if (apiCall === 'combatHistory') { } else if (apiCall === 'combatHistory') {
@ -397,7 +398,7 @@ app.post('/api/stats', async (req, res) => {
return res.status(200).json({ return res.status(200).json({
status: 'error', status: 'error',
message: 'Invalid game selected', message: 'Invalid game selected',
timestamp: new Date().toISOString(), timestamp: global.Utils.toIsoString(new Date()),
}); });
} }
} else if (apiCall === 'mapList') { } else if (apiCall === 'mapList') {
@ -410,14 +411,14 @@ app.post('/api/stats', async (req, res) => {
return res.status(200).json({ return res.status(200).json({
status: 'error', status: 'error',
message: 'Map list is only available for Modern Warfare', message: 'Map list is only available for Modern Warfare',
timestamp: new Date().toISOString(), timestamp: global.Utils.toIsoString(new Date()),
}); });
} }
} }
logger.debug('Data fetched successfully'); logger.debug('Data fetched successfully');
logger.debug(`Response Size: ~${JSON.stringify(data).length / 1024} KB`); logger.debug(`Response Size: ~${JSON.stringify(data).length / 1024} KB`);
logger.debug(`Response Time: ${new Date().toISOString()}`); logger.debug(`Response Time: ${global.Utils.toIsoString(new Date())}`);
// Safely handle the response data // Safely handle the response data
if (!data) { if (!data) {
@ -426,7 +427,7 @@ app.post('/api/stats', async (req, res) => {
status: 'partial_success', status: 'partial_success',
message: 'No data returned from API, but no error thrown', message: 'No data returned from API, but no error thrown',
data: null, data: null,
timestamp: new Date().toISOString(), timestamp: global.Utils.toIsoString(new Date()),
}); });
} }
@ -437,20 +438,20 @@ app.post('/api/stats', async (req, res) => {
return res.json({ return res.json({
// status: "success", // status: "success",
data: processJsonOutput(data, { sanitize, replaceKeys }), data: processJsonOutput(data, { sanitize, replaceKeys }),
timestamp: new Date().toISOString(), timestamp: global.Utils.toIsoString(new Date()),
}); });
} catch (apiError) { } catch (apiError) {
return handleApiError(apiError, res); return handleApiError(apiError, res);
} }
} catch (serverError) { } catch (serverError) {
console.error('Server Error:', serverError); logger.error('Server Error:', serverError);
// Return a structured error response even for unexpected errors // Return a structured error response even for unexpected errors
return res.status(200).json({ return res.status(200).json({
status: 'server_error', status: 'server_error',
message: 'The server encountered an unexpected error', message: 'The server encountered an unexpected error',
error_details: serverError.message || 'Unknown server error', error_details: serverError.message || 'Unknown server error',
timestamp: new Date().toISOString(), timestamp: global.Utils.toIsoString(new Date()),
}); });
} }
}); });
@ -503,7 +504,7 @@ app.post('/api/matches', async (req, res) => {
error_type: 'LoginError', error_type: 'LoginError',
message: 'SSO token login failed', message: 'SSO token login failed',
details: loginError.message || 'Unknown login error', details: loginError.message || 'Unknown login error',
timestamp: new Date().toISOString(), timestamp: global.Utils.toIsoString(new Date()),
}); });
} }
@ -527,7 +528,7 @@ app.post('/api/matches', async (req, res) => {
return res.status(200).json({ return res.status(200).json({
status: 'error', status: 'error',
message: `${game} requires Uno ID (numerical ID)`, message: `${game} requires Uno ID (numerical ID)`,
timestamp: new Date().toISOString(), timestamp: global.Utils.toIsoString(new Date()),
}); });
} }
@ -577,7 +578,7 @@ app.post('/api/matches', async (req, res) => {
return res.status(200).json({ return res.status(200).json({
status: 'error', status: 'error',
message: 'Invalid game selected', message: 'Invalid game selected',
timestamp: new Date().toISOString(), timestamp: global.Utils.toIsoString(new Date()),
}); });
} }
@ -586,7 +587,7 @@ app.post('/api/matches', async (req, res) => {
return res.json({ return res.json({
// status: "success", // status: "success",
data: processJsonOutput(data, { sanitize, replaceKeys }), data: processJsonOutput(data, { sanitize, replaceKeys }),
timestamp: new Date().toISOString(), timestamp: global.Utils.toIsoString(new Date()),
}); });
} catch (apiError) { } catch (apiError) {
return handleApiError(apiError, res); return handleApiError(apiError, res);
@ -596,7 +597,7 @@ app.post('/api/matches', async (req, res) => {
status: 'server_error', status: 'server_error',
message: 'The server encountered an unexpected error', message: 'The server encountered an unexpected error',
error_details: serverError.message || 'Unknown server error', error_details: serverError.message || 'Unknown server error',
timestamp: new Date().toISOString(), timestamp: global.Utils.toIsoString(new Date()),
}); });
} }
}); });
@ -650,7 +651,7 @@ app.post('/api/matchInfo', async (req, res) => {
error_type: 'LoginError', error_type: 'LoginError',
message: 'SSO token login failed', message: 'SSO token login failed',
details: loginError.message || 'Unknown login error', details: loginError.message || 'Unknown login error',
timestamp: new Date().toISOString(), timestamp: global.Utils.toIsoString(new Date()),
}); });
} }
@ -710,7 +711,7 @@ app.post('/api/matchInfo', async (req, res) => {
return res.status(200).json({ return res.status(200).json({
status: 'error', status: 'error',
message: 'Invalid game selected', message: 'Invalid game selected',
timestamp: new Date().toISOString(), timestamp: global.Utils.toIsoString(new Date()),
}); });
} }
@ -719,7 +720,7 @@ app.post('/api/matchInfo', async (req, res) => {
return res.json({ return res.json({
// status: "success", // status: "success",
data: processJsonOutput(data, { sanitize, replaceKeys }), data: processJsonOutput(data, { sanitize, replaceKeys }),
timestamp: new Date().toISOString(), timestamp: global.Utils.toIsoString(new Date()),
}); });
} catch (apiError) { } catch (apiError) {
return handleApiError(apiError, res); return handleApiError(apiError, res);
@ -729,7 +730,7 @@ app.post('/api/matchInfo', async (req, res) => {
status: 'server_error', status: 'server_error',
message: 'The server encountered an unexpected error', message: 'The server encountered an unexpected error',
error_details: serverError.message || 'Unknown server error', error_details: serverError.message || 'Unknown server error',
timestamp: new Date().toISOString(), timestamp: global.Utils.toIsoString(new Date()),
}); });
} }
}); });
@ -792,7 +793,7 @@ app.post('/api/user', async (req, res) => {
error_type: 'LoginError', error_type: 'LoginError',
message: 'SSO token login failed', message: 'SSO token login failed',
details: loginError.message || 'Unknown login error', details: loginError.message || 'Unknown login error',
timestamp: new Date().toISOString(), timestamp: global.Utils.toIsoString(new Date()),
}); });
} }
@ -843,7 +844,7 @@ app.post('/api/user', async (req, res) => {
return res.status(200).json({ return res.status(200).json({
status: 'error', status: 'error',
message: 'Invalid user API call selected', message: 'Invalid user API call selected',
timestamp: new Date().toISOString(), timestamp: global.Utils.toIsoString(new Date()),
}); });
} }
@ -852,7 +853,7 @@ app.post('/api/user', async (req, res) => {
return res.json({ return res.json({
// status: "success", // status: "success",
data: processJsonOutput(data, { sanitize, replaceKeys }), data: processJsonOutput(data, { sanitize, replaceKeys }),
timestamp: new Date().toISOString(), timestamp: global.Utils.toIsoString(new Date()),
}); });
} catch (apiError) { } catch (apiError) {
return handleApiError(apiError, res); return handleApiError(apiError, res);
@ -862,7 +863,7 @@ app.post('/api/user', async (req, res) => {
status: 'server_error', status: 'server_error',
message: 'The server encountered an unexpected error', message: 'The server encountered an unexpected error',
error_details: serverError.message || 'Unknown server error', error_details: serverError.message || 'Unknown server error',
timestamp: new Date().toISOString(), timestamp: global.Utils.toIsoString(new Date()),
}); });
} }
}); });
@ -912,7 +913,7 @@ app.post('/api/search', async (req, res) => {
error_type: 'LoginError', error_type: 'LoginError',
message: 'SSO token login failed', message: 'SSO token login failed',
details: loginError.message || 'Unknown login error', details: loginError.message || 'Unknown login error',
timestamp: new Date().toISOString(), timestamp: global.Utils.toIsoString(new Date()),
}); });
} }
@ -937,7 +938,7 @@ app.post('/api/search', async (req, res) => {
return res.json({ return res.json({
// status: "success", // status: "success",
data: processJsonOutput(data, { sanitize, replaceKeys }), data: processJsonOutput(data, { sanitize, replaceKeys }),
timestamp: new Date().toISOString(), timestamp: global.Utils.toIsoString(new Date()),
// link: "Stats pulled using codtracker.rimmyscorner.com", // link: "Stats pulled using codtracker.rimmyscorner.com",
}); });
} catch (apiError) { } catch (apiError) {
@ -948,7 +949,7 @@ app.post('/api/search', async (req, res) => {
status: 'server_error', status: 'server_error',
message: 'The server encountered an unexpected error', message: 'The server encountered an unexpected error',
error_details: serverError.message || 'Unknown server error', error_details: serverError.message || 'Unknown server error',
timestamp: new Date().toISOString(), timestamp: global.Utils.toIsoString(new Date()),
}); });
} }
}); });
@ -970,7 +971,7 @@ app.post('/api/log', (req, res) => {
logData = req.body; logData = req.body;
} else { } else {
// If no parsable data found, create a basic log entry // If no parsable data found, create a basic log entry
logData = { eventType: 'unknown', timestamp: new Date().toISOString() }; logData = { eventType: 'unknown', timestamp: global.Utils.toIsoString(new Date()) };
} }
// Enrich log with server-side data // Enrich log with server-side data
@ -982,7 +983,7 @@ app.post('/api/log', (req, res) => {
referer, referer,
origin, origin,
requestHeaders: sanitizeHeaders(req.headers), requestHeaders: sanitizeHeaders(req.headers),
serverTimestamp: new Date().toISOString(), serverTimestamp: global.Utils.toIsoString(new Date()),
requestId: req.id || Math.random().toString(36).substring(2, 15), requestId: req.id || Math.random().toString(36).substring(2, 15),
}, },
}; };
@ -1020,13 +1021,13 @@ function sanitizeHeaders(headers) {
function storeLogInDatabase(logData) { function storeLogInDatabase(logData) {
// Example with MongoDB // Example with MongoDB
db.collection('user_logs').insertOne(logData) db.collection('user_logs').insertOne(logData)
.catch(err => console.error('Failed to store log in database:', err)); .catch(err => logger.error('Failed to store log in database:', err));
} }
*/ */
// Basic health check endpoint // Basic health check endpoint
app.get('/health', (req, res) => { app.get('/health', (req, res) => {
res.json({ status: 'ok', timestamp: new Date().toISOString() }); res.json({ status: 'ok', timestamp: global.Utils.toIsoString(new Date()) });
}); });
// Serve the main HTML file // Serve the main HTML file

View File

@ -33,7 +33,7 @@ const clientLogger = {
[ [
JSON.stringify({ JSON.stringify({
eventType, eventType,
timestamp: new Date().toISOString(), timestamp: global.Utils.toIsoString(new Date()),
...data, ...data,
}), }),
], ],
@ -49,10 +49,10 @@ const clientLogger = {
keepalive: true, keepalive: true,
body: JSON.stringify({ body: JSON.stringify({
eventType, eventType,
timestamp: new Date().toISOString(), timestamp: global.Utils.toIsoString(new Date()),
...data, ...data,
}), }),
}).catch((e) => console.error('Logging error:', e)); }).catch((e) => logger.error('Logging error:', e));
} }
}, },
}; };

View File

@ -54,7 +54,7 @@ class Logger {
} }
formatLogEntry(type, message, data = {}) { formatLogEntry(type, message, data = {}) {
const timestamp = new Date().toISOString(); const timestamp = global.Utils.toIsoString(new Date());
const logObject = { const logObject = {
timestamp, timestamp,
type, type,

20
src/js/utils.js Normal file
View File

@ -0,0 +1,20 @@
global.Utils = {
toIsoString
};
function toIsoString(date) {
var tzo = -date.getTimezoneOffset(),
dif = tzo >= 0 ? '+' : '-',
pad = function(num) {
return (num < 10 ? '0' : '') + num;
};
return date.getFullYear() +
'-' + pad(date.getMonth() + 1) +
'-' + pad(date.getDate()) +
'T' + pad(date.getHours()) +
':' + pad(date.getMinutes()) +
':' + pad(date.getSeconds()) +
dif + pad(Math.floor(Math.abs(tzo) / 60)) +
':' + pad(Math.abs(tzo) % 60);
}