From 393d573a9280c3e43a84e7c45b30d3a19ec9392b Mon Sep 17 00:00:00 2001 From: boholder Date: Fri, 1 Mar 2024 17:28:21 +0800 Subject: [PATCH] Separate fastapi app instance from main module to ensure there is only one app instance is generated ref: https://stackoverflow.com/questions/70300675/fastapi-uvicorn-run-always-create-3-instances-but-i-want-it-1-instance --- app/app.py | 50 ++++++++++++++++++++++++++++++++++++++++++++++++ app/main.py | 55 +++++------------------------------------------------ 2 files changed, 55 insertions(+), 50 deletions(-) create mode 100644 app/app.py diff --git a/app/app.py b/app/app.py new file mode 100644 index 0000000..d161753 --- /dev/null +++ b/app/app.py @@ -0,0 +1,50 @@ +import logging +import logging.config +import logging.handlers +from contextlib import asynccontextmanager + +from asgi_correlation_id import correlation_id, CorrelationIdMiddleware +from fastapi import FastAPI, HTTPException, Request +from fastapi.exception_handlers import http_exception_handler +from starlette.responses import Response + +import log_config +from routers import demo, heavy_task + + +def find_log_file_handler() -> logging.Handler: + # ref: https://stackoverflow.com/a/55400327/11397457 + l = logging.Logger.manager.loggerDict["uvicorn"] + for h in l.handlers: + if isinstance(h, logging.handlers.TimedRotatingFileHandler): + return h + raise Exception("Can not found file handler.") + + +@asynccontextmanager +async def lifespan(app: FastAPI): + # We must configure app logging after uvicorn started, thus the file handler should exists for reusing. + log_config.configure_app_logging(find_log_file_handler()) + global log + log = logging.getLogger(__name__) + yield + + +app = FastAPI(lifespan=lifespan) +app.include_router(demo.router) +app.include_router(heavy_task.router) +app.add_middleware(CorrelationIdMiddleware) + + +@app.exception_handler(Exception) +async def unhandled_exception_handler(request: Request, exc: Exception) -> Response: + """ + ref: https://github.com/snok/asgi-correlation-id?tab=readme-ov-file#fastapi-1 + """ + return await http_exception_handler( + request, + HTTPException( + 500, + 'Internal server error', + headers={'X-Request-ID': correlation_id.get() or ""} + )) diff --git a/app/main.py b/app/main.py index 1f9d4bc..66ae456 100644 --- a/app/main.py +++ b/app/main.py @@ -1,59 +1,12 @@ -import logging -import logging.config import logging.handlers -from contextlib import asynccontextmanager import uvicorn -from asgi_correlation_id import correlation_id, CorrelationIdMiddleware -from fastapi import FastAPI, HTTPException, Request -from fastapi.exception_handlers import http_exception_handler -from starlette.responses import Response from uvicorn.config import LOGGING_CONFIG import log_config -from routers import demo log: logging.Logger -FILE_HANDLER_NAME = "file_handler" - - -def find_log_file_handler() -> logging.Handler: - # ref: https://stackoverflow.com/a/55400327/11397457 - l = logging.Logger.manager.loggerDict["uvicorn"] - for h in l.handlers: - if isinstance(h, logging.handlers.TimedRotatingFileHandler): - return h - raise Exception("Can not found file handler.") - - -@asynccontextmanager -async def lifespan(app: FastAPI): - # We must configure app logging after uvicorn started, thus the file handler should exists for reusing. - log_config.configure_app_logging(find_log_file_handler()) - global log - log = logging.getLogger(__name__) - yield - - -app = FastAPI(lifespan=lifespan) -app.include_router(demo.router) -app.add_middleware(CorrelationIdMiddleware) - - -@app.exception_handler(Exception) -async def unhandled_exception_handler(request: Request, exc: Exception) -> Response: - """ - ref: https://github.com/snok/asgi-correlation-id?tab=readme-ov-file#fastapi-1 - """ - return await http_exception_handler( - request, - HTTPException( - 500, - 'Internal server error', - headers={'X-Request-ID': correlation_id.get() or ""} - )) - def configure_uvicorn_logging(): """Must configure the uvicorn.config.LOGGING_CONFIG within the same module that runs uvicorn.run().""" @@ -63,15 +16,17 @@ def configure_uvicorn_logging(): for formatter in LOGGING_CONFIG["formatters"].values(): formatter["fmt"] = log_config.LOG_FORMAT_PATTERN - LOGGING_CONFIG["handlers"][FILE_HANDLER_NAME] = log_config.FILE_HANDLER_CONFIG + file_handler_name = "file_handler" + LOGGING_CONFIG["handlers"][file_handler_name] = log_config.FILE_HANDLER_CONFIG + # There are three loggers in uvicorn default logging config: "uvicorn", "uvicorn.access", "uvicorn.error". # We need to add "file_handler" to logger "uvicorn" and "uvicorn.access" # since logger "uvicorn.error" will propagate the log to the other two. for logger in LOGGING_CONFIG["loggers"].values(): if "handlers" in logger: - logger["handlers"] += [FILE_HANDLER_NAME] + logger["handlers"] += [file_handler_name] if __name__ == "__main__": configure_uvicorn_logging() - uvicorn.run("main:app", port=8000, reload=True) + uvicorn.run("app:app", port=8000, reload=True)