307 lines
10 KiB
JavaScript
307 lines
10 KiB
JavaScript
/**
|
|
* Galaxy Strike Online - Logging System
|
|
* Provides file-based logging with rotation and formatting
|
|
* Renderer process version that uses IPC to communicate with main process
|
|
*/
|
|
|
|
class Logger {
|
|
constructor() {
|
|
this.logLevel = 'INFO';
|
|
this.isRenderer = typeof window !== 'undefined' && typeof window.electronAPI !== 'undefined';
|
|
this.timers = new Map();
|
|
|
|
this.levels = {
|
|
ERROR: 0,
|
|
WARN: 1,
|
|
INFO: 2,
|
|
DEBUG: 3
|
|
};
|
|
}
|
|
|
|
async initialize(appDataPath) {
|
|
if (this.isRenderer) {
|
|
// In renderer process, just log that we're ready
|
|
console.log('Logger initialized in renderer process');
|
|
return;
|
|
}
|
|
|
|
// Main process initialization (original code)
|
|
const fs = require('fs').promises;
|
|
const path = require('path');
|
|
|
|
try {
|
|
// Set up log directory in app storage location
|
|
this.logDir = path.join(appDataPath, 'logs');
|
|
|
|
// Create logs directory if it doesn't exist
|
|
await fs.mkdir(this.logDir, { recursive: true });
|
|
|
|
// Set current log file with full timestamp (YYYY-MM-DD-HH-MM-SS)
|
|
const now = new Date();
|
|
const timestamp = now.toISOString()
|
|
.replace(/T/, '-')
|
|
.replace(/\..+/, '')
|
|
.replace(/:/g, '-');
|
|
this.currentLogFile = path.join(this.logDir, `galaxy-strike-${timestamp}.log`);
|
|
|
|
// Test write to ensure permissions
|
|
await this.writeToFile('=== Galaxy Strike Online Log Session Started ===\n');
|
|
|
|
this.isInitialized = true;
|
|
console.log(`Logger initialized: ${this.logDir}`);
|
|
|
|
} catch (error) {
|
|
console.error('Failed to initialize logger:', error);
|
|
this.isInitialized = false;
|
|
}
|
|
}
|
|
|
|
async writeToFile(message) {
|
|
if (this.isRenderer) {
|
|
// In renderer process, send to main process via IPC
|
|
return;
|
|
}
|
|
|
|
if (!this.isInitialized || !this.currentLogFile) return;
|
|
|
|
try {
|
|
const fs = require('fs').promises;
|
|
|
|
// Check if file needs rotation
|
|
await this.rotateLogIfNeeded();
|
|
|
|
// Append message to current log file
|
|
await fs.appendFile(this.currentLogFile, message, 'utf8');
|
|
} catch (error) {
|
|
console.error('Failed to write to log file:', error);
|
|
}
|
|
}
|
|
|
|
async rotateLogIfNeeded() {
|
|
if (this.isRenderer) return;
|
|
|
|
const fs = require('fs').promises;
|
|
const path = require('path');
|
|
|
|
try {
|
|
const stats = await fs.stat(this.currentLogFile);
|
|
|
|
if (stats.size >= this.maxFileSize) {
|
|
// Rotate log file with full timestamp
|
|
const now = new Date();
|
|
const timestamp = now.toISOString()
|
|
.replace(/T/, '-')
|
|
.replace(/\..+/, '')
|
|
.replace(/:/g, '-');
|
|
const rotatedFile = path.join(this.logDir, `galaxy-strike-${timestamp}.log`);
|
|
|
|
await fs.rename(this.currentLogFile, rotatedFile);
|
|
|
|
// Clean up old log files
|
|
await this.cleanupOldLogs();
|
|
|
|
// Create new current log file with new timestamp
|
|
const newTimestamp = new Date().toISOString()
|
|
.replace(/T/, '-')
|
|
.replace(/\..+/, '')
|
|
.replace(/:/g, '-');
|
|
this.currentLogFile = path.join(this.logDir, `galaxy-strike-${newTimestamp}.log`);
|
|
|
|
await this.writeToFile('=== Log Rotated ===\n');
|
|
}
|
|
} catch (error) {
|
|
// File might not exist yet, which is fine
|
|
if (error.code !== 'ENOENT') {
|
|
console.error('Failed to rotate log:', error);
|
|
}
|
|
}
|
|
}
|
|
|
|
async cleanupOldLogs() {
|
|
if (this.isRenderer) return;
|
|
|
|
const fs = require('fs').promises;
|
|
const path = require('path');
|
|
|
|
try {
|
|
const files = await fs.readdir(this.logDir);
|
|
const logFiles = files
|
|
.filter(file => file.startsWith('galaxy-strike-') && file.endsWith('.log'))
|
|
.map(file => ({
|
|
name: file,
|
|
path: path.join(this.logDir, file)
|
|
}));
|
|
|
|
if (logFiles.length > this.maxLogFiles) {
|
|
// Get file stats and sort by modification time
|
|
const filesWithStats = await Promise.all(
|
|
logFiles.map(async file => {
|
|
const stats = await fs.stat(file.path);
|
|
return { ...file, mtime: stats.mtime };
|
|
})
|
|
);
|
|
|
|
filesWithStats.sort((a, b) => b.mtime - a.mtime);
|
|
|
|
// Delete oldest files
|
|
const filesToDelete = filesWithStats.slice(this.maxLogFiles);
|
|
for (const file of filesToDelete) {
|
|
await fs.unlink(file.path);
|
|
}
|
|
}
|
|
} catch (error) {
|
|
console.error('Failed to cleanup old logs:', error);
|
|
}
|
|
}
|
|
|
|
formatMessage(level, message, data = null) {
|
|
const timestamp = new Date().toISOString();
|
|
const dataStr = data ? ` | Data: ${JSON.stringify(data)}` : '';
|
|
return `[${timestamp}] [${level}] ${message}${dataStr}\n`;
|
|
}
|
|
|
|
shouldLog(level) {
|
|
const currentLevel = this.levels[this.logLevel] || 2;
|
|
const messageLevel = this.levels[level] || 2;
|
|
return messageLevel <= currentLevel;
|
|
}
|
|
|
|
async log(level, message, data = null) {
|
|
if (!this.shouldLog(level)) return;
|
|
|
|
if (this.isRenderer && window.electronAPI) {
|
|
// Send to main process via IPC - ensure data is serializable
|
|
try {
|
|
const serializableData = data ? this.makeSerializable(data) : null;
|
|
window.electronAPI.log(level, message, serializableData);
|
|
} catch (error) {
|
|
console.error('Failed to send log to main process:', error);
|
|
// Fallback to console
|
|
console.log(`[${level}] ${message}`, data || '');
|
|
}
|
|
} else {
|
|
// Main process logging
|
|
const formattedMessage = this.formatMessage(level, message, data);
|
|
await this.writeToFile(formattedMessage);
|
|
console.log(`[${level}] ${message}`, data || '');
|
|
}
|
|
}
|
|
|
|
makeSerializable(obj) {
|
|
try {
|
|
// Convert to JSON and back to ensure it's serializable
|
|
return JSON.parse(JSON.stringify(obj));
|
|
} catch (error) {
|
|
// If not serializable, convert to string representation
|
|
return {
|
|
type: 'non-serializable',
|
|
toString: obj.toString ? obj.toString() : String(obj),
|
|
constructor: obj.constructor ? obj.constructor.name : 'Unknown'
|
|
};
|
|
}
|
|
}
|
|
|
|
async error(message, data = null) {
|
|
await this.log('error', message, data);
|
|
console.error(`[ERROR] ${message}`, data || '');
|
|
}
|
|
|
|
async warn(message, data = null) {
|
|
await this.log('warn', message, data);
|
|
console.warn(`[WARN] ${message}`, data || '');
|
|
}
|
|
|
|
async info(message, data = null) {
|
|
await this.log('info', message, data);
|
|
console.info(`[INFO] ${message}`, data || '');
|
|
}
|
|
|
|
async debug(message, data = null) {
|
|
await this.log('debug', message, data);
|
|
console.debug(`[DEBUG] ${message}`, data || '');
|
|
}
|
|
|
|
async gameEvent(eventType, details) {
|
|
const message = `Game Event: ${eventType}`;
|
|
await this.info(message, details);
|
|
}
|
|
|
|
// Timing methods
|
|
startTimer(name) {
|
|
this.timers.set(name, performance.now());
|
|
}
|
|
|
|
endTimer(name) {
|
|
const startTime = this.timers.get(name);
|
|
if (startTime) {
|
|
const duration = performance.now() - startTime;
|
|
this.timers.delete(name);
|
|
return duration;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
async timeAsync(name, asyncFunction) {
|
|
this.startTimer(name);
|
|
try {
|
|
const result = await asyncFunction();
|
|
const duration = this.endTimer(name);
|
|
await this.info(`${name} completed`, { duration: `${duration.toFixed(2)}ms` });
|
|
return result;
|
|
} catch (error) {
|
|
const duration = this.endTimer(name);
|
|
await this.error(`${name} failed`, { duration: `${duration.toFixed(2)}ms`, error: error.message });
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
async playerAction(action, details) {
|
|
const message = `Player Action: ${action}`;
|
|
await this.info(message, details);
|
|
}
|
|
|
|
async systemEvent(system, event, details) {
|
|
const message = `System Event: ${system} - ${event}`;
|
|
await this.info(message, details);
|
|
}
|
|
|
|
async errorEvent(error, context = null) {
|
|
const message = `Error Event: ${error.message || error}`;
|
|
const data = {
|
|
stack: error.stack,
|
|
context: context
|
|
};
|
|
await this.error(message, data);
|
|
}
|
|
|
|
setLogLevel(level) {
|
|
if (this.levels.hasOwnProperty(level)) {
|
|
this.logLevel = level;
|
|
this.info(`Log level changed to: ${level}`);
|
|
}
|
|
}
|
|
|
|
getLogInfo() {
|
|
return {
|
|
isInitialized: this.isInitialized,
|
|
isRenderer: this.isRenderer,
|
|
logDirectory: this.logDir,
|
|
currentLogFile: this.currentLogFile,
|
|
logLevel: this.logLevel
|
|
};
|
|
}
|
|
}
|
|
|
|
// Export singleton instance
|
|
const logger = new Logger();
|
|
|
|
// For Node.js environments
|
|
if (typeof module !== 'undefined' && module.exports) {
|
|
module.exports = logger;
|
|
}
|
|
|
|
// For browser environments
|
|
if (typeof window !== 'undefined') {
|
|
window.logger = logger;
|
|
}
|