__init__.py 4.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102
  1. """
  2. __init__.py -
  3. auther: tanlie
  4. date: 2026/8/30
  5. """
  6. import logging
  7. import os
  8. from logging.handlers import RotatingFileHandler
  9. import traceback
  10. from fastapi import FastAPI, Request, HTTPException
  11. from fastapi.exceptions import RequestValidationError
  12. from fastapi.responses import JSONResponse
  13. from app.mobile import register_mobile_routes
  14. from app.core.response import error_response
  15. # Configure logging: write to logs/app.log (rotating) and also to console. Avoid duplicate handlers.
  16. logger = logging.getLogger("app")
  17. if not logger.handlers:
  18. # Determine logs directory relative to this file (project-root/app/../logs)
  19. base_dir = os.path.abspath(os.path.join(os.path.dirname(__file__), ".."))
  20. log_dir = os.path.join(base_dir, "logs")
  21. os.makedirs(log_dir, exist_ok=True)
  22. log_file = os.path.join(log_dir, "app.log")
  23. formatter = logging.Formatter("%(asctime)s %(levelname)s %(name)s: %(message)s")
  24. # Rotating file handler
  25. file_handler = RotatingFileHandler(log_file, maxBytes=10 * 1024 * 1024, backupCount=7, encoding="utf-8")
  26. file_handler.setFormatter(formatter)
  27. file_handler.setLevel(logging.INFO)
  28. logger.addHandler(file_handler)
  29. # Console handler
  30. console_handler = logging.StreamHandler()
  31. console_handler.setFormatter(formatter)
  32. console_handler.setLevel(logging.INFO)
  33. logger.addHandler(console_handler)
  34. logger.setLevel(logging.INFO)
  35. def create_app():
  36. app = FastAPI()
  37. register_mobile_routes(app)
  38. @app.exception_handler(RequestValidationError)
  39. async def validation_exception_handler(request: Request, exc: RequestValidationError):
  40. # Build a friendly, JSON-serializable summary of validation errors
  41. errors = []
  42. for e in exc.errors():
  43. loc = e.get("loc", [])
  44. # remove leading location indicators like 'body', 'query', 'path', 'header'
  45. if loc and len(loc) > 0 and str(loc[0]) in ("body", "query", "path", "header"):
  46. loc_parts = loc[1:]
  47. else:
  48. loc_parts = loc
  49. # join remaining location parts into a dotted path for readability
  50. loc_str = ".".join([str(p) for p in loc_parts if p is not None]) if loc_parts else ""
  51. errors.append({"field": loc_str, "message": e.get("msg", "")})
  52. # Log validation details for debugging
  53. logger.info("Validation error for %s: %s", request.url.path, errors)
  54. return JSONResponse(
  55. status_code=422,
  56. content=error_response(msg="参数校验失败", code=422, data=errors),
  57. )
  58. @app.exception_handler(HTTPException)
  59. async def http_exception_handler(request: Request, exc: HTTPException):
  60. # Use HTTPException's status_code and detail
  61. detail = exc.detail if exc.detail is not None else ""
  62. logger.warning("HTTPException for %s: %s", request.url.path, detail)
  63. return JSONResponse(
  64. status_code=getattr(exc, "status_code", 500),
  65. content=error_response(msg=str(detail), code=getattr(exc, "status_code", 500)),
  66. )
  67. @app.exception_handler(Exception)
  68. async def generic_exception_handler(request: Request, exc: Exception):
  69. # Map common ValueError to a friendlier 400 parameter error
  70. if isinstance(exc, ValueError):
  71. # Log with stack for debugging
  72. tb = "".join(traceback.format_exception(type(exc), exc, exc.__traceback__))
  73. logger.warning("ValueError on %s: %s\n%s", request.url.path, str(exc), tb)
  74. return JSONResponse(
  75. status_code=400,
  76. content=error_response(msg=str(exc) or "参数错误", code=400),
  77. )
  78. # Generic fallback to avoid leaking internal stack traces
  79. # Log full stack trace for internal debugging
  80. tb = "".join(traceback.format_exception(type(exc), exc, exc.__traceback__))
  81. logger.error("Unhandled exception on %s: %s\n%s", request.url.path, str(exc), tb)
  82. return JSONResponse(
  83. status_code=500,
  84. content=error_response(msg="服务器内部错误", code=500),
  85. )
  86. return app