diff --git a/api-server/Dockerfile b/api-server/Dockerfile index d2f54bf..6eb6fba 100644 --- a/api-server/Dockerfile +++ b/api-server/Dockerfile @@ -2,4 +2,4 @@ FROM python:3.9-slim WORKDIR /app COPY .. . RUN pip install -r requirements.txt -CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000"] \ No newline at end of file +CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000", "--log-config", "log_conf.yaml"] \ No newline at end of file diff --git a/api-server/app/main.py b/api-server/app/main.py index fb36695..4abc977 100644 --- a/api-server/app/main.py +++ b/api-server/app/main.py @@ -1,5 +1,9 @@ from fastapi import FastAPI +from starlette.exceptions import HTTPException + from app.routers import student, login, user +from app.server.api_logger_middleware import APILoggerMiddleware +from app.server.http_exception_handler import http_exception_handler app = FastAPI( docs_url="/swagger", @@ -9,3 +13,8 @@ app.include_router(student.router) app.include_router(login.router) app.include_router(user.router) + +app.add_middleware(APILoggerMiddleware) + +app.add_exception_handler(HTTPException, http_exception_handler) + diff --git a/api-server/app/routers/login.py b/api-server/app/routers/login.py index 0a0c40c..8598505 100644 --- a/api-server/app/routers/login.py +++ b/api-server/app/routers/login.py @@ -1,16 +1,18 @@ +import logging from fastapi import APIRouter - from app.schema.loginschema import LoginRequestSchema, LoginResponseSchema, SigninResponseSchema, SigninRequestSchema router = APIRouter(prefix="/login", tags=["login"]) +logger = logging.getLogger(__name__) @router.post("/", response_model=LoginResponseSchema) async def login(login_request: LoginRequestSchema): - print(f"{login_request}") + logger.info(f"{login_request}") return LoginResponseSchema(token="nidetoken", user_id="niyeye") + @router.post("/signin", response_model=SigninResponseSchema) async def signin(signin_request: SigninRequestSchema): - print(f"{signin_request}") - return SigninResponseSchema(success=True) \ No newline at end of file + logger.info(f"{signin_request}") + return SigninResponseSchema(success=True) diff --git a/api-server/app/routers/user.py b/api-server/app/routers/user.py index e1d363a..54cb560 100644 --- a/api-server/app/routers/user.py +++ b/api-server/app/routers/user.py @@ -1,17 +1,18 @@ +import logging from fastapi import APIRouter from app.schema.userschema import UpdateUserinfoRequestSchema, UpdateUserinfoResponseSchema, GetUserinfoResponseSchema router = APIRouter(prefix="/userinfo", tags=["userinfo"]) +logger = logging.getLogger(__name__) @router.post("/", response_model=UpdateUserinfoResponseSchema) async def update_user(userinfo_request: UpdateUserinfoRequestSchema): - print(f"{userinfo_request}") + logger.info(f"{userinfo_request}") return UpdateUserinfoResponseSchema(success=True) @router.get("/{user_id}", response_model=GetUserinfoResponseSchema) async def get_user(user_id: str): return GetUserinfoResponseSchema(display_name="lulu", tel="23", address="tutu") - diff --git a/api-server/app/server/__init__.py b/api-server/app/server/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/api-server/app/server/api_logger_middleware.py b/api-server/app/server/api_logger_middleware.py new file mode 100644 index 0000000..0c983c6 --- /dev/null +++ b/api-server/app/server/api_logger_middleware.py @@ -0,0 +1,35 @@ +import json +import logging +import uuid +from fastapi import Request +from starlette.middleware.base import BaseHTTPMiddleware +import time + +# Initialize the logger +logger = logging.getLogger("app.api_logger") + + +# Custom Middleware for Logging on API request +class APILoggerMiddleware(BaseHTTPMiddleware): + async def dispatch(self, request: Request, call_next): + # Generate a unique request_id + request_id = str(uuid.uuid4()) + request.state.request_id = request_id # Attach to the request state + + # Log the incoming request + start_time = time.time() + body = await request.body() + try: + # Try to load JSON and log it in compact form + body_json = json.loads(body) + compact_body = json.dumps(body_json, separators=(",", ":")) + except json.JSONDecodeError: + # Fallback to plain text if not valid JSON + compact_body = body.decode("utf-8") + logger.info( + f"[request_id: {request_id}, method:{request.method}, path: {request.url}] " + f"Request body: {compact_body}." + ) + + response = await call_next(request) + return response diff --git a/api-server/app/server/http_exception_handler.py b/api-server/app/server/http_exception_handler.py new file mode 100644 index 0000000..8d2de07 --- /dev/null +++ b/api-server/app/server/http_exception_handler.py @@ -0,0 +1,18 @@ +import logging + +from fastapi.responses import JSONResponse + +logger = logging.getLogger("app.http_exc_handler") + + +async def http_exception_handler(request, exc): + logger.error( + f"[request_id: {request.state.request_id}, method:{request.method}, path: {request.url}] " + f"Failed to handle request. " + f"status_code={exc.status_code}, detail={exc.detail} " + ) + # Return the original response to the client + return JSONResponse( + status_code=exc.status_code, + content={"detail": exc.detail}, + ) diff --git a/api-server/log_conf.yaml b/api-server/log_conf.yaml new file mode 100644 index 0000000..611d5e8 --- /dev/null +++ b/api-server/log_conf.yaml @@ -0,0 +1,34 @@ +version: 1 +disable_existing_loggers: False +formatters: + default: + # "()": uvicorn.logging.DefaultFormatter + format: '%(asctime)s - %(levelname)-8s - %(name)-20s - %(message)s' + access: + # "()": uvicorn.logging.AccessFormatter + format: '%(asctime)s - %(levelname)-8s - %(name)-20s - %(message)s' +handlers: + default: + formatter: default + class: logging.StreamHandler + stream: ext://sys.stderr + access: + formatter: access + class: logging.StreamHandler + stream: ext://sys.stdout +loggers: + uvicorn.error: + level: INFO + handlers: + - default + propagate: no + uvicorn.access: + level: WARN # only log when access log is WARN level to reduce noise + handlers: + - access + propagate: no +root: + level: INFO + handlers: + - default + propagate: no \ No newline at end of file diff --git a/api-server/requirements.txt b/api-server/requirements.txt index c552c0f..9a81ef4 100644 --- a/api-server/requirements.txt +++ b/api-server/requirements.txt @@ -2,4 +2,5 @@ fastapi~=0.115.5 pydantic~=2.10.2 motor uvicorn +PyYAML pytest \ No newline at end of file