diff --git a/app/Console/Commands/CleanScheduledTaskLogsCommand.php b/app/Console/Commands/CleanScheduledTaskLogsCommand.php deleted file mode 100644 index 59afef1..0000000 --- a/app/Console/Commands/CleanScheduledTaskLogsCommand.php +++ /dev/null @@ -1,73 +0,0 @@ -option('days'); - $logPath = storage_path('logs/scheduled-tasks'); - - if (!File::exists($logPath)) { - Log::info('日志目录不存在,无需清理'); - return Command::SUCCESS; - } - - $cutoffDate = Carbon::now()->subDays($days); - Log::info("开始清理 {$days} 天前的定时任务日志..."); - Log::info("截止日期: {$cutoffDate->format('Y-m-d')}"); - - $files = File::files($logPath); - $deletedCount = 0; - $totalSize = 0; - - foreach ($files as $file) { - $filename = $file->getFilename(); - - // 匹配日志文件名格式: task-name-YYYY-MM-DD.log - if (preg_match('/-(\d{4}-\d{2}-\d{2})\.log$/', $filename, $matches)) { - $fileDate = Carbon::parse($matches[1]); - - if ($fileDate->lt($cutoffDate)) { - $fileSize = $file->getSize(); - $totalSize += $fileSize; - - File::delete($file->getPathname()); - $deletedCount++; - - Log::info("已删除: {$filename} (" . $this->formatBytes($fileSize) . ")"); - } - } - } - - if ($deletedCount > 0) { - Log::info("清理完成!共删除 {$deletedCount} 个日志文件,释放空间: " . $this->formatBytes($totalSize)); - } else { - Log::info('没有需要清理的日志文件'); - } - - return Command::SUCCESS; - } - - private function formatBytes(int $bytes): string - { - if ($bytes >= 1073741824) { - return number_format($bytes / 1073741824, 2) . ' GB'; - } elseif ($bytes >= 1048576) { - return number_format($bytes / 1048576, 2) . ' MB'; - } elseif ($bytes >= 1024) { - return number_format($bytes / 1024, 2) . ' KB'; - } - return $bytes . ' B'; - } -} diff --git a/app/Console/Commands/GitMonitorCacheCommand.php b/app/Console/Commands/GitMonitorCacheCommand.php index 320a156..466d0f8 100644 --- a/app/Console/Commands/GitMonitorCacheCommand.php +++ b/app/Console/Commands/GitMonitorCacheCommand.php @@ -17,11 +17,11 @@ class GitMonitorCacheCommand extends Command $cache = $monitor->refreshReleaseCache(true); if (empty($cache)) { - Log::warning('未获取到任何 release 版本信息,请检查配置。'); + Log::channel('git-monitor')->warning('未获取到任何 release 版本信息,请检查配置。'); return; } - Log::info(sprintf( + Log::channel('git-monitor')->info(sprintf( '已缓存 %d 个仓库的 release 分支信息。', count($cache['repositories'] ?? []) )); diff --git a/app/Console/Commands/GitMonitorCheckCommand.php b/app/Console/Commands/GitMonitorCheckCommand.php index ba5730b..ae68329 100644 --- a/app/Console/Commands/GitMonitorCheckCommand.php +++ b/app/Console/Commands/GitMonitorCheckCommand.php @@ -25,11 +25,11 @@ class GitMonitorCheckCommand extends Command foreach ($results as $repo => $result) { if (isset($result['error'])) { - Log::error(sprintf('[%s] %s', $repo, $result['error'])); + Log::channel('git-monitor')->error(sprintf('[%s] %s', $repo, $result['error'])); continue; } - Log::info(sprintf( + Log::channel('git-monitor')->info(sprintf( '[%s] 分支 %s 已对齐 %s,扫描 %d 个提交。', $repo, $result['branch'], @@ -38,9 +38,9 @@ class GitMonitorCheckCommand extends Command )); if (!empty($result['issues']['develop_merges'])) { - Log::warning(sprintf(' - 检测到 %d 个 develop merge:', count($result['issues']['develop_merges']))); + Log::channel('git-monitor')->warning(sprintf(' - 检测到 %d 个 develop merge:', count($result['issues']['develop_merges']))); foreach ($result['issues']['develop_merges'] as $commit) { - Log::warning(sprintf( + Log::channel('git-monitor')->warning(sprintf( ' • %s %s (%s)', substr($commit['hash'], 0, 8), $commit['subject'], @@ -50,9 +50,9 @@ class GitMonitorCheckCommand extends Command } if (!empty($result['issues']['missing_functions'])) { - Log::warning(sprintf(' - 检测到 %d 个疑似缺失函数的提交:', count($result['issues']['missing_functions']))); + Log::channel('git-monitor')->warning(sprintf(' - 检测到 %d 个疑似缺失函数的提交:', count($result['issues']['missing_functions']))); foreach ($result['issues']['missing_functions'] as $issue) { - Log::warning(sprintf( + Log::channel('git-monitor')->warning(sprintf( ' • %s %s (%s)', substr($issue['commit']['hash'], 0, 8), $issue['commit']['subject'], @@ -60,7 +60,7 @@ class GitMonitorCheckCommand extends Command )); foreach ($issue['details'] as $detail) { $functions = implode(', ', array_slice($detail['functions'], 0, 5)); - Log::warning(sprintf(' %s => %s', $detail['file'], $functions)); + Log::channel('git-monitor')->warning(sprintf(' %s => %s', $detail['file'], $functions)); } } } diff --git a/app/Console/Commands/JenkinsMonitorCommand.php b/app/Console/Commands/JenkinsMonitorCommand.php index 63b6871..fc883fa 100644 --- a/app/Console/Commands/JenkinsMonitorCommand.php +++ b/app/Console/Commands/JenkinsMonitorCommand.php @@ -14,29 +14,29 @@ class JenkinsMonitorCommand extends Command public function handle(JenkinsMonitorService $service): void { - Log::info('开始检查 Jenkins 构建...'); + Log::channel('jenkins-monitor')->info('开始检查 Jenkins 构建...'); $results = $service->checkAllProjects(); if (isset($results['skipped'])) { - Log::warning('跳过检查: ' . ($results['reason'] ?? 'unknown')); + Log::channel('jenkins-monitor')->warning('跳过检查: ' . ($results['reason'] ?? 'unknown')); return; } foreach ($results as $slug => $result) { if (isset($result['skipped'])) { - Log::info(sprintf('[%s] 跳过: %s', $slug, $result['reason'] ?? 'unknown')); + Log::channel('jenkins-monitor')->info(sprintf('[%s] 跳过: %s', $slug, $result['reason'] ?? 'unknown')); continue; } $newBuilds = $result['new_builds'] ?? []; if (empty($newBuilds)) { - Log::info(sprintf('[%s] 无新构建', $slug)); + Log::channel('jenkins-monitor')->info(sprintf('[%s] 无新构建', $slug)); } else { - Log::info(sprintf('[%s] 发现 %d 个新构建: #%s', $slug, count($newBuilds), implode(', #', $newBuilds))); + Log::channel('jenkins-monitor')->info(sprintf('[%s] 发现 %d 个新构建: #%s', $slug, count($newBuilds), implode(', #', $newBuilds))); } } - Log::info('检查完成'); + Log::channel('jenkins-monitor')->info('检查完成'); } } diff --git a/app/Console/Commands/LogAnalysisCommand.php b/app/Console/Commands/LogAnalysisCommand.php index eda4f49..4f5feb1 100644 --- a/app/Console/Commands/LogAnalysisCommand.php +++ b/app/Console/Commands/LogAnalysisCommand.php @@ -30,12 +30,12 @@ class LogAnalysisCommand extends Command ): int { // 检查配置 if (!$slsService->isConfigured()) { - Log::error('SLS 服务未配置,请检查 .env 中的 SLS_* 配置项'); + Log::channel('log-analysis')->error('SLS 服务未配置,请检查 .env 中的 SLS_* 配置项'); return Command::FAILURE; } if (!$aiService->isConfigured()) { - Log::error('AI 服务未配置,请在页面上配置 AI 提供商或设置 .env 中的 AI_* 配置项'); + Log::channel('log-analysis')->error('AI 服务未配置,请在页面上配置 AI 提供商或设置 .env 中的 AI_* 配置项'); return Command::FAILURE; } @@ -44,7 +44,7 @@ class LogAnalysisCommand extends Command $to = $this->parseTime($this->option('to') ?? 'now'); if ($from >= $to) { - Log::error('开始时间必须早于结束时间'); + Log::channel('log-analysis')->error('开始时间必须早于结束时间'); return Command::FAILURE; } @@ -56,11 +56,10 @@ class LogAnalysisCommand extends Command $query = $this->option('query'); - Log::info("开始分析日志..."); - Log::info(" 时间范围: {$from->format('Y-m-d H:i:s')} ~ {$to->format('Y-m-d H:i:s')}"); - Log::info(" 查询语句: " . ($query ?: '*')); - Log::info(" 分析模式: {$mode->label()}"); - $this->newLine(); + Log::channel('log-analysis')->info("开始分析日志..."); + Log::channel('log-analysis')->info(" 时间范围: {$from->format('Y-m-d H:i:s')} ~ {$to->format('Y-m-d H:i:s')}"); + Log::channel('log-analysis')->info(" 查询语句: " . ($query ?: '*')); + Log::channel('log-analysis')->info(" 分析模式: {$mode->label()}"); try { $result = $analysisService->analyze( @@ -75,17 +74,17 @@ class LogAnalysisCommand extends Command if ($outputPath = $this->option('output')) { $json = json_encode($result, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE); file_put_contents($outputPath, $json); - Log::info("报告已保存到: {$outputPath}"); + Log::channel('log-analysis')->info("报告已保存到: {$outputPath}"); } // 推送到钉钉 if ($this->option('push')) { - Log::info("正在推送到钉钉..."); + Log::channel('log-analysis')->info("正在推送到钉钉..."); $pushed = $analysisService->pushToNotification($result); if ($pushed) { - Log::info("已推送到钉钉"); + Log::channel('log-analysis')->info("已推送到钉钉"); } else { - Log::warning("钉钉推送失败"); + Log::channel('log-analysis')->warning("钉钉推送失败"); } } @@ -94,7 +93,7 @@ class LogAnalysisCommand extends Command return Command::SUCCESS; } catch (\Exception $e) { - Log::error("分析失败: {$e->getMessage()}"); + Log::channel('log-analysis')->error("分析失败: {$e->getMessage()}"); return Command::FAILURE; } } @@ -133,57 +132,45 @@ class LogAnalysisCommand extends Command */ private function displaySummary(array $result): void { - $this->newLine(); - $this->info('=== 分析摘要 ==='); - $this->line("总日志数: {$result['metadata']['total_logs']}"); - $this->line("分析应用数: {$result['metadata']['apps_analyzed']}"); - $this->line("执行时间: {$result['metadata']['execution_time_ms']}ms"); - $this->newLine(); + Log::channel('log-analysis')->info('=== 分析摘要 ==='); + Log::channel('log-analysis')->info("总日志数: {$result['metadata']['total_logs']}"); + Log::channel('log-analysis')->info("分析应用数: {$result['metadata']['apps_analyzed']}"); + Log::channel('log-analysis')->info("执行时间: {$result['metadata']['execution_time_ms']}ms"); if (empty($result['results'])) { - $this->warn('未找到匹配的日志'); + Log::channel('log-analysis')->warning('未找到匹配的日志'); return; } foreach ($result['results'] as $appName => $appResult) { - $this->line("【{$appName}】"); + Log::channel('log-analysis')->info("【{$appName}】"); if (isset($appResult['error'])) { - $this->error(" 分析失败: {$appResult['error']}"); + Log::channel('log-analysis')->error(" 分析失败: {$appResult['error']}"); continue; } $impact = $appResult['impact'] ?? 'unknown'; - $impactColor = match ($impact) { - 'high' => 'red', - 'medium' => 'yellow', - 'low' => 'green', - default => 'white', - }; - $this->line(" 日志数: {$appResult['log_count']}"); - $this->line(" 代码上下文: " . ($appResult['has_code_context'] ? '是' : '否')); - $this->line(" 影响级别: {$impact}"); - $this->line(" 摘要: " . ($appResult['summary'] ?? 'N/A')); + Log::channel('log-analysis')->info(" 日志数: {$appResult['log_count']}"); + Log::channel('log-analysis')->info(" 代码上下文: " . ($appResult['has_code_context'] ? '是' : '否')); + Log::channel('log-analysis')->info(" 影响级别: {$impact}"); + Log::channel('log-analysis')->info(" 摘要: " . ($appResult['summary'] ?? 'N/A')); $anomalies = $appResult['core_anomalies'] ?? []; if (!empty($anomalies)) { - $this->line(" 异常数: " . count($anomalies)); + Log::channel('log-analysis')->info(" 异常数: " . count($anomalies)); - $table = []; foreach (array_slice($anomalies, 0, 5) as $anomaly) { - $table[] = [ + Log::channel('log-analysis')->info(sprintf( + " - [%s] %s (数量: %d) - %s", $anomaly['type'] ?? 'N/A', $anomaly['classification'] ?? 'N/A', $anomaly['count'] ?? 1, - mb_substr($anomaly['possible_cause'] ?? 'N/A', 0, 40), - ]; + mb_substr($anomaly['possible_cause'] ?? 'N/A', 0, 40) + )); } - - $this->table(['类型', '分类', '数量', '可能原因'], $table); } - - $this->newLine(); } } } diff --git a/app/Console/Commands/ScheduledTaskRefreshCommand.php b/app/Console/Commands/ScheduledTaskRefreshCommand.php index 62121df..be1b6c2 100644 --- a/app/Console/Commands/ScheduledTaskRefreshCommand.php +++ b/app/Console/Commands/ScheduledTaskRefreshCommand.php @@ -15,15 +15,15 @@ class ScheduledTaskRefreshCommand extends Command public function handle(ScheduledTaskService $taskService): int { try { - Log::info('开始刷新定时任务列表...'); + Log::channel('scheduled-tasks')->info('开始刷新定时任务列表...'); $tasks = $taskService->getAllTasks(); - Log::info(sprintf('成功刷新 %d 个定时任务', count($tasks))); + Log::channel('scheduled-tasks')->info(sprintf('成功刷新 %d 个定时任务', count($tasks))); - // 记录任务列表到日志 + // 显示任务列表 foreach ($tasks as $task) { - Log::info(sprintf( + Log::channel('scheduled-tasks')->info(sprintf( ' - %s: %s (%s) [%s]', $task['name'], $task['description'], @@ -34,7 +34,7 @@ class ScheduledTaskRefreshCommand extends Command return Command::SUCCESS; } catch (\Exception $e) { - Log::error("刷新失败: {$e->getMessage()}"); + Log::channel('scheduled-tasks')->error("刷新失败: {$e->getMessage()}"); return Command::FAILURE; } } diff --git a/app/Services/JiraService.php b/app/Services/JiraService.php index d0e6227..5d499d9 100644 --- a/app/Services/JiraService.php +++ b/app/Services/JiraService.php @@ -170,7 +170,7 @@ class JiraService $endOfWeek = $now->copy()->subWeek()->endOfWeek(); $workLogs = $this->getWorkLogs($username, $startOfWeek, $endOfWeek); - $organizedTasks = $this->organizeTasksForReport($workLogs); + $organizedTasks = $this->organizeTasksForReport($workLogs, $username); $nextWeekTasks = $this->getNextWeekTasks($username); @@ -307,11 +307,14 @@ class JiraService 'created', 'fixVersions', 'labels', - 'customfield_10004', // Sprint字段 - 'customfield_10900', // Bug发现阶段 - 'customfield_12700', // Bug错误类型 - 'customfield_10115', // Bug修复描述 - 'customfield_14305', // 需求类型 + 'assignee', // 经办人 + 'customfield_10004', // Sprint字段 + 'customfield_10900', // Bug发现阶段 + 'customfield_11000', // 开发人 + 'customfield_11301', // 实际修复人 + 'customfield_12700', // Bug错误类型 + 'customfield_10115', // Bug修复描述 + 'customfield_14305', // 需求类型 ]); if (!empty($issues->issues)) { @@ -369,6 +372,11 @@ class JiraService // 提取需求类型 $requirementType = $this->extractRequirementType($issue); + // 提取经办人、开发人、实际修复人 + $assignee = $this->extractAssignee($issue); + $developer = $this->extractDeveloper($issue); + $actualFixer = $this->extractActualFixer($issue); + $workLogs->push([ 'id' => $worklog->id ?? '', 'project' => $issue->fields->project->name ?? '', @@ -385,6 +393,9 @@ class JiraService 'bug_type' => $bugType, 'bug_description' => $bugDescription, 'requirement_type' => $requirementType, + 'assignee' => $assignee, + 'developer' => $developer, + 'actual_fixer' => $actualFixer, 'date' => $worklogDate->format('Y-m-d'), 'time' => $worklogDate->format('H:i'), 'hours' => round(($worklog->timeSpentSeconds ?? 0) / 3600, 2), @@ -593,6 +604,60 @@ class JiraService return null; } + /** + * 提取经办人 + */ + private function extractAssignee($issue): ?string + { + // 从assignee字段获取经办人 + if (isset($issue->fields->assignee)) { + $assignee = $issue->fields->assignee; + + // 处理对象类型 + if (is_object($assignee)) { + return $assignee->name ?? $assignee->key ?? null; + } elseif (is_string($assignee)) { + return $assignee; + } + } + + return null; + } + + /** + * 提取开发人 + */ + private function extractDeveloper($issue): ?string + { + // 从customfield_11000获取开发人 + if (isset($issue->fields->customFields['customfield_11000'])) { + $developer = $issue->fields->customFields['customfield_11000']; + + if (is_string($developer) && !empty($developer)) { + return $developer; + } + } + + return null; + } + + /** + * 提取实际修复人 + */ + private function extractActualFixer($issue): ?string + { + // 从customfield_11301获取实际修复人 + if (isset($issue->fields->customFields['customfield_11301'])) { + $fixer = $issue->fields->customFields['customfield_11301']; + + if (is_string($fixer) && !empty($fixer)) { + return $fixer; + } + } + + return null; + } + /** * 清理摘要中的图片链接 */ @@ -610,29 +675,27 @@ class JiraService */ private function isTaskCompleted(string $status): bool { - // 只有已完成或已取消才打勾 - $completedStatuses = [ - '已完成', - '完成', - 'Done', - 'Closed', - 'Resolved', - ]; - $cancelledStatuses = [ - '已取消', - '取消', - 'Cancelled', - 'Canceled', + // 定义"进行中"的状态列表 + // 如果状态不在这个列表中,则认为任务已完成 + $inProgressStatuses = [ + '需求已确认', + '开发中', + '需求调研中', + '需求已调研', + '需求已评审', + '需求已排期', + '待提测', + '需求设计中', ]; - return in_array($status, $completedStatuses, true) - || in_array($status, $cancelledStatuses, true); + // 如果状态不在"进行中"列表中,则标记为已完成 + return !in_array($status, $inProgressStatuses, true); } /** * 组织任务数据用于周报生成 */ - private function organizeTasksForReport(Collection $workLogs): Collection + private function organizeTasksForReport(Collection $workLogs, string $username): Collection { $organized = collect([ 'sprints' => collect(), @@ -671,6 +734,21 @@ class JiraService $isSubtask = in_array($issueType, ['Sub-task', 'sub-task', '子任务']); if ($isBug && $workLog['bug_stage']) { + // Bug过滤逻辑:必须经办人、实际修复人或开发人是当前用户 + $assignee = $workLog['assignee'] ?? null; + $developer = $workLog['developer'] ?? null; + $actualFixer = $workLog['actual_fixer'] ?? null; + + // 检查是否有任一字段匹配当前用户 + $isUserRelated = ($assignee === $username) + || ($developer === $username) + || ($actualFixer === $username); + + // 如果不是当前用户相关的Bug,跳过 + if (!$isUserRelated) { + continue; + } + // Bug按发现阶段分类 $stage = $workLog['bug_stage']; if (!$organized['bugs']->has($stage)) { diff --git a/config/logging.php b/config/logging.php index 9e998a4..2b1de4e 100644 --- a/config/logging.php +++ b/config/logging.php @@ -127,6 +127,38 @@ return [ 'path' => storage_path('logs/laravel.log'), ], + 'jenkins-monitor' => [ + 'driver' => 'daily', + 'path' => storage_path('logs/scheduled-tasks/jenkins-monitor.log'), + 'level' => env('LOG_LEVEL', 'debug'), + 'days' => 7, + 'replace_placeholders' => true, + ], + + 'git-monitor' => [ + 'driver' => 'daily', + 'path' => storage_path('logs/scheduled-tasks/git-monitor.log'), + 'level' => env('LOG_LEVEL', 'debug'), + 'days' => 7, + 'replace_placeholders' => true, + ], + + 'log-analysis' => [ + 'driver' => 'daily', + 'path' => storage_path('logs/scheduled-tasks/log-analysis.log'), + 'level' => env('LOG_LEVEL', 'debug'), + 'days' => 7, + 'replace_placeholders' => true, + ], + + 'scheduled-tasks' => [ + 'driver' => 'daily', + 'path' => storage_path('logs/scheduled-tasks/scheduled-tasks.log'), + 'level' => env('LOG_LEVEL', 'debug'), + 'days' => 7, + 'replace_placeholders' => true, + ], + ], ]; diff --git a/routes/console.php b/routes/console.php index e9c1255..30cb274 100644 --- a/routes/console.php +++ b/routes/console.php @@ -26,7 +26,6 @@ Schedule::command('git-monitor:check') ->withoutOverlapping() ->runInBackground() ->description('git-monitor-check') - ->appendOutputTo(storage_path('logs/scheduled-tasks/git-monitor-' . date('Y-m-d') . '.log')) ->when(fn() => \App\Services\ScheduledTaskService::isEnabled('git-monitor-check')); // Git Monitor - 每天凌晨 2 点刷新 release 缓存 @@ -34,7 +33,6 @@ Schedule::command('git-monitor:cache') ->dailyAt('02:00') ->withoutOverlapping() ->description('git-monitor-cache') - ->appendOutputTo(storage_path('logs/scheduled-tasks/git-monitor-' . date('Y-m-d') . '.log')) ->when(fn() => \App\Services\ScheduledTaskService::isEnabled('git-monitor-cache')); // SLS 日志分析 - 每天凌晨 2 点执行 @@ -43,7 +41,6 @@ Schedule::command('log-analysis:run --from="-24h" --to="now" --query="ERROR or W ->withoutOverlapping() ->runInBackground() ->description('daily-log-analysis') - ->appendOutputTo(storage_path('logs/scheduled-tasks/log-analysis-' . date('Y-m-d') . '.log')) ->when(fn() => \App\Services\ScheduledTaskService::isEnabled('daily-log-analysis')) ->onFailure(fn() => Log::error('每日日志分析定时任务执行失败')); @@ -53,7 +50,6 @@ Schedule::command('log-analysis:run --from="-6h" --to="now" --query="ERROR or WA ->withoutOverlapping() ->runInBackground() ->description('frequent-log-analysis') - ->appendOutputTo(storage_path('logs/scheduled-tasks/log-analysis-' . date('Y-m-d') . '.log')) ->when(fn() => \App\Services\ScheduledTaskService::isEnabled('frequent-log-analysis')) ->onFailure(fn() => Log::error('SLS 日志分析定时任务执行失败')); @@ -63,7 +59,6 @@ Schedule::command('jenkins:monitor') ->withoutOverlapping() ->runInBackground() ->description('jenkins-monitor') - ->appendOutputTo(storage_path('logs/scheduled-tasks/jenkins-monitor-' . date('Y-m-d') . '.log')) ->when(fn() => \App\Services\ScheduledTaskService::isEnabled('jenkins-monitor')); // 定时任务刷新 - 每天凌晨 3 点刷新定时任务列表 @@ -71,13 +66,4 @@ Schedule::command('scheduled-task:refresh') ->dailyAt('03:00') ->withoutOverlapping() ->description('scheduled-task-refresh') - ->appendOutputTo(storage_path('logs/scheduled-tasks/scheduled-tasks-' . date('Y-m-d') . '.log')) ->when(fn() => \App\Services\ScheduledTaskService::isEnabled('scheduled-task-refresh')); - -// 日志清理 - 每天凌晨 4 点清理 7 天前的定时任务日志 -Schedule::command('logs:clean-scheduled-tasks --days=7') - ->dailyAt('04:00') - ->withoutOverlapping() - ->description('logs-cleanup') - ->appendOutputTo(storage_path('logs/scheduled-tasks/scheduled-tasks-' . date('Y-m-d') . '.log')) - ->when(fn() => \App\Services\ScheduledTaskService::isEnabled('logs-cleanup'));