feat/startup-log #39
@@ -4,6 +4,7 @@ const helmet = require('helmet');
|
|||||||
const path = require('path');
|
const path = require('path');
|
||||||
const fs = require('fs');
|
const fs = require('fs');
|
||||||
const { initDatabase } = require('./db/init');
|
const { initDatabase } = require('./db/init');
|
||||||
|
const { logStartupBanner } = require('./utils/startupLog');
|
||||||
|
|
||||||
const app = express();
|
const app = express();
|
||||||
const PORT = process.env.PORT || 3000;
|
const PORT = process.env.PORT || 3000;
|
||||||
@@ -20,21 +21,22 @@ const DATA_DIR = process.env.DATA_DIR || path.join(__dirname, '../data');
|
|||||||
// Init DB (path is managed internally by db/init.js)
|
// Init DB (path is managed internally by db/init.js)
|
||||||
console.log('Initializing database...');
|
console.log('Initializing database...');
|
||||||
initDatabase();
|
initDatabase();
|
||||||
|
const dbStatus = '✓ Connected';
|
||||||
console.log('✓ Database ready!\n');
|
console.log('✓ Database ready!\n');
|
||||||
|
|
||||||
// ── Middleware ──────────────────────────────────────────────────────────
|
// ── Middleware ─────────────────────────────────────────────────────────
|
||||||
app.use(helmet({ contentSecurityPolicy: false }));
|
app.use(helmet({ contentSecurityPolicy: false }));
|
||||||
app.use(cors());
|
app.use(cors());
|
||||||
app.use(express.json());
|
app.use(express.json());
|
||||||
app.use(express.urlencoded({ extended: true }));
|
app.use(express.urlencoded({ extended: true }));
|
||||||
|
|
||||||
// ── Static file serving ─────────────────────────────────────────────
|
// ── Static file serving ──────────────────────────────────────────────
|
||||||
app.use('/uploads', express.static(UPLOAD_PATH));
|
app.use('/uploads', express.static(UPLOAD_PATH));
|
||||||
app.use('/static', express.static(STATIC_PATH));
|
app.use('/static', express.static(STATIC_PATH));
|
||||||
app.use('/uploads', (_req, res) => res.status(404).json({ error: 'Upload not found' }));
|
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' }));
|
app.use('/static', (_req, res) => res.status(404).json({ error: 'Static asset not found' }));
|
||||||
|
|
||||||
// ── API Routes ────────────────────────────────────────────────────────
|
// ── API Routes ──────────────────────────────────────────────────────────
|
||||||
app.use('/api/dogs', require('./routes/dogs'));
|
app.use('/api/dogs', require('./routes/dogs'));
|
||||||
app.use('/api/litters', require('./routes/litters'));
|
app.use('/api/litters', require('./routes/litters'));
|
||||||
app.use('/api/health', require('./routes/health'));
|
app.use('/api/health', require('./routes/health'));
|
||||||
@@ -43,16 +45,16 @@ app.use('/api/pedigree', require('./routes/pedigree'));
|
|||||||
app.use('/api/breeding', require('./routes/breeding'));
|
app.use('/api/breeding', require('./routes/breeding'));
|
||||||
app.use('/api/settings', require('./routes/settings'));
|
app.use('/api/settings', require('./routes/settings'));
|
||||||
|
|
||||||
// ── Production SPA fallback ───────────────────────────────────────────
|
// ── Production SPA fallback ────────────────────────────────────────────────
|
||||||
if (process.env.NODE_ENV === 'production') {
|
if (process.env.NODE_ENV === 'production') {
|
||||||
const clientBuild = path.join(__dirname, '../client/dist');
|
const clientBuild = path.join(__dirname, '../client/dist');
|
||||||
app.use(express.static(clientBuild));
|
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'));
|
res.sendFile(path.join(clientBuild, 'index.html'));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── Global error handler ──────────────────────────────────────────────
|
// ── Global error handler ──────────────────────────────────────────────────
|
||||||
app.use((err, _req, res, _next) => {
|
app.use((err, _req, res, _next) => {
|
||||||
console.error('Error:', err);
|
console.error('Error:', err);
|
||||||
res.status(err.status || 500).json({
|
res.status(err.status || 500).json({
|
||||||
@@ -62,15 +64,16 @@ app.use((err, _req, res, _next) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
app.listen(PORT, '0.0.0.0', () => {
|
app.listen(PORT, '0.0.0.0', () => {
|
||||||
console.log(`\n🐕 BREEDR Server Running`);
|
// Display comprehensive startup log
|
||||||
console.log(`=============================================`);
|
logStartupBanner({
|
||||||
console.log(`Environment : ${process.env.NODE_ENV || 'development'}`);
|
appName: 'BREEDR',
|
||||||
console.log(`Port : ${PORT}`);
|
port: PORT,
|
||||||
console.log(`Data dir : ${DATA_DIR}`);
|
environment: process.env.NODE_ENV || 'development',
|
||||||
console.log(`Uploads : ${UPLOAD_PATH}`);
|
dataDir: DATA_DIR,
|
||||||
console.log(`Static : ${STATIC_PATH}`);
|
uploadPath: UPLOAD_PATH,
|
||||||
console.log(`Access : http://localhost:${PORT}`);
|
staticPath: STATIC_PATH,
|
||||||
console.log(`=============================================\n`);
|
dbStatus: dbStatus
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
module.exports = app;
|
module.exports = app;
|
||||||
167
server/utils/README.md
Normal file
167
server/utils/README.md
Normal file
@@ -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
|
||||||
177
server/utils/startupLog.js
Normal file
177
server/utils/startupLog.js
Normal file
@@ -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
|
||||||
|
};
|
||||||
Reference in New Issue
Block a user