From 799edcf3c484ed8e520f4a9c39a48f2c3d67888e Mon Sep 17 00:00:00 2001 From: jason Date: Tue, 10 Mar 2026 12:56:26 -0500 Subject: [PATCH 1/3] feat: Add startup log utility with system info and ASCII banner --- server/utils/startupLog.js | 177 +++++++++++++++++++++++++++++++++++++ 1 file changed, 177 insertions(+) create mode 100644 server/utils/startupLog.js diff --git a/server/utils/startupLog.js b/server/utils/startupLog.js new file mode 100644 index 0000000..6c55a89 --- /dev/null +++ b/server/utils/startupLog.js @@ -0,0 +1,177 @@ +const os = require('os'); +const path = require('path'); +const fs = require('fs'); + +/** + * Startup Log Utility + * Displays comprehensive system information and branding on server start + */ + +function getSystemInfo() { + return { + hostname: os.hostname(), + platform: os.platform(), + arch: os.arch(), + nodeVersion: process.version, + cpuCores: os.cpus().length, + totalMemory: (os.totalmem() / 1024 / 1024 / 1024).toFixed(2) + ' GB', + freeMemory: (os.freemem() / 1024 / 1024 / 1024).toFixed(2) + ' GB', + uptime: process.uptime().toFixed(2) + 's' + }; +} + +function getProcessInfo() { + const memUsage = process.memoryUsage(); + return { + pid: process.pid, + heapUsed: (memUsage.heapUsed / 1024 / 1024).toFixed(2) + ' MB', + heapTotal: (memUsage.heapTotal / 1024 / 1024).toFixed(2) + ' MB', + external: (memUsage.external / 1024 / 1024).toFixed(2) + ' MB' + }; +} + +function checkDirectories(dirs) { + const status = {}; + dirs.forEach(({ name, path: dirPath }) => { + status[name] = { + exists: fs.existsSync(dirPath), + path: dirPath, + writable: false + }; + + // Check write permissions if directory exists + if (status[name].exists) { + try { + fs.accessSync(dirPath, fs.constants.W_OK); + status[name].writable = true; + } catch (err) { + status[name].writable = false; + } + } + }); + return status; +} + +function getAppVersion() { + try { + const packagePath = path.join(__dirname, '../../package.json'); + if (fs.existsSync(packagePath)) { + const pkg = JSON.parse(fs.readFileSync(packagePath, 'utf8')); + return pkg.version || 'unknown'; + } + } catch (err) { + // Silently fail + } + return 'unknown'; +} + +function logStartupBanner(config = {}) { + const { + appName = 'BREEDR', + port = 3000, + environment = 'development', + dataDir = './data', + uploadPath = './uploads', + staticPath = './static', + dbStatus = 'unknown' + } = config; + + const version = getAppVersion(); + const sysInfo = getSystemInfo(); + const procInfo = getProcessInfo(); + const timestamp = new Date().toISOString(); + + const directories = [ + { name: 'Data', path: dataDir }, + { name: 'Uploads', path: uploadPath }, + { name: 'Static', path: staticPath } + ]; + const dirStatus = checkDirectories(directories); + + // ASCII Banner + console.log('\n'); + console.log('╔══════════════════════════════════════════════════════════╗'); + console.log('║ ║'); + console.log('║ ██████╗ ██████╗ ███████╗███████╗██████╗ ██████╗ ║'); + console.log('║ ██╔══██╗██╔══██╗██╔════╝██╔════╝██╔══██╗██╔══██╗ ║'); + console.log('║ ██████╔╝██████╔╝█████╗ █████╗ ██║ ██║██████╔╝ ║'); + console.log('║ ██╔══██╗██╔══██╗██╔══╝ ██╔══╝ ██║ ██║██╔══██╗ ║'); + console.log('║ ██████╔╝██║ ██║███████╗███████╗██████╔╝██║ ██║ ║'); + console.log('║ ╚═════╝ ╚═╝ ╚═╝╚══════╝╚══════╝╚═════╝ ╚═╝ ╚═╝ ║'); + console.log('║ ║'); + console.log('║ Dog Breeding Genealogy Management System ║'); + console.log('║ ║'); + console.log('╚══════════════════════════════════════════════════════════╝'); + console.log(''); + + // Application Info + console.log('┌─────────────────────────────────────────────────────────┐'); + console.log('│ 📦 APPLICATION INFO │'); + console.log('├─────────────────────────────────────────────────────────┤'); + console.log(`│ Version : ${version.padEnd(40)} │`); + console.log(`│ Environment : ${environment.padEnd(40)} │`); + console.log(`│ Started : ${timestamp.padEnd(40)} │`); + console.log(`│ Node.js : ${sysInfo.nodeVersion.padEnd(40)} │`); + console.log('└─────────────────────────────────────────────────────────┘'); + console.log(''); + + // Server Configuration + console.log('┌─────────────────────────────────────────────────────────┐'); + console.log('│ 🌐 SERVER CONFIGURATION │'); + console.log('├─────────────────────────────────────────────────────────┤'); + console.log(`│ Port : ${String(port).padEnd(40)} │`); + console.log(`│ Access URL : http://localhost:${port}${' '.repeat(27)} │`); + console.log(`│ Database : ${dbStatus.padEnd(40)} │`); + console.log('└─────────────────────────────────────────────────────────┘'); + console.log(''); + + // Directory Status + console.log('┌─────────────────────────────────────────────────────────┐'); + console.log('│ 📁 DIRECTORY STATUS │'); + console.log('├─────────────────────────────────────────────────────────┤'); + Object.entries(dirStatus).forEach(([name, status]) => { + const statusIcon = status.exists ? (status.writable ? '✓' : '⚠') : '✗'; + const statusText = status.exists ? (status.writable ? 'OK' : 'READ-ONLY') : 'MISSING'; + console.log(`│ ${statusIcon} ${name.padEnd(10)} : ${statusText.padEnd(10)} ${status.path.substring(0, 25).padEnd(25)} │`); + }); + console.log('└─────────────────────────────────────────────────────────┘'); + console.log(''); + + // System Resources + console.log('┌─────────────────────────────────────────────────────────┐'); + console.log('│ 💻 SYSTEM RESOURCES │'); + console.log('├─────────────────────────────────────────────────────────┤'); + console.log(`│ Hostname : ${sysInfo.hostname.padEnd(40)} │`); + console.log(`│ Platform : ${sysInfo.platform.padEnd(40)} │`); + console.log(`│ Architecture : ${sysInfo.arch.padEnd(40)} │`); + console.log(`│ CPU Cores : ${String(sysInfo.cpuCores).padEnd(40)} │`); + console.log(`│ Total Memory : ${sysInfo.totalMemory.padEnd(40)} │`); + console.log(`│ Free Memory : ${sysInfo.freeMemory.padEnd(40)} │`); + console.log('└─────────────────────────────────────────────────────────┘'); + console.log(''); + + // Process Info + console.log('┌─────────────────────────────────────────────────────────┐'); + console.log('│ ⚙️ PROCESS INFO │'); + console.log('├─────────────────────────────────────────────────────────┤'); + console.log(`│ PID : ${String(procInfo.pid).padEnd(40)} │`); + console.log(`│ Heap Used : ${procInfo.heapUsed.padEnd(40)} │`); + console.log(`│ Heap Total : ${procInfo.heapTotal.padEnd(40)} │`); + console.log(`│ External : ${procInfo.external.padEnd(40)} │`); + console.log(`│ Uptime : ${sysInfo.uptime.padEnd(40)} │`); + console.log('└─────────────────────────────────────────────────────────┘'); + console.log(''); + + // Ready message + console.log('🚀 Server is ready and listening for connections'); + console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━'); + console.log(''); +} + +module.exports = { + logStartupBanner, + getSystemInfo, + getProcessInfo, + checkDirectories, + getAppVersion +}; \ No newline at end of file -- 2.49.1 From 326bf318a1ac909fc84b4422f5081fdebdd72423 Mon Sep 17 00:00:00 2001 From: jason Date: Tue, 10 Mar 2026 12:56:54 -0500 Subject: [PATCH 2/3] feat: Integrate startup log utility in server initialization --- server/index.js | 37 ++++++++++++++++++++----------------- 1 file changed, 20 insertions(+), 17 deletions(-) diff --git a/server/index.js b/server/index.js index b7c32a4..7d9ea70 100644 --- a/server/index.js +++ b/server/index.js @@ -4,6 +4,7 @@ const helmet = require('helmet'); const path = require('path'); const fs = require('fs'); const { initDatabase } = require('./db/init'); +const { logStartupBanner } = require('./utils/startupLog'); const app = express(); const PORT = process.env.PORT || 3000; @@ -20,22 +21,23 @@ const DATA_DIR = process.env.DATA_DIR || path.join(__dirname, '../data'); // Init DB (path is managed internally by db/init.js) console.log('Initializing database...'); initDatabase(); +const dbStatus = '✓ Connected'; console.log('✓ Database ready!\n'); -// ── Middleware ────────────────────────────────────────────────────────── +// ── Middleware ───────────────────────────────────────────────────────── app.use(helmet({ contentSecurityPolicy: false })); app.use(cors()); app.use(express.json()); app.use(express.urlencoded({ extended: true })); -// ── Static file serving ───────────────────────────────────────────── +// ── Static file serving ────────────────────────────────────────────── app.use('/uploads', express.static(UPLOAD_PATH)); app.use('/static', express.static(STATIC_PATH)); app.use('/uploads', (_req, res) => res.status(404).json({ error: 'Upload not found' })); app.use('/static', (_req, res) => res.status(404).json({ error: 'Static asset not found' })); -// ── API Routes ──────────────────────────────────────────────────────── -app.use('/api/dogs', require('./routes/dogs')); +// ── API Routes ────────────────────────────────────────────────────────── +app.use('/api/dogs', require('./routes/dogs')); app.use('/api/litters', require('./routes/litters')); app.use('/api/health', require('./routes/health')); app.use('/api/genetics', require('./routes/genetics')); @@ -43,16 +45,16 @@ app.use('/api/pedigree', require('./routes/pedigree')); app.use('/api/breeding', require('./routes/breeding')); app.use('/api/settings', require('./routes/settings')); -// ── Production SPA fallback ─────────────────────────────────────────── +// ── Production SPA fallback ──────────────────────────────────────────────── if (process.env.NODE_ENV === 'production') { const clientBuild = path.join(__dirname, '../client/dist'); app.use(express.static(clientBuild)); - app.get(/^(?!\/(?:api|static|uploads)\/).+$/, (_req, res) => { + app.get(/^(?!\/(?: api|static|uploads)\/).*$/, (_req, res) => { res.sendFile(path.join(clientBuild, 'index.html')); }); } -// ── Global error handler ────────────────────────────────────────────── +// ── Global error handler ────────────────────────────────────────────────── app.use((err, _req, res, _next) => { console.error('Error:', err); res.status(err.status || 500).json({ @@ -62,15 +64,16 @@ app.use((err, _req, res, _next) => { }); app.listen(PORT, '0.0.0.0', () => { - console.log(`\n🐕 BREEDR Server Running`); - console.log(`=============================================`); - console.log(`Environment : ${process.env.NODE_ENV || 'development'}`); - console.log(`Port : ${PORT}`); - console.log(`Data dir : ${DATA_DIR}`); - console.log(`Uploads : ${UPLOAD_PATH}`); - console.log(`Static : ${STATIC_PATH}`); - console.log(`Access : http://localhost:${PORT}`); - console.log(`=============================================\n`); + // Display comprehensive startup log + logStartupBanner({ + appName: 'BREEDR', + port: PORT, + environment: process.env.NODE_ENV || 'development', + dataDir: DATA_DIR, + uploadPath: UPLOAD_PATH, + staticPath: STATIC_PATH, + dbStatus: dbStatus + }); }); -module.exports = app; +module.exports = app; \ No newline at end of file -- 2.49.1 From 6e8f747c8a6bfe2f9c93889267515c4e87262544 Mon Sep 17 00:00:00 2001 From: jason Date: Tue, 10 Mar 2026 12:57:32 -0500 Subject: [PATCH 3/3] docs: Add documentation for startup log utility --- server/utils/README.md | 167 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 167 insertions(+) create mode 100644 server/utils/README.md diff --git a/server/utils/README.md b/server/utils/README.md new file mode 100644 index 0000000..0415335 --- /dev/null +++ b/server/utils/README.md @@ -0,0 +1,167 @@ +# Server Utilities + +## Startup Log (`startupLog.js`) + +Comprehensive server startup logging utility that displays system information, configuration, and health checks on application boot. + +### Features + +- **ASCII Banner** - Eye-catching branded header with BREEDR logo +- **Application Info** - Version, environment, timestamp, Node.js version +- **Server Configuration** - Port, access URL, database status +- **Directory Status** - Checks existence and write permissions for data/uploads/static directories +- **System Resources** - Hostname, platform, architecture, CPU, memory +- **Process Info** - PID, heap usage, uptime + +### Usage + +```javascript +const { logStartupBanner } = require('./utils/startupLog'); + +app.listen(PORT, '0.0.0.0', () => { + logStartupBanner({ + appName: 'BREEDR', + port: PORT, + environment: process.env.NODE_ENV || 'development', + dataDir: DATA_DIR, + uploadPath: UPLOAD_PATH, + staticPath: STATIC_PATH, + dbStatus: '✓ Connected' + }); +}); +``` + +### Configuration Options + +| Option | Type | Default | Description | +|--------|------|---------|-------------| +| `appName` | string | `'BREEDR'` | Application name | +| `port` | number | `3000` | Server port | +| `environment` | string | `'development'` | Environment (development/production) | +| `dataDir` | string | `'./data'` | Data directory path | +| `uploadPath` | string | `'./uploads'` | Uploads directory path | +| `staticPath` | string | `'./static'` | Static assets directory path | +| `dbStatus` | string | `'unknown'` | Database connection status | + +### Exported Functions + +#### `logStartupBanner(config)` + +Displays the complete startup banner with all system information. + +**Parameters:** +- `config` (object) - Configuration options (see table above) + +**Returns:** void + +#### `getSystemInfo()` + +Returns system information object. + +**Returns:** +```javascript +{ + hostname: string, + platform: string, + arch: string, + nodeVersion: string, + cpuCores: number, + totalMemory: string, // in GB + freeMemory: string, // in GB + uptime: string // in seconds +} +``` + +#### `getProcessInfo()` + +Returns current process information. + +**Returns:** +```javascript +{ + pid: number, + heapUsed: string, // in MB + heapTotal: string, // in MB + external: string // in MB +} +``` + +#### `checkDirectories(dirs)` + +Checks directory existence and write permissions. + +**Parameters:** +- `dirs` (array) - Array of `{ name, path }` objects + +**Returns:** +```javascript +{ + [name]: { + exists: boolean, + path: string, + writable: boolean + } +} +``` + +#### `getAppVersion()` + +Reads version from package.json. + +**Returns:** string - Version number or 'unknown' + +### Example Output + +``` +╔══════════════════════════════════════════════════════════╗ +║ ║ +║ ██████╗ ██████╗ ███████╗███████╗██████╗ ██████╗ ║ +║ Dog Breeding Genealogy Management System ║ +╚══════════════════════════════════════════════════════════╝ + +┌─────────────────────────────────────────────────────────┐ +│ 📦 APPLICATION INFO │ +├─────────────────────────────────────────────────────────┤ +│ Version : 0.6.0 │ +│ Environment : production │ +│ Node.js : v18.19.0 │ +└─────────────────────────────────────────────────────────┘ + +┌─────────────────────────────────────────────────────────┐ +│ 🌐 SERVER CONFIGURATION │ +├─────────────────────────────────────────────────────────┤ +│ Port : 3000 │ +│ Access URL : http://localhost:3000 │ +│ Database : ✓ Connected │ +└─────────────────────────────────────────────────────────┘ + +🚀 Server is ready and listening for connections +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +``` + +### Benefits + +1. **Instant System Visibility** - See all critical system info at startup +2. **Troubleshooting** - Quickly identify configuration or resource issues +3. **Professional Logging** - Clean, organized output for production environments +4. **Directory Health** - Immediate feedback on filesystem permissions +5. **Resource Monitoring** - Memory and process info at a glance + +### Integration Checklist + +- [x] Create `server/utils/startupLog.js` +- [x] Update `server/index.js` to import and call `logStartupBanner()` +- [x] Replace simple console.log startup with comprehensive banner +- [x] Test in development environment +- [ ] Test in production Docker container +- [ ] Verify all directory checks work correctly +- [ ] Update main README.md if needed + +### Future Enhancements + +- [ ] Add color support using chalk or similar library +- [ ] Log to file option for production environments +- [ ] Add API endpoint status checks +- [ ] Display loaded routes count +- [ ] Show database migration status +- [ ] Add startup time measurement \ No newline at end of file -- 2.49.1