forked from jsnbuchanan/crowd-funder-for-time-pwa
WIP: Debugging sqlite-run/dbExec hanging issue - Add renderer-to-main process log forwarding - Implement operation tracking with unique IDs - Add detailed timing and retry logging - Enhance error capture and formatting - Move logs to app user data directory - Add exponential backoff for retries This commit adds comprehensive logging to help diagnose why dbExec operations are hanging when sent through the sqlite-run channel. Changes include: - Forward all renderer process logs to main process - Track SQLite operations with unique IDs - Log operation timing and retry attempts - Capture detailed error information - Implement exponential backoff for retries - Centralize logs in app user data directory Security: - Logs are stored in app user data directory - Sensitive data is sanitized in logs - Error stacks are properly captured Testing: - Manual testing of logging system - Verify log capture in both processes - Check log rotation and file sizes TODO: - Monitor logs to identify root cause - Add specific logging for settings table - Consider connection pooling if needed
188 lines
5.6 KiB
TypeScript
188 lines
5.6 KiB
TypeScript
/**
|
|
* Enhanced logging system for TimeSafari Electron
|
|
* Provides structured logging with proper levels and formatting
|
|
* Supports both console and file output with different verbosity levels
|
|
*
|
|
* @author Matthew Raymer
|
|
*/
|
|
|
|
import { app, ipcMain } from 'electron';
|
|
import winston from 'winston';
|
|
import path from 'path';
|
|
import os from 'os';
|
|
import fs from 'fs';
|
|
|
|
// Extend Winston Logger type with our custom loggers
|
|
declare module 'winston' {
|
|
interface Logger {
|
|
sqlite: {
|
|
debug: (message: string, ...args: unknown[]) => void;
|
|
info: (message: string, ...args: unknown[]) => void;
|
|
warn: (message: string, ...args: unknown[]) => void;
|
|
error: (message: string, ...args: unknown[]) => void;
|
|
};
|
|
migration: {
|
|
debug: (message: string, ...args: unknown[]) => void;
|
|
info: (message: string, ...args: unknown[]) => void;
|
|
warn: (message: string, ...args: unknown[]) => void;
|
|
error: (message: string, ...args: unknown[]) => void;
|
|
};
|
|
}
|
|
}
|
|
|
|
// Create logs directory if it doesn't exist
|
|
const logsDir = path.join(app.getPath('userData'), 'logs');
|
|
if (!fs.existsSync(logsDir)) {
|
|
fs.mkdirSync(logsDir, { recursive: true });
|
|
}
|
|
|
|
// Custom format for console output with migration filtering
|
|
const consoleFormat = winston.format.combine(
|
|
winston.format.timestamp(),
|
|
winston.format.colorize(),
|
|
winston.format.printf(({ level, message, timestamp, ...metadata }) => {
|
|
// Skip migration logs unless DEBUG_MIGRATIONS is set
|
|
if (level === 'info' &&
|
|
typeof message === 'string' &&
|
|
message.includes('[Migration]') &&
|
|
!process.env.DEBUG_MIGRATIONS) {
|
|
return '';
|
|
}
|
|
|
|
let msg = `${timestamp} [${level}] ${message}`;
|
|
if (Object.keys(metadata).length > 0) {
|
|
msg += ` ${JSON.stringify(metadata, null, 2)}`;
|
|
}
|
|
return msg;
|
|
})
|
|
);
|
|
|
|
// Custom format for file output
|
|
const fileFormat = winston.format.combine(
|
|
winston.format.timestamp(),
|
|
winston.format.json()
|
|
);
|
|
|
|
// Create logger instance
|
|
const logger = winston.createLogger({
|
|
level: process.env.NODE_ENV === 'development' ? 'debug' : 'info',
|
|
format: fileFormat,
|
|
defaultMeta: { service: 'timesafari-electron' },
|
|
transports: [
|
|
// Console transport with custom format and migration filtering
|
|
new winston.transports.Console({
|
|
format: consoleFormat,
|
|
level: process.env.NODE_ENV === 'development' ? 'debug' : 'info',
|
|
silent: false // Ensure we can still see non-migration logs
|
|
}),
|
|
// File transport for all logs
|
|
new winston.transports.File({
|
|
filename: path.join(logsDir, 'error.log'),
|
|
level: 'error',
|
|
maxsize: 5242880, // 5MB
|
|
maxFiles: 5
|
|
}),
|
|
// File transport for all logs including debug
|
|
new winston.transports.File({
|
|
filename: path.join(logsDir, 'combined.log'),
|
|
maxsize: 5242880, // 5MB
|
|
maxFiles: 5
|
|
})
|
|
]
|
|
}) as winston.Logger & {
|
|
sqlite: {
|
|
debug: (message: string, ...args: unknown[]) => void;
|
|
info: (message: string, ...args: unknown[]) => void;
|
|
warn: (message: string, ...args: unknown[]) => void;
|
|
error: (message: string, ...args: unknown[]) => void;
|
|
};
|
|
migration: {
|
|
debug: (message: string, ...args: unknown[]) => void;
|
|
info: (message: string, ...args: unknown[]) => void;
|
|
warn: (message: string, ...args: unknown[]) => void;
|
|
error: (message: string, ...args: unknown[]) => void;
|
|
};
|
|
};
|
|
|
|
// Add SQLite specific logger
|
|
logger.sqlite = {
|
|
debug: (message: string, ...args: unknown[]) => {
|
|
logger.debug(`[SQLite] ${message}`, ...args);
|
|
},
|
|
info: (message: string, ...args: unknown[]) => {
|
|
logger.info(`[SQLite] ${message}`, ...args);
|
|
},
|
|
warn: (message: string, ...args: unknown[]) => {
|
|
logger.warn(`[SQLite] ${message}`, ...args);
|
|
},
|
|
error: (message: string, ...args: unknown[]) => {
|
|
logger.error(`[SQLite] ${message}`, ...args);
|
|
}
|
|
};
|
|
|
|
// Add migration specific logger with debug filtering
|
|
logger.migration = {
|
|
debug: (message: string, ...args: unknown[]) => {
|
|
if (process.env.DEBUG_MIGRATIONS) {
|
|
//logger.debug(`[Migration] ${message}`, ...args);
|
|
}
|
|
},
|
|
info: (message: string, ...args: unknown[]) => {
|
|
// Always log to file, but only log to console if DEBUG_MIGRATIONS is set
|
|
if (process.env.DEBUG_MIGRATIONS) {
|
|
//logger.info(`[Migration] ${message}`, ...args);
|
|
} else {
|
|
// Use a separate transport for migration logs to file only
|
|
const metadata = args[0] as Record<string, unknown>;
|
|
logger.write({
|
|
level: 'info',
|
|
message: `[Migration] ${message}`,
|
|
...(metadata || {})
|
|
});
|
|
}
|
|
},
|
|
warn: (message: string, ...args: unknown[]) => {
|
|
// Always log warnings to both console and file
|
|
//logger.warn(`[Migration] ${message}`, ...args);
|
|
},
|
|
error: (message: string, ...args: unknown[]) => {
|
|
// Always log errors to both console and file
|
|
//logger.error(`[Migration] ${message}`, ...args);
|
|
}
|
|
};
|
|
|
|
// Add renderer log handler
|
|
ipcMain.on('renderer-log', (_event, { level, args, source, operation, error }) => {
|
|
const message = args.map((arg: unknown) =>
|
|
typeof arg === 'object' ? JSON.stringify(arg, null, 2) : String(arg)
|
|
).join(' ');
|
|
|
|
const meta = {
|
|
source: source || 'renderer',
|
|
...(operation && { operation }),
|
|
...(error && { error })
|
|
};
|
|
|
|
switch (level) {
|
|
case 'error':
|
|
logger.error(message, meta);
|
|
break;
|
|
case 'warn':
|
|
logger.warn(message, meta);
|
|
break;
|
|
case 'info':
|
|
logger.info(message, meta);
|
|
break;
|
|
case 'debug':
|
|
logger.debug(message, meta);
|
|
break;
|
|
default:
|
|
logger.log(level, message, meta);
|
|
}
|
|
});
|
|
|
|
// Export logger instance
|
|
export { logger };
|
|
|
|
// Export a function to get the logs directory
|
|
export const getLogsDirectory = () => logsDir;
|