Files
crowd-funder-from-jason/electron/src/rt/logger.ts
Matthew Raymer 981920dd7a feat(electron): enhance SQLite operation logging and debugging
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
2025-06-05 13:28:35 +00:00

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;