-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add log config and custom API logger to log requests (#6)
uvicorn log access of API like ```sh api-server-1 | INFO: 192.168.65.1:41988 - "GET /swagger HTTP/1.1" 200 OK ``` but it's not helping, because it does not log the request body. - Following this issue encode/uvicorn#491, I made a custom log config to add logger name in log and adjust log format - Add a `middleware` of fastapi to log the request in a more detailed way. - Override HTTPExceptionHandler to create error log when API is not responding 200. https://fastapi.tiangolo.com/tutorial/handling-errors/#override-the-httpexception-error-handler - Hide uvicorn access log Now the log looks like: ```sh api-server-1 | 2024-12-09 09:35:13,363 - INFO - app.api_logger - [request_id: 7608958e-36a6-428e-8427-83ced6b9d7c3, method:POST, path: http://0.0.0.0:8000/login/] Request body: {"e_mail":"123","password":"string"}. api-server-1 | 2024-12-09 09:35:13,367 - INFO - app.routers.login - e_mail='123' password='string' api-server-1 | 2024-12-09 09:35:13,367 - ERROR - app.http_exc_handler - [request_id: 7608958e-36a6-428e-8427-83ced6b9d7c3, method:POST, path: http://0.0.0.0:8000/login/] Failed to handle request. status_code=404, detail=asdasdas ``` **What can be improved in the future:** - Instead of only outputting logs to console, we should also keep logs as files in disk and use logstash to send logs to es like real-world application does. - Response is not logged, but we may want to add it. APILoggerMiddleware has to build a dummy Response from the outgoing bytestreams, which is not good. So we may want to find another way to do so.
- Loading branch information
Showing
9 changed files
with
107 additions
and
7 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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) | ||
logger.info(f"{signin_request}") | ||
return SigninResponseSchema(success=True) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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") | ||
|
Empty file.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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}, | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -2,4 +2,5 @@ fastapi~=0.115.5 | |
pydantic~=2.10.2 | ||
motor | ||
uvicorn | ||
PyYAML | ||
pytest |