client->isConfigured(); } /** * 获取日志 * * @param Carbon $from 开始时间 * @param Carbon $to 结束时间 * @param string|null $query SLS 查询语句 * @param int|null $limit 最大返回数量 * @return Collection */ public function fetchLogs( Carbon $from, Carbon $to, ?string $query = null, ?int $limit = null ): Collection { $settings = $this->configService->get('log_analysis.settings', []); $maxLogs = $limit ?? ($settings['max_logs_per_batch'] ?? 1000); $logs = $this->client->getAllLogs( $from->timestamp, $to->timestamp, $query, $maxLogs ); return collect($logs)->map(function ($log) { return $this->normalizeLog($log); }); } /** * 从多个 logstore 获取日志 * * @param Carbon $from 开始时间 * @param Carbon $to 结束时间 * @param string|null $query SLS 查询语句 * @param int|null $limit 最大返回数量 * @param array|null $logstores 要查询的 logstore 列表 * @return array 按 logstore 分组的日志数据 */ public function fetchLogsFromMultipleStores( Carbon $from, Carbon $to, ?string $query = null, ?int $limit = null, ?array $logstores = null ): array { $settings = $this->configService->get('log_analysis.settings', []); $maxLogs = $limit ?? ($settings['max_logs_per_batch'] ?? 1000); $results = $this->client->getAllLogsFromMultipleStores( $from->timestamp, $to->timestamp, $query, $maxLogs, $logstores ); // 标准化每个 logstore 的日志 $normalized = []; foreach ($results as $logstore => $data) { $normalized[$logstore] = [ 'logs' => collect($data['logs'])->map(function ($log) use ($logstore) { $normalizedLog = $this->normalizeLog($log); $normalizedLog['_logstore'] = $logstore; return $normalizedLog; }), 'count' => $data['count'], 'success' => $data['success'], 'error' => $data['error'] ?? null, ]; } return $normalized; } /** * 获取配置的所有 logstore */ public function getLogstores(): array { return $this->client->getLogstores(); } /** * 按 app_name 分组日志 * * @param Collection $logs * @return array */ public function groupByAppName(Collection $logs): array { $grouped = $logs->groupBy(function ($log) { return $log['app_name'] ?? $log['__source__'] ?? 'unknown'; }); return $grouped->map(function ($group) { return $group->values(); })->toArray(); } /** * 按日志级别过滤 * * @param Collection $logs * @param array $levels 要保留的级别 ['ERROR', 'WARN', 'INFO'] * @return Collection */ public function filterByLevel(Collection $logs, array $levels): Collection { $levels = array_map('strtoupper', $levels); return $logs->filter(function ($log) use ($levels) { $level = strtoupper($log['level'] ?? $log['__level__'] ?? ''); return in_array($level, $levels); }); } /** * 获取日志分布直方图 * * @param Carbon $from * @param Carbon $to * @param string|null $query * @return array */ public function getHistogram(Carbon $from, Carbon $to, ?string $query = null): array { return $this->client->getHistograms( $from->timestamp, $to->timestamp, $query ); } /** * 获取日志统计信息 * * @param Collection $logs * @return array */ public function getStatistics(Collection $logs): array { $byLevel = $logs->groupBy(function ($log) { return strtoupper($log['level'] ?? $log['__level__'] ?? 'UNKNOWN'); })->map(fn ($group) => $group->count()); $byApp = $logs->groupBy(function ($log) { return $log['app_name'] ?? $log['__source__'] ?? 'unknown'; })->map(fn ($group) => $group->count()); return [ 'total' => $logs->count(), 'by_level' => $byLevel->toArray(), 'by_app' => $byApp->toArray(), ]; } /** * 获取多 logstore 的统计信息 * * @param array $logstoreData 按 logstore 分组的日志数据 * @return array */ public function getMultiStoreStatistics(array $logstoreData): array { $totalCount = 0; $byLogstore = []; $byLevel = []; $byApp = []; foreach ($logstoreData as $logstore => $data) { if (!$data['success']) { $byLogstore[$logstore] = [ 'count' => 0, 'success' => false, 'error' => $data['error'], ]; continue; } $logs = $data['logs']; $count = $logs->count(); $totalCount += $count; $byLogstore[$logstore] = [ 'count' => $count, 'success' => true, ]; // 按级别统计 $logs->groupBy(function ($log) { return strtoupper($log['level'] ?? $log['__level__'] ?? 'UNKNOWN'); })->each(function ($group, $level) use (&$byLevel) { $byLevel[$level] = ($byLevel[$level] ?? 0) + $group->count(); }); // 按应用统计 $logs->groupBy(function ($log) { return $log['app_name'] ?? $log['__source__'] ?? 'unknown'; })->each(function ($group, $app) use (&$byApp) { $byApp[$app] = ($byApp[$app] ?? 0) + $group->count(); }); } return [ 'total' => $totalCount, 'by_logstore' => $byLogstore, 'by_level' => $byLevel, 'by_app' => $byApp, ]; } /** * 测试连接 */ public function testConnection(): bool { return $this->client->testConnection(); } /** * 标准化日志格式 */ private function normalizeLog(array $log): array { // 如果日志内容是 JSON 字符串(某些情况下 SLS 会将整条日志作为一个字段返回) // 尝试解析 content 或 message 字段 if (isset($log['content']) && is_string($log['content']) && str_starts_with(trim($log['content']), '{')) { $decoded = json_decode($log['content'], true); if (json_last_error() === JSON_ERROR_NONE && is_array($decoded)) { $log = array_merge($log, $decoded); } } // 尝试提取常见字段,优先使用业务字段,其次使用 SLS 系统字段 $normalized = [ 'time' => $log['date'] ?? $log['time'] ?? $log['timestamp'] ?? $log['__time__'] ?? null, 'level' => $log['level'] ?? $log['__level__'] ?? $log['severity'] ?? null, 'message' => $log['message'] ?? $log['msg'] ?? $log['content'] ?? null, 'app_name' => $log['app_name'] ?? $log['application'] ?? $log['service'] ?? $log['__source__'] ?? null, 'trace' => $log['stack_trace'] ?? $log['trace'] ?? $log['exception'] ?? null, 'file' => $log['file'] ?? $log['filename'] ?? null, 'line' => $log['line'] ?? $log['lineno'] ?? null, ]; // 格式化时间 if (is_numeric($normalized['time'])) { // Unix 时间戳 $normalized['time'] = Carbon::createFromTimestamp($normalized['time'])->format('Y-m-d H:i:s'); } elseif (is_string($normalized['time']) && !empty($normalized['time'])) { // ISO 8601 格式或其他字符串格式 try { $normalized['time'] = Carbon::parse($normalized['time'])->format('Y-m-d H:i:s'); } catch (\Exception $e) { // 如果解析失败,保持原样 } } // 保留原始数据 $normalized['_raw'] = $log; return $normalized; } }