reportId); if (!$report) { Log::error("LogAnalysisJob: Report not found", ['report_id' => $this->reportId]); return; } try { $startTime = microtime(true); // 1. 获取日志 $logs = $slsService->fetchLogs($this->from, $this->to, $this->query); if ($logs->isEmpty()) { $report->update([ 'status' => 'completed', 'total_logs' => 0, 'results' => [], 'metadata' => [ 'total_logs' => 0, 'apps_analyzed' => 0, 'execution_time_ms' => 0, 'analyzed_at' => Carbon::now()->format('Y-m-d H:i:s'), 'message' => '未找到匹配的日志', ], ]); return; } // 2. 按 app_name 分组 $grouped = $slsService->groupByAppName($logs); // 3. 分析每个分组 $results = []; $settings = $configService->get('log_analysis.settings', []); $maxLogsPerApp = $settings['max_logs_per_app'] ?? 500; foreach ($grouped as $appName => $appLogs) { $appLogsCollection = collect($appLogs); // 限制每个 app 的日志数量 if ($appLogsCollection->count() > $maxLogsPerApp) { $appLogsCollection = $appLogsCollection->take($maxLogsPerApp); } // 获取代码上下文(如果需要) $codeContext = null; if ($this->mode === AnalysisMode::LogsWithCode) { $repoPath = $codeContextService->getRepoPath($appName); if ($repoPath) { $codeContext = $codeContextService->extractRelevantCode($repoPath, $appLogsCollection); } } // 准备日志内容 $logsContent = $this->formatLogsForAnalysis($appLogsCollection); // AI 分析 try { $results[$appName] = $aiService->analyzeLogs($logsContent, $codeContext); $results[$appName]['log_count'] = $appLogsCollection->count(); $results[$appName]['has_code_context'] = $codeContext !== null; } catch (\Exception $e) { $results[$appName] = [ 'error' => $e->getMessage(), 'log_count' => $appLogsCollection->count(), 'has_code_context' => false, ]; } } $executionTime = (microtime(true) - $startTime) * 1000; // 4. 更新报告 $report->update([ 'status' => 'completed', 'total_logs' => $logs->count(), 'results' => $results, 'metadata' => [ 'total_logs' => $logs->count(), 'apps_analyzed' => count($results), 'execution_time_ms' => round($executionTime), 'analyzed_at' => Carbon::now()->format('Y-m-d H:i:s'), ], ]); // 5. 推送通知(如果需要) if ($this->pushNotification) { $this->pushToNotification($report, $dingTalkService); } Log::info("LogAnalysisJob: Completed", [ 'report_id' => $this->reportId, 'total_logs' => $logs->count(), 'execution_time_ms' => round($executionTime), ]); } catch (\Exception $e) { Log::error("LogAnalysisJob: Failed", [ 'report_id' => $this->reportId, 'error' => $e->getMessage(), ]); $report->update([ 'status' => 'failed', 'error_message' => $e->getMessage(), ]); } } private function formatLogsForAnalysis(Collection $logs): string { $formatted = []; foreach ($logs as $log) { $line = sprintf( "[%s] [%s] %s", $log['time'] ?? 'N/A', $log['level'] ?? 'N/A', $log['message'] ?? json_encode($log['_raw'] ?? $log) ); if (!empty($log['trace'])) { $line .= "\n" . $log['trace']; } $formatted[] = $line; } return implode("\n\n", $formatted); } private function pushToNotification(LogAnalysisReport $report, DingTalkService $dingTalkService): void { $lines = []; $lines[] = "📊 SLS 日志分析报告"; $lines[] = "时间范围: {$report->from_time->format('Y-m-d H:i:s')} ~ {$report->to_time->format('Y-m-d H:i:s')}"; $lines[] = "总日志数: {$report->total_logs}"; $lines[] = ""; foreach ($report->results as $appName => $appResult) { $lines[] = "【{$appName}】"; if (isset($appResult['error'])) { $lines[] = " 分析失败: {$appResult['error']}"; continue; } $impact = $appResult['impact'] ?? 'unknown'; $impactEmoji = match ($impact) { 'high' => '🔴', 'medium' => '🟡', 'low' => '🟢', default => '⚪', }; $lines[] = " 影响级别: {$impactEmoji} {$impact}"; $lines[] = " 摘要: " . ($appResult['summary'] ?? 'N/A'); $anomalies = $appResult['core_anomalies'] ?? []; if (!empty($anomalies)) { $lines[] = " 异常数: " . count($anomalies); foreach (array_slice($anomalies, 0, 3) as $anomaly) { $lines[] = " - [{$anomaly['classification']}] {$anomaly['possible_cause']}"; } } $lines[] = ""; } $message = implode("\n", $lines); try { $dingTalkService->sendText($message); } catch (\Exception $e) { Log::warning("LogAnalysisJob: Failed to push notification", [ 'report_id' => $this->reportId, 'error' => $e->getMessage(), ]); } } }