import { getApiLogsByHour } from '../src/apiLogs'; import { createApiLogProvider } from './flexibleApiLogProvider'; import mongoose from 'mongoose'; // User should implement this to fetch data for a given date/hour export async function getDataForHour(date: string, hourRange: string): Promise { console.log(`🔍 Fetching data for ${date} ${hourRange}`); try { // Method 1: Use flexible API log provider (supports both existing and new collections) const provider = createApiLogProvider(); const [startHour] = hourRange.split('-').map(h => parseInt(h)); const parsedDate = new Date(date); const logs = await provider.getApiLogsByHour(parsedDate, startHour); if (logs.length > 0) { console.log(`📊 Found ${logs.length} logs for ${hourRange} from collection "${provider.getCollectionName()}"`); return logs; } // Method 2: Fallback to traditional API logs (for backward compatibility) const apiLogs = await getApiLogsByHour(date, hourRange); if (apiLogs.length > 0) { console.log(`📊 Found ${apiLogs.length} API logs for ${hourRange} (fallback method)`); return apiLogs; } // Method 2: Query custom collection (replace with your actual collection name) const db = mongoose.connection.db; if (db) { // Example collection names - replace with your actual collection const possibleCollections = ['logs', 'app_logs', 'system_logs', 'audit_logs']; for (const collectionName of possibleCollections) { try { const collection = db.collection(collectionName); // Check if collection exists and has documents const count = await collection.countDocuments(); if (count === 0) continue; const [startHour] = hourRange.split('-').map(h => parseInt(h)); // UTC-BASED: Use UTC hours directly without timezone conversion const startDate = new Date(`${date}T${startHour.toString().padStart(2, '0')}:00:00.000Z`); const endDate = new Date(startDate.getTime() + 60 * 60 * 1000); // Try common timestamp field names const timestampFields = ['timestamp', 'createdAt', 'created_at', 'date', 'time']; for (const field of timestampFields) { // First check count to avoid memory issues const totalCount = await collection.countDocuments({ [field]: { $gte: startDate, $lt: endDate } }); if (totalCount > 0) { console.log(`📊 Found ${totalCount} logs for ${date} ${hourRange} from collection UTC "${collectionName}"`); // 🚨 EXTREME MEMORY PROTECTION for server restart prevention const MEMORY_SAFE_LIMIT = totalCount > 10000 ? 50 : totalCount > 1000 ? 100 : 200; if (totalCount > 1000) { console.log(`🚨 EXTREME DATASET DETECTED (${totalCount} logs). Using ultra-safe limit: ${MEMORY_SAFE_LIMIT}`); console.log(`🛡️ Server restart protection: MAXIMUM SAFETY MODE activated`); } else if (totalCount > MEMORY_SAFE_LIMIT) { console.log(`⚠️ Large dataset detected (${totalCount} logs). Limiting to ${MEMORY_SAFE_LIMIT} for memory safety`); console.log(`🛡️ Server restart protection: Processing oldest ${MEMORY_SAFE_LIMIT} logs only (UTC-based)`); } // 🌊 STREAMING APPROACH - Never use .toArray()! const logs: any[] = []; const cursor = collection.find({ [field]: { $gte: startDate, $lt: endDate } }) .sort({ [field]: 1 }) // Get oldest logs first (ASCENDING ORDER) .limit(Math.min(totalCount, MEMORY_SAFE_LIMIT)); // Memory-safe limit let processedCount = 0; const memUsageInitial = process.memoryUsage(); const memInitialMB = Math.round(memUsageInitial.heapUsed / 1024 / 1024); console.log(`🌊 Starting streaming cursor processing. Initial memory: ${memInitialMB}MB`); try { // Process documents one by one to prevent memory overflow // Use for await loop instead of forEach for proper break control for await (const doc of cursor) { logs.push(doc); processedCount++; // Memory check every 100 documents (reduced logging for performance) if (processedCount % 100 === 0) { const memUsage = process.memoryUsage(); const memUsedMB = Math.round(memUsage.heapUsed / 1024 / 1024); console.log(`🌊 Processed ${processedCount}/${Math.min(totalCount, MEMORY_SAFE_LIMIT)} logs, Memory: ${memUsedMB}MB`); // EMERGENCY BRAKE: If memory exceeds safe threshold, stop processing if (memUsedMB > (memInitialMB + 150)) { // Allow max 150MB increase console.log(`🚨 MEMORY EMERGENCY BRAKE: Stopping at ${processedCount} logs (Memory: ${memUsedMB}MB)`); break; // Properly break the loop } } } } catch (error) { console.error(`❌ Streaming cursor error at document ${processedCount}:`, error); } finally { // Ensure cursor is closed await cursor.close(); } if (logs.length > 0) { const finalMemUsage = process.memoryUsage(); const finalMemMB = Math.round(finalMemUsage.heapUsed / 1024 / 1024); console.log(`📋 Streaming completed: ${logs.length}/${totalCount} logs from '${collectionName}', Memory: ${finalMemMB}MB`); return logs; } } } } catch (err) { // Continue to next collection if this one fails continue; } } } // Method 3: Fallback - return sample data for testing console.log(`⚠️ No logs found, generating sample data for ${hourRange}`); return generateSampleData(date, hourRange); } catch (error) { console.error(`❌ Error fetching data for ${date} ${hourRange}:`, error); return generateSampleData(date, hourRange); } } function generateSampleData(date: string, hourRange: string) { const [startHour] = hourRange.split('-').map(h => parseInt(h)); const sampleData = []; // Generate 3-10 sample log entries for this hour const numLogs = Math.floor(Math.random() * 8) + 3; for (let i = 0; i < numLogs; i++) { const timestamp = new Date(`${date}T${startHour.toString().padStart(2, '0')}:${Math.floor(Math.random() * 60).toString().padStart(2, '0')}:00.000Z`); sampleData.push({ id: Math.random().toString(36).substr(2, 9), timestamp, message: `Sample log entry ${i + 1} for hour ${hourRange}`, level: ['info', 'warn', 'error', 'debug'][Math.floor(Math.random() * 4)], source: 'sample-data-generator', data: { userId: Math.floor(Math.random() * 1000), action: ['login', 'logout', 'view_page', 'api_call'][Math.floor(Math.random() * 4)], ip: `192.168.1.${Math.floor(Math.random() * 255)}`, } }); } console.log(`🔄 Generated ${sampleData.length} sample log entries for ${hourRange}`); return sampleData; }