feat: Add detailed application event logging and enhance Chart.js health monitoring with auto-recovery features

This commit is contained in:
Miguel 2025-08-15 00:51:18 +02:00
parent b864e81aa3
commit 73f743ce7c
3 changed files with 279 additions and 4 deletions

View File

@ -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
}

View File

@ -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>
)}

View File

@ -9,5 +9,5 @@
]
},
"auto_recovery_enabled": true,
"last_update": "2025-08-15T00:35:28.773668"
"last_update": "2025-08-15T00:46:30.361074"
}