Skip to content

Commit

Permalink
Separate fastapi app instance from main module to ensure there is onl…
Browse files Browse the repository at this point in the history
  • Loading branch information
boholder committed Mar 1, 2024
1 parent f186a66 commit 393d573
Show file tree
Hide file tree
Showing 2 changed files with 55 additions and 50 deletions.
50 changes: 50 additions & 0 deletions app/app.py
Original file line number Diff line number Diff line change
@@ -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 ""}
))
55 changes: 5 additions & 50 deletions app/main.py
Original file line number Diff line number Diff line change
@@ -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()."""
Expand All @@ -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)

0 comments on commit 393d573

Please sign in to comment.