bug_report.py 2.9 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495
  1. """Bug report endpoint for submitting user bug reports to GitHub."""
  2. import asyncio
  3. import logging
  4. from fastapi import APIRouter
  5. from pydantic import BaseModel
  6. from backend.app.api.routes.support import (
  7. _apply_log_level,
  8. _collect_support_info,
  9. _get_debug_setting,
  10. _get_recent_sanitized_logs,
  11. _set_debug_setting,
  12. )
  13. from backend.app.core.database import async_session
  14. from backend.app.services.bug_report import submit_report
  15. from backend.app.services.printer_manager import printer_manager
  16. router = APIRouter(prefix="/bug-report", tags=["bug-report"])
  17. logger = logging.getLogger(__name__)
  18. LOG_COLLECTION_SECONDS = 30
  19. class BugReportRequest(BaseModel):
  20. description: str
  21. email: str | None = None
  22. screenshot_base64: str | None = None
  23. include_support_info: bool = True
  24. class BugReportResponse(BaseModel):
  25. success: bool
  26. message: str
  27. issue_url: str | None = None
  28. issue_number: int | None = None
  29. async def _collect_debug_logs() -> str:
  30. """Enable debug logging, push all printers, wait, then collect logs."""
  31. # Check if debug was already enabled
  32. async with async_session() as db:
  33. was_debug, _ = await _get_debug_setting(db)
  34. # Enable debug logging
  35. if not was_debug:
  36. async with async_session() as db:
  37. await _set_debug_setting(db, True)
  38. _apply_log_level(True)
  39. logger.info("Bug report: temporarily enabled debug logging")
  40. # Send push_all to all connected printers
  41. for printer_id in list(printer_manager._clients.keys()):
  42. try:
  43. printer_manager.request_status_update(printer_id)
  44. except Exception:
  45. logger.debug("Failed to push_all for printer %s", printer_id)
  46. # Wait for logs to accumulate
  47. await asyncio.sleep(LOG_COLLECTION_SECONDS)
  48. # Collect logs
  49. logs = await _get_recent_sanitized_logs()
  50. # Restore previous log level if it wasn't debug before
  51. if not was_debug:
  52. async with async_session() as db:
  53. await _set_debug_setting(db, False)
  54. _apply_log_level(False)
  55. logger.info("Bug report: restored normal logging")
  56. return logs
  57. @router.post("/submit", response_model=BugReportResponse)
  58. async def submit_bug_report(report: BugReportRequest):
  59. """Submit a bug report. No auth required — anyone should be able to report bugs."""
  60. support_info = None
  61. if report.include_support_info:
  62. try:
  63. support_info = await _collect_support_info()
  64. logs = await _collect_debug_logs()
  65. if logs:
  66. support_info["recent_logs"] = logs
  67. except Exception:
  68. logger.exception("Failed to collect support info for bug report")
  69. result = await submit_report(
  70. description=report.description,
  71. reporter_email=report.email,
  72. screenshot_base64=report.screenshot_base64,
  73. support_info=support_info,
  74. )
  75. return BugReportResponse(**result)