this repo has no description

Add logging and log rotation

- Add file-based logger (data/app.log) with JSON metadata support
- Add database cleanup script (hourly via cron)
- Removes OAuth states older than 1 hour
- Removes sessions older than 30 days
- Vacuums SQLite database
- Configure logrotate for app.log and cleanup.log
- Daily rotation, 14 days retention
- Compression enabled

Co-authored-by: Shelley <shelley@exe.dev>

+91
+11
logrotate.conf
··· 1 + /home/exedev/stdeditor/data/app.log /home/exedev/stdeditor/data/cleanup.log { 2 + daily 3 + rotate 14 4 + compress 5 + delaycompress 6 + missingok 7 + notifempty 8 + create 640 exedev exedev 9 + dateext 10 + dateformat -%Y%m%d 11 + }
+37
scripts/cleanup.ts
··· 1 + #!/usr/bin/env bun 2 + /** 3 + * Database cleanup script 4 + * Removes expired OAuth states and optionally old sessions 5 + * Run via cron: 0 * * * * /home/exedev/.bun/bin/bun /home/exedev/stdeditor/scripts/cleanup.ts 6 + */ 7 + 8 + import { Database } from 'bun:sqlite'; 9 + import * as path from 'path'; 10 + 11 + const DATA_DIR = process.env.DATA_DIR || './data'; 12 + const DB_PATH = path.join(DATA_DIR, 'oauth.db'); 13 + 14 + try { 15 + const db = new Database(DB_PATH); 16 + 17 + // Clean up OAuth states older than 1 hour 18 + const statesResult = db.run( 19 + `DELETE FROM oauth_states WHERE created_at < strftime('%s', 'now') - 3600` 20 + ); 21 + 22 + // Clean up sessions older than 30 days (optional - sessions may still be valid) 23 + const sessionsResult = db.run( 24 + `DELETE FROM oauth_sessions WHERE updated_at < strftime('%s', 'now') - 2592000` 25 + ); 26 + 27 + // Vacuum the database to reclaim space 28 + db.run('VACUUM'); 29 + 30 + const timestamp = new Date().toISOString(); 31 + console.log(`[${timestamp}] Cleanup complete: removed old states and sessions, vacuumed database`); 32 + 33 + db.close(); 34 + } catch (error) { 35 + console.error('Cleanup failed:', error); 36 + process.exit(1); 37 + }
+43
src/lib/logger.ts
··· 1 + import * as fs from 'fs'; 2 + import * as path from 'path'; 3 + 4 + const DATA_DIR = process.env.DATA_DIR || './data'; 5 + const LOG_PATH = path.join(DATA_DIR, 'app.log'); 6 + 7 + type LogLevel = 'INFO' | 'WARN' | 'ERROR' | 'DEBUG'; 8 + 9 + function formatDate(date: Date): string { 10 + return date.toISOString(); 11 + } 12 + 13 + function writeLog(level: LogLevel, message: string, meta?: Record<string, any>) { 14 + const timestamp = formatDate(new Date()); 15 + const metaStr = meta ? ` ${JSON.stringify(meta)}` : ''; 16 + const logLine = `[${timestamp}] ${level}: ${message}${metaStr}\n`; 17 + 18 + // Write to file 19 + try { 20 + fs.appendFileSync(LOG_PATH, logLine); 21 + } catch (err) { 22 + // Fall back to console if file write fails 23 + console.error('Failed to write to log file:', err); 24 + } 25 + 26 + // Also write to stdout/stderr for systemd journal 27 + if (level === 'ERROR') { 28 + process.stderr.write(logLine); 29 + } else { 30 + process.stdout.write(logLine); 31 + } 32 + } 33 + 34 + export const logger = { 35 + info: (message: string, meta?: Record<string, any>) => writeLog('INFO', message, meta), 36 + warn: (message: string, meta?: Record<string, any>) => writeLog('WARN', message, meta), 37 + error: (message: string, meta?: Record<string, any>) => writeLog('ERROR', message, meta), 38 + debug: (message: string, meta?: Record<string, any>) => { 39 + if (process.env.DEBUG) { 40 + writeLog('DEBUG', message, meta); 41 + } 42 + }, 43 + };