generate_report.py 5.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135
  1. #!/usr/bin/env python3
  2. import json
  3. import os
  4. from datetime import datetime, timedelta
  5. from typing import List, Dict, Any
  6. def load_monitoring_results() -> List[Dict[str, Any]]:
  7. """加载 monitoring_results.json"""
  8. if not os.path.exists("monitoring_results.json"):
  9. print("No monitoring results found")
  10. return []
  11. try:
  12. with open("monitoring_results.json", "r", encoding="utf-8") as f:
  13. return json.load(f)
  14. except (json.JSONDecodeError, OSError) as e:
  15. print(f"Error loading monitoring_results.json: {e}")
  16. return []
  17. def get_beijing_time() -> datetime:
  18. return datetime.utcnow() + timedelta(hours=8)
  19. def format_time(dt: datetime) -> str:
  20. return dt.strftime("%Y-%m-%d %H:%M")
  21. def classify_error(step_name: str, job_name: str) -> str:
  22. """错误类型分类"""
  23. step_lower = step_name.lower()
  24. if any(x in step_lower for x in ["test", "suite", "pytest", "unittest"]):
  25. return "TEST_FAILURE"
  26. if "lint" in step_lower or "flake8" in step_lower:
  27. return "LINT_ERROR"
  28. if "build" in step_lower or "compile" in step_lower:
  29. return "BUILD_ERROR"
  30. if "deploy" in step_lower or "upload" in step_lower or "publish" in step_lower:
  31. return "DEPLOY_ERROR"
  32. if "check" in step_lower or "validate" in step_lower or "verify" in step_lower:
  33. return "VALIDATION_ERROR"
  34. if "generate" in step_lower or "render" in step_lower:
  35. return "GENERATION_ERROR"
  36. return "UNKNOWN"
  37. def generate_report():
  38. """生成符合最新样式的故障聚合报告"""
  39. results = load_monitoring_results()
  40. if not results:
  41. return
  42. failed_workflows = [r for r in results if r.get('conclusion') == 'failure']
  43. if not failed_workflows:
  44. print("No failed workflows to report")
  45. return
  46. now = get_beijing_time()
  47. date_str = now.strftime("%Y%m%d")
  48. # 时间范围
  49. created_times = [
  50. datetime.fromisoformat(r["created_at"].replace("Z", "+00:00")) + timedelta(hours=8)
  51. for r in failed_workflows
  52. ]
  53. updated_times = [
  54. datetime.fromisoformat(r["updated_at"].replace("Z", "+00:00")) + timedelta(hours=8)
  55. for r in failed_workflows
  56. ]
  57. start_time = min(created_times)
  58. end_time = max(updated_times)
  59. total = len(results)
  60. failed_count = len(failed_workflows)
  61. success_rate = 0.0 if total == 0 else round((total - failed_count) / total * 100, 1)
  62. # === 第一行:用于 JS 提取标题(必须)===
  63. report = f"# {date_str}_ci_integration-failed-report\n\n"
  64. # === 第二行:用户看到的主标题(H1)===
  65. report += f"# 🚨 {date_str} GitHub Actions 故障聚合报告 | Incident Aggregate Report\n\n"
  66. # === 执行概览 ===
  67. report += f"## 执行概览 | Executive Summary\n"
  68. report += f"- **监控时间范围 | Monitoring Period**: {format_time(start_time)}–{format_time(end_time)} (UTC+8)\n"
  69. report += f"- **检测到失败运行 | Failed Runs Detected**: {failed_count}个\n"
  70. report += f"- **成功率 | Success Rate**: {success_rate}% \n\n"
  71. # === 故障详情 ===
  72. report += f"## 🔍 故障详情 | Failure Details\n\n"
  73. for wf in failed_workflows:
  74. run_id = wf.get("run_id", "N/A")
  75. name = wf["name"]
  76. html_url = wf.get("html_url", "#")
  77. details = wf.get("failure_details", [])
  78. report += f"**📌 Run-{run_id}** | [{name}]({html_url})\n"
  79. if not details:
  80. report += "└─ 无失败作业详情 | No details of failed jobs\n\n"
  81. continue
  82. failed_jobs = [j for j in details if j.get("steps")]
  83. for i, job in enumerate(failed_jobs):
  84. job_name = job["name"]
  85. steps = job["steps"]
  86. job_prefix = "└─" if i == len(failed_jobs) - 1 else "├─"
  87. report += f"{job_prefix} **失败作业 | Failed Job**: {job_name}\n"
  88. for j, step in enumerate(steps):
  89. step_name = step["name"]
  90. step_num = step["number"]
  91. error_type = classify_error(step_name, job_name)
  92. step_prefix = " └─" if j == len(steps) - 1 else " ├─"
  93. report += f"{step_prefix} **失败步骤 | Failed Step**: {step_name} (Step {step_num})\n"
  94. indent = " " if j == len(steps) - 1 else " │ "
  95. report += f"{indent}**错误类型 | Error Type**: `{error_type}`\n"
  96. report += "\n"
  97. # === Team Collaboration & Support ===
  98. report += f"## 👥 团队协作与支持 | Team Collaboration & Support\n\n"
  99. report += f"请求维护支持:本报告需要RT-Thread官方团队的专业经验进行审核与指导。 \n"
  100. report += f"Call for Maintenance Support: This report requires the expertise of the RT-Thread official team for review and guidance.\n\n"
  101. report += f"提审负责人:@Rbb666 @kurisaW\n"
  102. report += f"Requested Reviewers from RT-Thread: @Rbb666 @kurisaW\n\n"
  103. report += f"烦请尽快关注此事,万分感谢。 \n"
  104. report += f"Your prompt attention to this matter is greatly appreciated.\n"
  105. # 保存
  106. try:
  107. with open("failure_details.md", "w", encoding="utf-8") as f:
  108. f.write(report.rstrip() + "\n")
  109. print("Report generated: failure_details.md")
  110. print(f"Report size: {os.path.getsize('failure_details.md')} bytes")
  111. except Exception as e:
  112. print(f"Error writing report: {e}")
  113. if __name__ == "__main__":
  114. generate_report()