package scanner import ( "encoding/json" "fmt" "sync" "sync/atomic" "time" "github.com/ethereum/go-ethereum/common" "github.com/fraktal/mev-beta/internal/logger" ) // ParsingMonitor tracks parsing success rates and performance metrics type ParsingMonitor struct { logger *logger.Logger mutex sync.RWMutex // Parsing statistics stats struct { totalTransactions atomic.Int64 dexTransactions atomic.Int64 successfulParsing atomic.Int64 failedParsing atomic.Int64 zeroAddressRejected atomic.Int64 suspiciousRejected atomic.Int64 duplicateRejected atomic.Int64 // Protocol-specific stats uniswapV3Parsed atomic.Int64 uniswapV2Parsed atomic.Int64 multicallParsed atomic.Int64 universalRouterParsed atomic.Int64 // Protocol-specific errors uniswapV3Errors atomic.Int64 uniswapV2Errors atomic.Int64 multicallErrors atomic.Int64 universalRouterErrors atomic.Int64 } // Time-based metrics hourlyMetrics map[int]*HourlyParsingMetrics dailyMetrics map[string]*DailyParsingMetrics realTimeMetrics *RealTimeParsingMetrics // Configuration config *ParsingMonitorConfig // Start time for uptime calculation startTime time.Time } // ParsingMonitorConfig configures the parsing monitor type ParsingMonitorConfig struct { EnableRealTimeMonitoring bool `json:"enable_real_time_monitoring"` MetricsRetentionHours int `json:"metrics_retention_hours"` AlertThresholds AlertThresholds `json:"alert_thresholds"` ReportInterval time.Duration `json:"report_interval"` } // AlertThresholds defines when to trigger parsing alerts type AlertThresholds struct { MinSuccessRatePercent float64 `json:"min_success_rate_percent"` MaxZeroAddressRatePercent float64 `json:"max_zero_address_rate_percent"` MaxErrorRatePercent float64 `json:"max_error_rate_percent"` MinTransactionsPerHour int64 `json:"min_transactions_per_hour"` } // HourlyParsingMetrics tracks metrics for a specific hour type HourlyParsingMetrics struct { Hour int `json:"hour"` Date string `json:"date"` TotalTransactions int64 `json:"total_transactions"` DexTransactions int64 `json:"dex_transactions"` SuccessfulParsing int64 `json:"successful_parsing"` FailedParsing int64 `json:"failed_parsing"` SuccessRate float64 `json:"success_rate"` ZeroAddressRejected int64 `json:"zero_address_rejected"` SuspiciousRejected int64 `json:"suspicious_rejected"` // Protocol breakdown ProtocolStats map[string]ProtocolMetrics `json:"protocol_stats"` Timestamp time.Time `json:"timestamp"` } // DailyParsingMetrics tracks metrics for a specific day type DailyParsingMetrics struct { Date string `json:"date"` TotalTransactions int64 `json:"total_transactions"` DexTransactions int64 `json:"dex_transactions"` SuccessfulParsing int64 `json:"successful_parsing"` FailedParsing int64 `json:"failed_parsing"` SuccessRate float64 `json:"success_rate"` ZeroAddressRejected int64 `json:"zero_address_rejected"` SuspiciousRejected int64 `json:"suspicious_rejected"` // Protocol breakdown ProtocolStats map[string]ProtocolMetrics `json:"protocol_stats"` // Hourly breakdown HourlyBreakdown [24]*HourlyParsingMetrics `json:"hourly_breakdown"` Timestamp time.Time `json:"timestamp"` } // RealTimeParsingMetrics tracks real-time parsing performance type RealTimeParsingMetrics struct { LastUpdateTime time.Time `json:"last_update_time"` ParsesPerSecond float64 `json:"parses_per_second"` SuccessRatePercent float64 `json:"success_rate_percent"` ErrorRatePercent float64 `json:"error_rate_percent"` ZeroAddressRatePercent float64 `json:"zero_address_rate_percent"` // Recent activity (last 5 minutes) RecentSuccesses int64 `json:"recent_successes"` RecentFailures int64 `json:"recent_failures"` RecentZeroAddresses int64 `json:"recent_zero_addresses"` // Protocol health ProtocolHealth map[string]ProtocolHealth `json:"protocol_health"` } // ProtocolMetrics tracks parsing metrics for a specific protocol type ProtocolMetrics struct { Protocol string `json:"protocol"` TotalParsed int64 `json:"total_parsed"` Errors int64 `json:"errors"` SuccessRate float64 `json:"success_rate"` AverageParseTimeMs float64 `json:"average_parse_time_ms"` LastParseTime time.Time `json:"last_parse_time"` } // ProtocolHealth tracks real-time health of a protocol type ProtocolHealth struct { Protocol string `json:"protocol"` Status string `json:"status"` // "healthy", "degraded", "critical" SuccessRate float64 `json:"success_rate"` ErrorRate float64 `json:"error_rate"` LastSuccessTime time.Time `json:"last_success_time"` LastErrorTime time.Time `json:"last_error_time"` ConsecutiveErrors int `json:"consecutive_errors"` } // ParsingEvent represents a parsing event for monitoring type ParsingEvent struct { TransactionHash common.Hash `json:"transaction_hash"` Protocol string `json:"protocol"` Success bool `json:"success"` Error string `json:"error,omitempty"` RejectionReason string `json:"rejection_reason,omitempty"` PoolAddress common.Address `json:"pool_address"` Token0 common.Address `json:"token0"` Token1 common.Address `json:"token1"` ParseTimeMs float64 `json:"parse_time_ms"` Timestamp time.Time `json:"timestamp"` } // NewParsingMonitor creates a new parsing monitor func NewParsingMonitor(logger *logger.Logger, config *ParsingMonitorConfig) *ParsingMonitor { if config == nil { config = &ParsingMonitorConfig{ EnableRealTimeMonitoring: true, MetricsRetentionHours: 72, // 3 days AlertThresholds: AlertThresholds{ MinSuccessRatePercent: 80.0, MaxZeroAddressRatePercent: 5.0, MaxErrorRatePercent: 15.0, MinTransactionsPerHour: 100, }, ReportInterval: 5 * time.Minute, } } monitor := &ParsingMonitor{ logger: logger, hourlyMetrics: make(map[int]*HourlyParsingMetrics), dailyMetrics: make(map[string]*DailyParsingMetrics), config: config, startTime: time.Now(), realTimeMetrics: &RealTimeParsingMetrics{ ProtocolHealth: make(map[string]ProtocolHealth), }, } // Start background monitoring if config.EnableRealTimeMonitoring { go monitor.startRealTimeMonitoring() go monitor.startPeriodicReporting() } return monitor } // RecordParsingEvent records a parsing event func (pm *ParsingMonitor) RecordParsingEvent(event ParsingEvent) { pm.stats.totalTransactions.Add(1) if event.Success { pm.stats.successfulParsing.Add(1) pm.stats.dexTransactions.Add(1) // Update protocol-specific success stats switch event.Protocol { case "UniswapV3": pm.stats.uniswapV3Parsed.Add(1) case "UniswapV2": pm.stats.uniswapV2Parsed.Add(1) case "Multicall": pm.stats.multicallParsed.Add(1) case "UniversalRouter": pm.stats.universalRouterParsed.Add(1) } } else { pm.stats.failedParsing.Add(1) // Categorize rejection reasons switch event.RejectionReason { case "zero_address": pm.stats.zeroAddressRejected.Add(1) case "suspicious_address": pm.stats.suspiciousRejected.Add(1) case "duplicate_address": pm.stats.duplicateRejected.Add(1) } // Update protocol-specific error stats switch event.Protocol { case "UniswapV3": pm.stats.uniswapV3Errors.Add(1) case "UniswapV2": pm.stats.uniswapV2Errors.Add(1) case "Multicall": pm.stats.multicallErrors.Add(1) case "UniversalRouter": pm.stats.universalRouterErrors.Add(1) } } // Update time-based metrics pm.updateTimeBasedMetrics(event) } // RecordTransactionProcessed records that a transaction was processed func (pm *ParsingMonitor) RecordTransactionProcessed() { pm.stats.totalTransactions.Add(1) } // RecordDEXTransactionFound records that a DEX transaction was found func (pm *ParsingMonitor) RecordDEXTransactionFound() { pm.stats.dexTransactions.Add(1) } // RecordParsingSuccess records a successful parsing func (pm *ParsingMonitor) RecordParsingSuccess(protocol string) { pm.stats.successfulParsing.Add(1) switch protocol { case "UniswapV3": pm.stats.uniswapV3Parsed.Add(1) case "UniswapV2": pm.stats.uniswapV2Parsed.Add(1) case "Multicall": pm.stats.multicallParsed.Add(1) case "UniversalRouter": pm.stats.universalRouterParsed.Add(1) } } // RecordParsingFailure records a parsing failure func (pm *ParsingMonitor) RecordParsingFailure(protocol, reason string) { pm.stats.failedParsing.Add(1) switch reason { case "zero_address": pm.stats.zeroAddressRejected.Add(1) case "suspicious_address": pm.stats.suspiciousRejected.Add(1) case "duplicate_address": pm.stats.duplicateRejected.Add(1) } switch protocol { case "UniswapV3": pm.stats.uniswapV3Errors.Add(1) case "UniswapV2": pm.stats.uniswapV2Errors.Add(1) case "Multicall": pm.stats.multicallErrors.Add(1) case "UniversalRouter": pm.stats.universalRouterErrors.Add(1) } } // GetCurrentStats returns current parsing statistics func (pm *ParsingMonitor) GetCurrentStats() map[string]interface{} { totalTx := pm.stats.totalTransactions.Load() dexTx := pm.stats.dexTransactions.Load() successfulParsing := pm.stats.successfulParsing.Load() failedParsing := pm.stats.failedParsing.Load() var successRate, dexDetectionRate float64 if totalTx > 0 { if successfulParsing+failedParsing > 0 { successRate = float64(successfulParsing) / float64(successfulParsing+failedParsing) * 100 } dexDetectionRate = float64(dexTx) / float64(totalTx) * 100 } return map[string]interface{}{ "total_transactions": totalTx, "dex_transactions": dexTx, "successful_parsing": successfulParsing, "failed_parsing": failedParsing, "success_rate_percent": successRate, "dex_detection_rate_percent": dexDetectionRate, "zero_address_rejected": pm.stats.zeroAddressRejected.Load(), "suspicious_rejected": pm.stats.suspiciousRejected.Load(), "duplicate_rejected": pm.stats.duplicateRejected.Load(), "uptime_hours": time.Since(pm.startTime).Hours(), "protocol_stats": map[string]interface{}{ "uniswap_v3": map[string]interface{}{ "parsed": pm.stats.uniswapV3Parsed.Load(), "errors": pm.stats.uniswapV3Errors.Load(), }, "uniswap_v2": map[string]interface{}{ "parsed": pm.stats.uniswapV2Parsed.Load(), "errors": pm.stats.uniswapV2Errors.Load(), }, "multicall": map[string]interface{}{ "parsed": pm.stats.multicallParsed.Load(), "errors": pm.stats.multicallErrors.Load(), }, "universal_router": map[string]interface{}{ "parsed": pm.stats.universalRouterParsed.Load(), "errors": pm.stats.universalRouterErrors.Load(), }, }, } } // updateTimeBasedMetrics updates hourly and daily metrics func (pm *ParsingMonitor) updateTimeBasedMetrics(event ParsingEvent) { now := time.Now() hour := now.Hour() date := now.Format("2006-01-02") pm.mutex.Lock() defer pm.mutex.Unlock() // Update hourly metrics if _, exists := pm.hourlyMetrics[hour]; !exists { pm.hourlyMetrics[hour] = &HourlyParsingMetrics{ Hour: hour, Date: date, ProtocolStats: make(map[string]ProtocolMetrics), Timestamp: now, } } hourlyMetric := pm.hourlyMetrics[hour] hourlyMetric.TotalTransactions++ if event.Success { hourlyMetric.SuccessfulParsing++ hourlyMetric.DexTransactions++ } else { hourlyMetric.FailedParsing++ if event.RejectionReason == "zero_address" { hourlyMetric.ZeroAddressRejected++ } else if event.RejectionReason == "suspicious_address" { hourlyMetric.SuspiciousRejected++ } } if hourlyMetric.SuccessfulParsing+hourlyMetric.FailedParsing > 0 { hourlyMetric.SuccessRate = float64(hourlyMetric.SuccessfulParsing) / float64(hourlyMetric.SuccessfulParsing+hourlyMetric.FailedParsing) * 100 } // Update daily metrics if _, exists := pm.dailyMetrics[date]; !exists { pm.dailyMetrics[date] = &DailyParsingMetrics{ Date: date, ProtocolStats: make(map[string]ProtocolMetrics), Timestamp: now, } } dailyMetric := pm.dailyMetrics[date] dailyMetric.TotalTransactions++ if event.Success { dailyMetric.SuccessfulParsing++ dailyMetric.DexTransactions++ } else { dailyMetric.FailedParsing++ if event.RejectionReason == "zero_address" { dailyMetric.ZeroAddressRejected++ } else if event.RejectionReason == "suspicious_address" { dailyMetric.SuspiciousRejected++ } } if dailyMetric.SuccessfulParsing+dailyMetric.FailedParsing > 0 { dailyMetric.SuccessRate = float64(dailyMetric.SuccessfulParsing) / float64(dailyMetric.SuccessfulParsing+dailyMetric.FailedParsing) * 100 } } // startRealTimeMonitoring starts real-time monitoring func (pm *ParsingMonitor) startRealTimeMonitoring() { ticker := time.NewTicker(10 * time.Second) defer ticker.Stop() for range ticker.C { pm.updateRealTimeMetrics() } } // updateRealTimeMetrics updates real-time parsing metrics func (pm *ParsingMonitor) updateRealTimeMetrics() { pm.mutex.Lock() defer pm.mutex.Unlock() totalParsing := pm.stats.successfulParsing.Load() + pm.stats.failedParsing.Load() successfulParsing := pm.stats.successfulParsing.Load() if totalParsing > 0 { pm.realTimeMetrics.SuccessRatePercent = float64(successfulParsing) / float64(totalParsing) * 100 pm.realTimeMetrics.ErrorRatePercent = 100.0 - pm.realTimeMetrics.SuccessRatePercent } zeroAddressRejected := pm.stats.zeroAddressRejected.Load() if totalParsing > 0 { pm.realTimeMetrics.ZeroAddressRatePercent = float64(zeroAddressRejected) / float64(totalParsing) * 100 } pm.realTimeMetrics.LastUpdateTime = time.Now() // Check for alert conditions pm.checkParsingAlerts() } // checkParsingAlerts checks for alert conditions and logs warnings func (pm *ParsingMonitor) checkParsingAlerts() { successRate := pm.realTimeMetrics.SuccessRatePercent totalParsing := pm.stats.successfulParsing.Load() + pm.stats.failedParsing.Load() // Skip alerts if we don't have enough data if totalParsing < 10 { return } // Critical alert: Success rate below 50% if successRate < 50.0 && totalParsing > 100 { pm.logger.Error(fmt.Sprintf("CRITICAL PARSING ALERT: Success rate %.2f%% is critically low (total: %d)", successRate, totalParsing)) } // Warning alert: Success rate below 80% if successRate < 80.0 && successRate >= 50.0 && totalParsing > 50 { pm.logger.Warn(fmt.Sprintf("PARSING WARNING: Success rate %.2f%% is below normal (total: %d)", successRate, totalParsing)) } // Zero address corruption alert zeroAddressRejected := pm.stats.zeroAddressRejected.Load() if zeroAddressRejected > 10 { zeroAddressRate := pm.realTimeMetrics.ZeroAddressRatePercent pm.logger.Warn(fmt.Sprintf("PARSING CORRUPTION: %d zero address events rejected (%.2f%% of total)", zeroAddressRejected, zeroAddressRate)) } // High error rate alert errorRate := pm.realTimeMetrics.ErrorRatePercent if errorRate > 20.0 && totalParsing > 50 { pm.logger.Warn(fmt.Sprintf("HIGH ERROR RATE: %.2f%% parsing failures detected", errorRate)) } } // startPeriodicReporting starts periodic reporting func (pm *ParsingMonitor) startPeriodicReporting() { ticker := time.NewTicker(pm.config.ReportInterval) defer ticker.Stop() for range ticker.C { pm.generateAndLogReport() } } // generateAndLogReport generates and logs a parsing performance report func (pm *ParsingMonitor) generateAndLogReport() { stats := pm.GetCurrentStats() report := fmt.Sprintf("PARSING PERFORMANCE REPORT - Uptime: %.1f hours, Success Rate: %.1f%%, DEX Detection: %.1f%%, Zero Address Rejected: %d", stats["uptime_hours"].(float64), stats["success_rate_percent"].(float64), stats["dex_detection_rate_percent"].(float64), stats["zero_address_rejected"].(int64)) pm.logger.Info(report) // Check for alerts pm.checkParsingAlertsLegacy(stats) } // checkParsingAlertsLegacy checks for parsing performance alerts func (pm *ParsingMonitor) checkParsingAlertsLegacy(stats map[string]interface{}) { successRate := stats["success_rate_percent"].(float64) zeroAddressRate := (float64(stats["zero_address_rejected"].(int64)) / float64(stats["total_transactions"].(int64))) * 100 if successRate < pm.config.AlertThresholds.MinSuccessRatePercent { pm.logger.Warn(fmt.Sprintf("PARSING ALERT: Success rate %.1f%% below threshold %.1f%%", successRate, pm.config.AlertThresholds.MinSuccessRatePercent)) } if zeroAddressRate > pm.config.AlertThresholds.MaxZeroAddressRatePercent { pm.logger.Warn(fmt.Sprintf("PARSING ALERT: Zero address rate %.1f%% above threshold %.1f%%", zeroAddressRate, pm.config.AlertThresholds.MaxZeroAddressRatePercent)) } } // ExportMetrics exports current metrics in JSON format func (pm *ParsingMonitor) ExportMetrics() ([]byte, error) { stats := pm.GetCurrentStats() return json.MarshalIndent(stats, "", " ") } // GetHealthStatus returns the overall health status of parsing func (pm *ParsingMonitor) GetHealthStatus() string { stats := pm.GetCurrentStats() successRate := stats["success_rate_percent"].(float64) switch { case successRate >= 95: return "excellent" case successRate >= 85: return "good" case successRate >= 70: return "fair" case successRate >= 50: return "poor" default: return "critical" } } // GetDashboardData returns comprehensive dashboard data for real-time monitoring func (pm *ParsingMonitor) GetDashboardData() map[string]interface{} { pm.mutex.RLock() defer pm.mutex.RUnlock() // Get current stats stats := pm.GetCurrentStats() successRate := stats["success_rate_percent"].(float64) totalTransactions := stats["total_transactions"].(int64) // Calculate health status healthStatus := pm.GetHealthStatus() // Protocol performance analysis using available atomic counters protocolPerformance := map[string]interface{}{ "uniswap_v3": map[string]interface{}{ "parsed_transactions": pm.stats.uniswapV3Parsed.Load(), "status": "healthy", // Simplified for now }, "uniswap_v2": map[string]interface{}{ "parsed_transactions": pm.stats.uniswapV2Parsed.Load(), "status": "healthy", // Simplified for now }, "multicall": map[string]interface{}{ "parsed_transactions": pm.stats.multicallParsed.Load(), "status": "healthy", // Simplified for now }, "universal_router": map[string]interface{}{ "parsed_transactions": pm.stats.universalRouterParsed.Load(), "status": "healthy", // Simplified for now }, } // Error breakdown analysis zeroAddressRejected := pm.stats.zeroAddressRejected.Load() suspiciousRejected := pm.stats.suspiciousRejected.Load() duplicateRejected := pm.stats.duplicateRejected.Load() errorBreakdown := map[string]interface{}{ "zero_address": map[string]interface{}{ "count": zeroAddressRejected, "percentage": float64(zeroAddressRejected) / float64(totalTransactions) * 100, }, "suspicious_address": map[string]interface{}{ "count": suspiciousRejected, "percentage": float64(suspiciousRejected) / float64(totalTransactions) * 100, }, "duplicate_address": map[string]interface{}{ "count": duplicateRejected, "percentage": float64(duplicateRejected) / float64(totalTransactions) * 100, }, } // Real-time metrics realTimeMetrics := map[string]interface{}{ "success_rate_percent": pm.realTimeMetrics.SuccessRatePercent, "error_rate_percent": pm.realTimeMetrics.ErrorRatePercent, "zero_address_rate": pm.realTimeMetrics.ZeroAddressRatePercent, "last_update_time": pm.realTimeMetrics.LastUpdateTime, } return map[string]interface{}{ "system_health": map[string]interface{}{ "status": healthStatus, "total_transactions": totalTransactions, "success_rate": successRate, "uptime_minutes": time.Since(pm.startTime).Minutes(), }, "real_time_metrics": realTimeMetrics, "protocol_performance": protocolPerformance, "error_breakdown": errorBreakdown, "alerts": map[string]interface{}{ "critical_alerts": pm.getCriticalAlerts(successRate, totalTransactions), "warning_alerts": pm.getWarningAlerts(successRate, totalTransactions), }, "generated_at": time.Now(), } } // getCriticalAlerts returns current critical alerts func (pm *ParsingMonitor) getCriticalAlerts(successRate float64, totalTransactions int64) []map[string]interface{} { var alerts []map[string]interface{} if successRate < 50.0 && totalTransactions > 100 { alerts = append(alerts, map[string]interface{}{ "type": "critical", "message": fmt.Sprintf("Critical: Success rate %.2f%% is dangerously low", successRate), "metric": "success_rate", "value": successRate, "threshold": 50.0, "timestamp": time.Now(), }) } zeroAddressRate := pm.realTimeMetrics.ZeroAddressRatePercent if zeroAddressRate > 10.0 && totalTransactions > 50 { alerts = append(alerts, map[string]interface{}{ "type": "critical", "message": fmt.Sprintf("Critical: Zero address corruption rate %.2f%% is too high", zeroAddressRate), "metric": "zero_address_rate", "value": zeroAddressRate, "threshold": 10.0, "timestamp": time.Now(), }) } return alerts } // getWarningAlerts returns current warning alerts func (pm *ParsingMonitor) getWarningAlerts(successRate float64, totalTransactions int64) []map[string]interface{} { var alerts []map[string]interface{} if successRate < 80.0 && successRate >= 50.0 && totalTransactions > 50 { alerts = append(alerts, map[string]interface{}{ "type": "warning", "message": fmt.Sprintf("Warning: Success rate %.2f%% is below normal", successRate), "metric": "success_rate", "value": successRate, "threshold": 80.0, "timestamp": time.Now(), }) } errorRate := pm.realTimeMetrics.ErrorRatePercent if errorRate > 15.0 && totalTransactions > 30 { alerts = append(alerts, map[string]interface{}{ "type": "warning", "message": fmt.Sprintf("Warning: Error rate %.2f%% is elevated", errorRate), "metric": "error_rate", "value": errorRate, "threshold": 15.0, "timestamp": time.Now(), }) } return alerts } // GenerateHealthReport generates a comprehensive health report for the parsing system func (pm *ParsingMonitor) GenerateHealthReport() string { dashboardData := pm.GetDashboardData() report := fmt.Sprintf("=== MEV Bot Parsing Health Report ===\n") report += fmt.Sprintf("Generated: %v\n\n", dashboardData["generated_at"]) // System Health systemHealth := dashboardData["system_health"].(map[string]interface{}) report += fmt.Sprintf("SYSTEM HEALTH: %s\n", systemHealth["status"]) report += fmt.Sprintf("Success Rate: %.2f%%\n", systemHealth["success_rate"]) report += fmt.Sprintf("Total Transactions: %d\n", systemHealth["total_transactions"]) report += fmt.Sprintf("Uptime: %.1f minutes\n\n", systemHealth["uptime_minutes"]) // Alerts alerts := dashboardData["alerts"].(map[string]interface{}) criticalAlerts := alerts["critical_alerts"].([]map[string]interface{}) warningAlerts := alerts["warning_alerts"].([]map[string]interface{}) if len(criticalAlerts) > 0 { report += "CRITICAL ALERTS:\n" for _, alert := range criticalAlerts { report += fmt.Sprintf("- %s\n", alert["message"]) } report += "\n" } if len(warningAlerts) > 0 { report += "WARNING ALERTS:\n" for _, alert := range warningAlerts { report += fmt.Sprintf("- %s\n", alert["message"]) } report += "\n" } // Protocol Performance protocolPerf := dashboardData["protocol_performance"].(map[string]interface{}) if len(protocolPerf) > 0 { report += "PROTOCOL PERFORMANCE:\n" for protocol, perfData := range protocolPerf { perf := perfData.(map[string]interface{}) report += fmt.Sprintf("- %s: %.2f%% success rate (%s)\n", protocol, perf["success_rate"], perf["status"]) } report += "\n" } // Error Breakdown errorBreakdown := dashboardData["error_breakdown"].(map[string]interface{}) report += "ERROR BREAKDOWN:\n" for errorType, errorData := range errorBreakdown { data := errorData.(map[string]interface{}) report += fmt.Sprintf("- %s: %d events (%.2f%%)\n", errorType, data["count"], data["percentage"]) } report += "\n=== End Report ===\n" return report }