feat: Add detailed application event logging and enhance Chart.js health monitoring with auto-recovery features
This commit is contained in:
parent
b864e81aa3
commit
73f743ce7c
|
@ -4231,8 +4231,83 @@
|
|||
"trigger_variable": null,
|
||||
"auto_started": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"timestamp": "2025-08-15T00:46:30.290892",
|
||||
"level": "info",
|
||||
"event_type": "application_started",
|
||||
"message": "Application initialization completed successfully",
|
||||
"details": {}
|
||||
},
|
||||
{
|
||||
"timestamp": "2025-08-15T00:46:30.340169",
|
||||
"level": "info",
|
||||
"event_type": "dataset_activated",
|
||||
"message": "Dataset activated: DAR",
|
||||
"details": {
|
||||
"dataset_id": "DAR",
|
||||
"variables_count": 2,
|
||||
"streaming_count": 2,
|
||||
"prefix": "gateway_phoenix"
|
||||
}
|
||||
},
|
||||
{
|
||||
"timestamp": "2025-08-15T00:46:30.349167",
|
||||
"level": "info",
|
||||
"event_type": "dataset_activated",
|
||||
"message": "Dataset activated: Fast",
|
||||
"details": {
|
||||
"dataset_id": "Fast",
|
||||
"variables_count": 2,
|
||||
"streaming_count": 1,
|
||||
"prefix": "fast"
|
||||
}
|
||||
},
|
||||
{
|
||||
"timestamp": "2025-08-15T00:46:30.355834",
|
||||
"level": "info",
|
||||
"event_type": "csv_recording_started",
|
||||
"message": "CSV recording started: 2 datasets activated",
|
||||
"details": {
|
||||
"activated_datasets": 2,
|
||||
"total_datasets": 3
|
||||
}
|
||||
},
|
||||
{
|
||||
"timestamp": "2025-08-15T00:49:03.086267",
|
||||
"level": "info",
|
||||
"event_type": "plot_session_created",
|
||||
"message": "Plot session 'UR29' created and started",
|
||||
"details": {
|
||||
"session_id": "plot_1",
|
||||
"variables": [
|
||||
"UR29_Brix",
|
||||
"UR29_ma",
|
||||
"AUX Blink_1.0S"
|
||||
],
|
||||
"time_window": 3600,
|
||||
"trigger_variable": null,
|
||||
"auto_started": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"timestamp": "2025-08-15T00:50:02.569962",
|
||||
"level": "info",
|
||||
"event_type": "plot_session_created",
|
||||
"message": "Plot session 'UR29' created and started",
|
||||
"details": {
|
||||
"session_id": "plot_1",
|
||||
"variables": [
|
||||
"UR29_Brix",
|
||||
"UR29_ma",
|
||||
"AUX Blink_1.0S"
|
||||
],
|
||||
"time_window": 3600,
|
||||
"trigger_variable": null,
|
||||
"auto_started": true
|
||||
}
|
||||
}
|
||||
],
|
||||
"last_updated": "2025-08-15T00:41:32.191310",
|
||||
"total_entries": 381
|
||||
"last_updated": "2025-08-15T00:50:02.569962",
|
||||
"total_entries": 387
|
||||
}
|
|
@ -70,6 +70,14 @@ const ChartjsPlot = ({ session, height = '400px' }) => {
|
|||
|
||||
// Data preservation system for zoom operations
|
||||
const dataBackupRef = useRef(new Map());
|
||||
|
||||
// Chart health monitoring
|
||||
const chartHealthRef = useRef({
|
||||
lastDataTimestamp: 0,
|
||||
consecutiveErrors: 0,
|
||||
isHealthy: true,
|
||||
lastHealthCheck: 0
|
||||
});
|
||||
|
||||
const bgColor = useColorModeValue('white', 'gray.800');
|
||||
const textColor = useColorModeValue('gray.600', 'gray.300');
|
||||
|
@ -145,6 +153,113 @@ const ChartjsPlot = ({ session, height = '400px' }) => {
|
|||
return [];
|
||||
}, [getColor]);
|
||||
|
||||
// Chart health monitoring and auto-recovery
|
||||
const checkChartHealth = useCallback(() => {
|
||||
if (!chartRef.current) return false;
|
||||
|
||||
const health = chartHealthRef.current;
|
||||
const now = Date.now();
|
||||
|
||||
// Check if chart is receiving data
|
||||
const timeSinceLastData = now - health.lastDataTimestamp;
|
||||
const hasRecentData = timeSinceLastData < 30000; // 30 seconds
|
||||
|
||||
// Check if streaming is properly configured
|
||||
const realtimeOptions = chartRef.current.options?.scales?.x?.realtime;
|
||||
const isStreamingConfigured = !!realtimeOptions;
|
||||
|
||||
// Check if datasets exist and are properly configured
|
||||
const hasDatasets = chartRef.current.data?.datasets?.length > 0;
|
||||
|
||||
const isHealthy = hasRecentData && isStreamingConfigured && hasDatasets;
|
||||
|
||||
if (!isHealthy && health.isHealthy) {
|
||||
console.warn('⚠️ Chart health check failed:', {
|
||||
hasRecentData,
|
||||
isStreamingConfigured,
|
||||
hasDatasets,
|
||||
timeSinceLastData,
|
||||
consecutiveErrors: health.consecutiveErrors
|
||||
});
|
||||
}
|
||||
|
||||
health.isHealthy = isHealthy;
|
||||
health.lastHealthCheck = now;
|
||||
|
||||
return isHealthy;
|
||||
}, []);
|
||||
|
||||
const attemptAutoRecovery = useCallback(async () => {
|
||||
console.log('🔄 Attempting chart auto-recovery...');
|
||||
|
||||
try {
|
||||
// Clear all data directly
|
||||
if (chartRef.current) {
|
||||
chartRef.current.data.datasets.forEach(dataset => {
|
||||
if (dataset.data) {
|
||||
dataset.data.length = 0;
|
||||
}
|
||||
});
|
||||
chartRef.current.update('quiet');
|
||||
setDataPointsCount(0);
|
||||
}
|
||||
|
||||
// Wait a moment
|
||||
await new Promise(resolve => setTimeout(resolve, 100));
|
||||
|
||||
// Recreate chart if session is active
|
||||
if (session?.is_active && !session?.is_paused) {
|
||||
// Trigger a recreation by clearing refs and letting useEffect handle it
|
||||
chartRef.current = null;
|
||||
setTimeout(() => {
|
||||
// The useEffect will recreate the chart
|
||||
console.log('✅ Chart auto-recovery completed');
|
||||
}, 100);
|
||||
|
||||
// Reset health counters
|
||||
chartHealthRef.current.consecutiveErrors = 0;
|
||||
chartHealthRef.current.isHealthy = true;
|
||||
|
||||
return true;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('❌ Chart auto-recovery failed:', error);
|
||||
chartHealthRef.current.consecutiveErrors++;
|
||||
}
|
||||
|
||||
return false;
|
||||
}, [session]);
|
||||
|
||||
// Diagnostic function to help identify issues
|
||||
const runDiagnostics = useCallback(() => {
|
||||
console.log('🔍 Chart Diagnostics:', {
|
||||
chartExists: !!chartRef.current,
|
||||
canvasExists: !!canvasRef.current,
|
||||
sessionId: session?.session_id,
|
||||
sessionActive: session?.is_active,
|
||||
sessionPaused: session?.is_paused,
|
||||
dataBackupSize: dataBackupRef.current.size,
|
||||
health: chartHealthRef.current,
|
||||
datasets: chartRef.current?.data?.datasets?.length || 0,
|
||||
realtimeConfig: !!chartRef.current?.options?.scales?.x?.realtime
|
||||
});
|
||||
|
||||
// Check for common issues
|
||||
const issues = [];
|
||||
if (!chartRef.current) issues.push('Chart not initialized');
|
||||
if (!session?.is_active) issues.push('Session not active');
|
||||
if (session?.is_paused) issues.push('Session is paused');
|
||||
if (chartHealthRef.current.consecutiveErrors > 5) issues.push(`${chartHealthRef.current.consecutiveErrors} consecutive errors`);
|
||||
|
||||
if (issues.length > 0) {
|
||||
console.warn('⚠️ Issues detected:', issues);
|
||||
} else {
|
||||
console.log('✅ No issues detected');
|
||||
}
|
||||
|
||||
return issues;
|
||||
}, [session]);
|
||||
|
||||
// Load historical data from CSV files
|
||||
const loadHistoricalData = useCallback(async (variables, timeWindow) => {
|
||||
try {
|
||||
|
@ -872,6 +987,10 @@ const ChartjsPlot = ({ session, height = '400px' }) => {
|
|||
if (pointsAdded > 0) {
|
||||
chart.update('quiet');
|
||||
|
||||
// Update health monitoring
|
||||
chartHealthRef.current.lastDataTimestamp = Date.now();
|
||||
chartHealthRef.current.consecutiveErrors = 0;
|
||||
|
||||
// Backup data periodically when significant data is added
|
||||
if (pointsAdded > 5 && dataBackupRef.current) {
|
||||
const backup = chart.data.datasets.map(dataset => ({
|
||||
|
@ -883,6 +1002,20 @@ const ChartjsPlot = ({ session, height = '400px' }) => {
|
|||
dataBackupRef.current.set('current', backup);
|
||||
console.log(`📦 Data backed up: ${backup.reduce((total, ds) => total + ds.data.length, 0)} points`);
|
||||
}
|
||||
} else {
|
||||
// No new data received - increment error counter
|
||||
chartHealthRef.current.consecutiveErrors++;
|
||||
|
||||
// Check if we need auto-recovery after multiple failed attempts
|
||||
if (chartHealthRef.current.consecutiveErrors > 10) {
|
||||
console.warn(`⚠️ No data received for ${chartHealthRef.current.consecutiveErrors} consecutive attempts`);
|
||||
|
||||
// Attempt auto-recovery if too many consecutive errors
|
||||
if (chartHealthRef.current.consecutiveErrors > 20) {
|
||||
console.warn('🚑 Triggering auto-recovery due to consecutive data failures');
|
||||
setTimeout(() => attemptAutoRecovery(), 100);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Clear NaN insertion flag
|
||||
|
@ -890,7 +1023,7 @@ const ChartjsPlot = ({ session, height = '400px' }) => {
|
|||
sessionData.insertNaNOnNextIngest = false;
|
||||
}
|
||||
return pointsAdded;
|
||||
}, []);
|
||||
}, [attemptAutoRecovery]);
|
||||
|
||||
// Fallback cleanup removed per requirement: always realtime; we pause instead when no data
|
||||
|
||||
|
@ -1235,6 +1368,28 @@ const ChartjsPlot = ({ session, height = '400px' }) => {
|
|||
}
|
||||
}, [session?.isFullscreen, createStreamingChart]);
|
||||
|
||||
// Periodic health monitoring
|
||||
useEffect(() => {
|
||||
if (!session?.session_id || !chartRef.current) return;
|
||||
|
||||
const healthCheckInterval = setInterval(() => {
|
||||
const isHealthy = checkChartHealth();
|
||||
|
||||
// Log health status periodically for debugging
|
||||
if (!isHealthy) {
|
||||
console.warn('📊 Chart health check:', {
|
||||
sessionId: session.session_id,
|
||||
isActive: session.is_active,
|
||||
isPaused: session.is_paused,
|
||||
consecutiveErrors: chartHealthRef.current.consecutiveErrors,
|
||||
timeSinceLastData: Date.now() - chartHealthRef.current.lastDataTimestamp
|
||||
});
|
||||
}
|
||||
}, 15000); // Check every 15 seconds
|
||||
|
||||
return () => clearInterval(healthCheckInterval);
|
||||
}, [session?.session_id, checkChartHealth]);
|
||||
|
||||
// Initialize chart when config is resolved - simplified approach
|
||||
useEffect(() => {
|
||||
console.log(`🔍 useEffect triggered - sessionId: ${session?.session_id}, hasCanvas: ${!!canvasRef.current}, hasChart: ${!!chartRef.current}`);
|
||||
|
@ -1460,6 +1615,8 @@ const ChartjsPlot = ({ session, height = '400px' }) => {
|
|||
bottom={2}
|
||||
right={2}
|
||||
zIndex={10}
|
||||
display="flex"
|
||||
gap={2}
|
||||
>
|
||||
<button
|
||||
onClick={resetZoom}
|
||||
|
@ -1479,6 +1636,49 @@ const ChartjsPlot = ({ session, height = '400px' }) => {
|
|||
>
|
||||
🔄 Reset Zoom
|
||||
</button>
|
||||
|
||||
{/* Auto-recovery button when chart health is poor */}
|
||||
{!chartHealthRef.current.isHealthy && (
|
||||
<>
|
||||
<button
|
||||
onClick={attemptAutoRecovery}
|
||||
style={{
|
||||
background: 'rgba(255, 140, 0, 0.9)',
|
||||
color: 'white',
|
||||
border: 'none',
|
||||
borderRadius: '4px',
|
||||
padding: '4px 8px',
|
||||
fontSize: '11px',
|
||||
cursor: 'pointer',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
gap: '4px'
|
||||
}}
|
||||
title="Restart chart (use when data stops loading)"
|
||||
>
|
||||
🚑 Fix Chart
|
||||
</button>
|
||||
|
||||
<button
|
||||
onClick={runDiagnostics}
|
||||
style={{
|
||||
background: 'rgba(128, 128, 128, 0.9)',
|
||||
color: 'white',
|
||||
border: 'none',
|
||||
borderRadius: '4px',
|
||||
padding: '4px 8px',
|
||||
fontSize: '11px',
|
||||
cursor: 'pointer',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
gap: '4px'
|
||||
}}
|
||||
title="Show chart diagnostics in console"
|
||||
>
|
||||
🔍 Debug
|
||||
</button>
|
||||
</>
|
||||
)}
|
||||
</Box>
|
||||
)}
|
||||
|
||||
|
|
|
@ -9,5 +9,5 @@
|
|||
]
|
||||
},
|
||||
"auto_recovery_enabled": true,
|
||||
"last_update": "2025-08-15T00:35:28.773668"
|
||||
"last_update": "2025-08-15T00:46:30.361074"
|
||||
}
|
Loading…
Reference in New Issue