Browse Source
- Add Winston-based structured logging system with: - Separate console and file output formats - Custom SQLite and migration loggers - Configurable log levels and verbosity - Log rotation and file management - Type-safe logger extensions - Improve IPC handler management: - Add handler registration tracking - Implement proper cleanup before re-registration - Fix handler registration conflicts - Add better error handling for IPC operations - Add migration logging controls: - Configurable via DEBUG_MIGRATIONS env var - Reduced console noise while maintaining file logs - Structured migration status reporting Security: - Add proper log file permissions (0o755) - Implement log rotation to prevent disk space issues - Add type safety for all logging operations - Prevent handler registration conflicts Dependencies: - Add winston for enhanced logging - Remove deprecated @types/winston This change improves debugging capabilities while reducing console noise and fixing IPC handler registration issues that could cause database operation failures.sql-absurd-sql-further
5 changed files with 571 additions and 280 deletions
@ -1,77 +1,140 @@ |
|||||
/** |
/** |
||||
* Structured logging system for TimeSafari |
* Enhanced logging system for TimeSafari Electron |
||||
|
* Provides structured logging with proper levels and formatting |
||||
|
* Supports both console and file output with different verbosity levels |
||||
* |
* |
||||
* Provides consistent logging across the application with: |
* @author Matthew Raymer |
||||
* - Timestamp tracking |
|
||||
* - Log levels (debug, info, warn, error) |
|
||||
* - Structured data support |
|
||||
* - Component tagging |
|
||||
* |
|
||||
* @author Matthew Raymer <matthew.raymer@anomalistdesign.com> |
|
||||
* @version 1.0.0 |
|
||||
* @since 2025-06-01 |
|
||||
*/ |
*/ |
||||
|
|
||||
// Log levels
|
import winston from 'winston'; |
||||
export enum LogLevel { |
import path from 'path'; |
||||
DEBUG = 'DEBUG', |
import os from 'os'; |
||||
INFO = 'INFO', |
import fs from 'fs'; |
||||
WARN = 'WARN', |
|
||||
ERROR = 'ERROR' |
// 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; |
||||
|
}; |
||||
|
} |
||||
} |
} |
||||
|
|
||||
// Log entry interface
|
// Ensure log directory exists
|
||||
interface LogEntry { |
const logDir = path.join(os.homedir(), 'Logs', 'TimeSafari'); |
||||
timestamp: string; |
if (!fs.existsSync(logDir)) { |
||||
level: LogLevel; |
fs.mkdirSync(logDir, { recursive: true, mode: 0o755 }); |
||||
component: string; |
|
||||
message: string; |
|
||||
data?: unknown; |
|
||||
} |
} |
||||
|
|
||||
// Format log entry
|
// Custom format for console output
|
||||
const formatLogEntry = (entry: LogEntry): string => { |
const consoleFormat = winston.format.combine( |
||||
const { timestamp, level, component, message, data } = entry; |
winston.format.timestamp(), |
||||
const dataStr = data ? ` ${JSON.stringify(data, null, 2)}` : ''; |
winston.format.colorize(), |
||||
return `[${timestamp}] [${level}] [${component}] ${message}${dataStr}`; |
winston.format.printf(({ level, message, timestamp, ...metadata }) => { |
||||
}; |
// Skip debug logs for migrations unless explicitly enabled
|
||||
|
if (level === 'debug' && |
||||
|
typeof message === 'string' && |
||||
|
message.includes('Migration') && |
||||
|
!process.env.DEBUG_MIGRATIONS) { |
||||
|
return ''; |
||||
|
} |
||||
|
|
||||
// Create logger for a specific component
|
let msg = `${timestamp} [${level}] ${message}`; |
||||
export const createLogger = (component: string) => { |
if (Object.keys(metadata).length > 0) { |
||||
const log = (level: LogLevel, message: string, data?: unknown) => { |
msg += ` ${JSON.stringify(metadata, null, 2)}`; |
||||
const entry: LogEntry = { |
} |
||||
timestamp: new Date().toISOString(), |
return msg; |
||||
level, |
}) |
||||
component, |
); |
||||
message, |
|
||||
data |
|
||||
}; |
|
||||
|
|
||||
const formatted = formatLogEntry(entry); |
// Custom format for file output
|
||||
|
const fileFormat = winston.format.combine( |
||||
|
winston.format.timestamp(), |
||||
|
winston.format.json() |
||||
|
); |
||||
|
|
||||
switch (level) { |
// Create logger instance
|
||||
case LogLevel.DEBUG: |
export const logger = winston.createLogger({ |
||||
console.debug(formatted); |
level: process.env.NODE_ENV === 'development' ? 'debug' : 'info', |
||||
break; |
format: fileFormat, |
||||
case LogLevel.INFO: |
defaultMeta: { service: 'timesafari-electron' }, |
||||
console.info(formatted); |
transports: [ |
||||
break; |
// Console transport with custom format
|
||||
case LogLevel.WARN: |
new winston.transports.Console({ |
||||
console.warn(formatted); |
format: consoleFormat, |
||||
break; |
level: process.env.NODE_ENV === 'development' ? 'debug' : 'info' |
||||
case LogLevel.ERROR: |
}), |
||||
console.error(formatted); |
// File transport for all logs
|
||||
break; |
new winston.transports.File({ |
||||
} |
filename: path.join(logDir, 'error.log'), |
||||
|
level: 'error', |
||||
|
maxsize: 5242880, // 5MB
|
||||
|
maxFiles: 5 |
||||
|
}), |
||||
|
// File transport for all logs including debug
|
||||
|
new winston.transports.File({ |
||||
|
filename: path.join(logDir, '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; |
||||
|
}; |
||||
}; |
}; |
||||
|
|
||||
return { |
// Add SQLite specific logger
|
||||
debug: (message: string, data?: unknown) => log(LogLevel.DEBUG, message, data), |
logger.sqlite = { |
||||
info: (message: string, data?: unknown) => log(LogLevel.INFO, message, data), |
debug: (message: string, ...args: unknown[]) => { |
||||
warn: (message: string, data?: unknown) => log(LogLevel.WARN, message, data), |
logger.debug(`[SQLite] ${message}`, ...args); |
||||
error: (message: string, data?: unknown) => log(LogLevel.ERROR, message, data) |
}, |
||||
|
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
|
||||
|
logger.migration = { |
||||
|
debug: (message: string, ...args: unknown[]) => { |
||||
|
if (process.env.DEBUG_MIGRATIONS) { |
||||
|
logger.debug(`[Migration] ${message}`, ...args); |
||||
|
} |
||||
|
}, |
||||
|
info: (message: string, ...args: unknown[]) => { |
||||
|
logger.info(`[Migration] ${message}`, ...args); |
||||
|
}, |
||||
|
warn: (message: string, ...args: unknown[]) => { |
||||
|
logger.warn(`[Migration] ${message}`, ...args); |
||||
|
}, |
||||
|
error: (message: string, ...args: unknown[]) => { |
||||
|
logger.error(`[Migration] ${message}`, ...args); |
||||
|
} |
||||
}; |
}; |
||||
|
|
||||
// Create default logger for SQLite operations
|
// Export logger instance
|
||||
export const logger = createLogger('SQLite'); |
export default logger; |
Loading…
Reference in new issue