$from, 'to_time' => $to, 'query' => $effectiveQuery, 'mode' => $mode->value, 'total_logs' => 0, 'results' => [], 'metadata' => [], 'status' => 'pending', ]); // 分发后台任务 LogAnalysisJob::dispatch( $report->id, $from, $to, $effectiveQuery, $mode, $pushNotification ); return $report; } /** * 执行日志分析 * * @param Carbon $from 开始时间 * @param Carbon $to 结束时间 * @param string|null $query SLS 查询语句 * @param AnalysisMode $mode 分析模式 * @param bool $saveReport 是否保存报告 * @return array 分析结果 */ public function analyze( Carbon $from, Carbon $to, ?string $query = null, AnalysisMode $mode = AnalysisMode::Logs, bool $saveReport = true ): array { $startTime = microtime(true); // 1. 获取日志 $logs = $this->slsService->fetchLogs($from, $to, $query); if ($logs->isEmpty()) { return $this->buildEmptyResult($from, $to, $query, $mode); } // 2. 按 app_name 分组 $grouped = $this->slsService->groupByAppName($logs); // 3. 分析每个分组 $results = []; $settings = $this->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 ($mode === AnalysisMode::LogsWithCode) { $repoPath = $this->codeContextService->getRepoPath($appName); if ($repoPath) { $codeContext = $this->codeContextService->extractRelevantCode($repoPath, $appLogsCollection); } } // 准备日志内容 $logsContent = $this->formatLogsForAnalysis($appLogsCollection); // AI 分析 try { $results[$appName] = $this->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. 构建结果 $result = [ 'request' => [ 'from' => $from->format('Y-m-d H:i:s'), 'to' => $to->format('Y-m-d H:i:s'), 'query' => $query, 'mode' => $mode->value, ], '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 ($saveReport) { $this->saveReport($result, $from, $to, $query, $mode, $logs->count()); } return $result; } /** * 仅查询日志(不进行 AI 分析) * * @param Carbon $from * @param Carbon $to * @param string|null $query * @param int|null $limit * @return array */ public function queryLogs( Carbon $from, Carbon $to, ?string $query = null, ?int $limit = null ): array { $logs = $this->slsService->fetchLogs($from, $to, $query, $limit); $grouped = $this->slsService->groupByAppName($logs); $statistics = $this->slsService->getStatistics($logs); return [ 'total' => $logs->count(), 'statistics' => $statistics, 'grouped' => array_map(fn($g) => count($g), $grouped), 'logs' => $logs->take(100)->values()->toArray(), ]; } /** * 从多个 logstore 查询日志(不进行 AI 分析) * * @param Carbon $from * @param Carbon $to * @param string|null $query * @param int|null $limit * @param array|null $logstores * @return array */ public function queryLogsFromMultipleStores( Carbon $from, Carbon $to, ?string $query = null, ?int $limit = null, ?array $logstores = null ): array { $logstoreData = $this->slsService->fetchLogsFromMultipleStores($from, $to, $query, $limit, $logstores); $statistics = $this->slsService->getMultiStoreStatistics($logstoreData); // 合并所有 logstore 的日志用于预览 $allLogs = collect(); $groupedByLogstore = []; foreach ($logstoreData as $logstore => $data) { if ($data['success']) { $allLogs = $allLogs->merge($data['logs']); $groupedByLogstore[$logstore] = [ 'count' => $data['count'], 'success' => true, ]; } else { $groupedByLogstore[$logstore] = [ 'count' => 0, 'success' => false, 'error' => $data['error'], ]; } } return [ 'total' => $statistics['total'], 'statistics' => $statistics, 'grouped_by_logstore' => $groupedByLogstore, 'logs' => $allLogs->take(100)->values()->toArray(), ]; } /** * 获取历史报告列表 * * @param int $limit * @param int $offset * @return array */ public function getReports(int $limit = 20, int $offset = 0): array { $query = LogAnalysisReport::query() ->orderBy('created_at', 'desc'); $total = $query->count(); $reports = $query->skip($offset)->take($limit)->get(); return [ 'total' => $total, 'reports' => $reports->map(function ($report) { return [ 'id' => $report->id, 'from_time' => $report->from_time->format('Y-m-d H:i:s'), 'to_time' => $report->to_time->format('Y-m-d H:i:s'), 'query' => $report->query, 'mode' => $report->mode, 'total_logs' => $report->total_logs, 'status' => $report->status, 'created_at' => $report->created_at->format('Y-m-d H:i:s'), ]; })->toArray(), ]; } /** * 获取单个报告详情 * * @param int $id * @return array|null */ public function getReport(int $id): ?array { $report = LogAnalysisReport::find($id); if (!$report) { return null; } return [ 'id' => $report->id, 'request' => [ 'from' => $report->from_time->format('Y-m-d H:i:s'), 'to' => $report->to_time->format('Y-m-d H:i:s'), 'query' => $report->query, 'mode' => $report->mode, ], 'results' => $report->results, 'metadata' => $report->metadata, 'status' => $report->status, 'error_message' => $report->error_message, 'created_at' => $report->created_at->format('Y-m-d H:i:s'), ]; } /** * 推送分析结果到钉钉 * * @param array $result 分析结果 * @return bool */ public function pushToNotification(array $result): bool { $message = $this->formatNotificationMessage($result); try { $this->dingTalkService->sendText($message); return true; } catch (\Exception $e) { return false; } } /** * 格式化日志用于 AI 分析 */ 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 formatNotificationMessage(array $result): string { $lines = []; $lines[] = "📊 SLS 日志分析报告"; $lines[] = "时间范围: {$result['request']['from']} ~ {$result['request']['to']}"; $lines[] = "总日志数: {$result['metadata']['total_logs']}"; $lines[] = ""; foreach ($result['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[] = ""; } return implode("\n", $lines); } /** * 保存分析报告 */ private function saveReport( array $result, Carbon $from, Carbon $to, ?string $query, AnalysisMode $mode, int $totalLogs ): LogAnalysisReport { return LogAnalysisReport::create([ 'from_time' => $from, 'to_time' => $to, 'query' => $query, 'mode' => $mode->value, 'total_logs' => $totalLogs, 'results' => $result['results'], 'metadata' => $result['metadata'], 'status' => 'completed', ]); } /** * 构建空结果 */ private function buildEmptyResult( Carbon $from, Carbon $to, ?string $query, AnalysisMode $mode ): array { return [ 'request' => [ 'from' => $from->format('Y-m-d H:i:s'), 'to' => $to->format('Y-m-d H:i:s'), 'query' => $query, 'mode' => $mode->value, ], 'results' => [], 'metadata' => [ 'total_logs' => 0, 'apps_analyzed' => 0, 'execution_time_ms' => 0, 'analyzed_at' => Carbon::now()->format('Y-m-d H:i:s'), 'message' => '未找到匹配的日志', ], ]; } }