Merge pull request 'logging feature' (#8) from feature/logging into dev
Reviewed-on: #8
This commit was merged in pull request #8.
This commit is contained in:
@@ -21,6 +21,8 @@ Thumbs.db
|
|||||||
*.env
|
*.env
|
||||||
#db
|
#db
|
||||||
*.db
|
*.db
|
||||||
|
#logs
|
||||||
|
logs/
|
||||||
|
|
||||||
#Примеры документов
|
#Примеры документов
|
||||||
input/
|
input/
|
||||||
|
|||||||
@@ -27,6 +27,9 @@ services:
|
|||||||
- type: bind
|
- type: bind
|
||||||
source: ../uploads
|
source: ../uploads
|
||||||
target: /home/excel-project/uploads
|
target: /home/excel-project/uploads
|
||||||
|
- type: bind
|
||||||
|
source: ../logs
|
||||||
|
target: /home/excel-project/logs
|
||||||
networks:
|
networks:
|
||||||
- backend
|
- backend
|
||||||
ports:
|
ports:
|
||||||
@@ -53,6 +56,9 @@ services:
|
|||||||
- type: bind
|
- type: bind
|
||||||
source: ../uploads
|
source: ../uploads
|
||||||
target: /home/excel-project/uploads
|
target: /home/excel-project/uploads
|
||||||
|
- type: bind
|
||||||
|
source: ../logs
|
||||||
|
target: /home/excel-project/logs
|
||||||
networks:
|
networks:
|
||||||
- backend
|
- backend
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ from pathlib import Path
|
|||||||
# import uvicorn
|
# import uvicorn
|
||||||
from fastapi import FastAPI
|
from fastapi import FastAPI
|
||||||
|
|
||||||
|
from src.logging.logger import LoggingMiddleware, ProcessingTimeMiddleware
|
||||||
from src.web.protected_routes.auth_routes import router as protected_router
|
from src.web.protected_routes.auth_routes import router as protected_router
|
||||||
from src.web.protected_routes.protected_user_action_routes import (
|
from src.web.protected_routes.protected_user_action_routes import (
|
||||||
router as protected_user_action_routes,
|
router as protected_user_action_routes,
|
||||||
@@ -16,8 +17,10 @@ async def lifespan(app: FastAPI):
|
|||||||
yield
|
yield
|
||||||
print("shutting down")
|
print("shutting down")
|
||||||
|
|
||||||
|
|
||||||
app=FastAPI(root_path="/", lifespan=lifespan)
|
app=FastAPI(root_path="/", lifespan=lifespan)
|
||||||
|
app.add_middleware(LoggingMiddleware)
|
||||||
|
app.add_middleware(ProcessingTimeMiddleware)
|
||||||
app.include_router(router=protected_router)
|
app.include_router(router=protected_router)
|
||||||
app.include_router(router=protected_user_action_routes)
|
app.include_router(router=protected_user_action_routes)
|
||||||
|
|
||||||
@@ -32,7 +35,8 @@ def create_dirs():
|
|||||||
dirs_to_create=("./DB",
|
dirs_to_create=("./DB",
|
||||||
"./uploads/upload",
|
"./uploads/upload",
|
||||||
"./uploads/upload_bad",
|
"./uploads/upload_bad",
|
||||||
"./uploads/upload_finished")
|
"./uploads/upload_finished",
|
||||||
|
"./logs")
|
||||||
|
|
||||||
for x in dirs_to_create:
|
for x in dirs_to_create:
|
||||||
Path(x).mkdir(parents=True, exist_ok=True)
|
Path(x).mkdir(parents=True, exist_ok=True)
|
||||||
Generated
+13
-1
@@ -1,5 +1,17 @@
|
|||||||
# This file is automatically @generated by Poetry 2.4.1 and should not be changed by hand.
|
# This file is automatically @generated by Poetry 2.4.1 and should not be changed by hand.
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "aiofiles"
|
||||||
|
version = "25.1.0"
|
||||||
|
description = "File support for asyncio."
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.9"
|
||||||
|
groups = ["main"]
|
||||||
|
files = [
|
||||||
|
{file = "aiofiles-25.1.0-py3-none-any.whl", hash = "sha256:abe311e527c862958650f9438e859c1fa7568a141b22abcd015e120e86a85695"},
|
||||||
|
{file = "aiofiles-25.1.0.tar.gz", hash = "sha256:a8d728f0a29de45dc521f18f07297428d56992a742f0cd2701ba86e44d23d5b2"},
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "aiosqlite"
|
name = "aiosqlite"
|
||||||
version = "0.22.1"
|
version = "0.22.1"
|
||||||
@@ -2263,4 +2275,4 @@ files = [
|
|||||||
[metadata]
|
[metadata]
|
||||||
lock-version = "2.1"
|
lock-version = "2.1"
|
||||||
python-versions = ">=3.13"
|
python-versions = ">=3.13"
|
||||||
content-hash = "e1edf156eafb0e85fb3c090f3f17595ef56216f6bb9a2881f553619a19d83f3f"
|
content-hash = "ab9787e2afb0885dd340270dcdff2b44a81cbf34bc03fc9ac1e9ffb1fdf476f4"
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ dependencies = [
|
|||||||
"python-multipart (>=0.0.32,<0.0.33)",
|
"python-multipart (>=0.0.32,<0.0.33)",
|
||||||
"aiosqlite (>=0.22.1,<0.23.0)",
|
"aiosqlite (>=0.22.1,<0.23.0)",
|
||||||
"greenlet (>=3.5.4,<4.0.0)",
|
"greenlet (>=3.5.4,<4.0.0)",
|
||||||
|
"aiofiles (>=25.1.0,<26.0.0)",
|
||||||
]
|
]
|
||||||
|
|
||||||
[tool.poetry.group.dev.dependencies]
|
[tool.poetry.group.dev.dependencies]
|
||||||
|
|||||||
@@ -0,0 +1,9 @@
|
|||||||
|
#logger decorators and middlewares
|
||||||
|
|
||||||
|
import logging
|
||||||
|
|
||||||
|
from .logger import LoggerDB
|
||||||
|
|
||||||
|
sql_logger = logging.getLogger("sqlalchemy.engine")
|
||||||
|
sql_logger.setLevel(logging.INFO)
|
||||||
|
sql_logger.addHandler(LoggerDB())
|
||||||
@@ -0,0 +1,66 @@
|
|||||||
|
import asyncio
|
||||||
|
import json
|
||||||
|
import logging
|
||||||
|
from time import gmtime, perf_counter, strftime
|
||||||
|
from typing import cast
|
||||||
|
|
||||||
|
import aiofiles
|
||||||
|
from fastapi import Request
|
||||||
|
from starlette.concurrency import iterate_in_threadpool
|
||||||
|
from starlette.middleware.base import BaseHTTPMiddleware
|
||||||
|
from starlette.responses import Response, StreamingResponse
|
||||||
|
|
||||||
|
|
||||||
|
class ProcessingTimeMiddleware(BaseHTTPMiddleware):
|
||||||
|
async def dispatch(self, request: Request, call_next)->Response:
|
||||||
|
start_time = perf_counter()
|
||||||
|
response = await call_next(request)
|
||||||
|
process_time = perf_counter() - start_time
|
||||||
|
response.headers["X-Process-Time"] = str(process_time)
|
||||||
|
return response
|
||||||
|
|
||||||
|
|
||||||
|
class LoggingMiddleware(BaseHTTPMiddleware):
|
||||||
|
async def dispatch(self, request: Request, call_next) -> Response:
|
||||||
|
current_time = strftime("%a, %d %b %Y %H:%M:%S +0000", gmtime())
|
||||||
|
file_time=strftime("%b_%Y", gmtime())
|
||||||
|
response = await call_next(request)
|
||||||
|
streaming_response = cast(StreamingResponse, response)
|
||||||
|
|
||||||
|
chunks = []
|
||||||
|
async for chunk in streaming_response.body_iterator:
|
||||||
|
if isinstance(chunk, str):
|
||||||
|
chunks.append(chunk.encode())
|
||||||
|
else:
|
||||||
|
chunks.append(bytes(chunk))
|
||||||
|
|
||||||
|
body_bytes = b"".join(chunks)
|
||||||
|
streaming_response.body_iterator = iterate_in_threadpool(iter([body_bytes]))
|
||||||
|
|
||||||
|
try:
|
||||||
|
body = json.loads(body_bytes)
|
||||||
|
body=body.get("detail", None)
|
||||||
|
except (json.JSONDecodeError, TypeError):
|
||||||
|
body = None
|
||||||
|
|
||||||
|
client_ip = request.headers.get('x-forwarded-for', '').split(',')[0].strip() or (request.client.host if request.client else 'unknown')
|
||||||
|
|
||||||
|
async with aiofiles.open(f"./logs/endpoints_log_{file_time}.txt", "a") as file:
|
||||||
|
await file.write(f"[{current_time}] [{response.status_code}] [{body}] [{client_ip}]\n")
|
||||||
|
|
||||||
|
return response
|
||||||
|
|
||||||
|
|
||||||
|
class LoggerDB(logging.Handler):
|
||||||
|
|
||||||
|
def emit(self, record: logging.LogRecord) -> None:
|
||||||
|
msg = self.format(record)
|
||||||
|
asyncio.create_task(self._write(msg))
|
||||||
|
|
||||||
|
async def _write(self, msg: str) -> None:
|
||||||
|
|
||||||
|
file_time = strftime("%b_%Y", gmtime())
|
||||||
|
current_time = strftime("%a, %d %b %Y %H:%M:%S +0000", gmtime())
|
||||||
|
|
||||||
|
async with aiofiles.open(f"./logs/sql_log_{file_time}.txt", "a") as f:
|
||||||
|
await f.write(f"[{current_time}] {msg}\n")
|
||||||
@@ -15,7 +15,7 @@ from sqlalchemy import (
|
|||||||
from sqlalchemy.ext.asyncio import create_async_engine
|
from sqlalchemy.ext.asyncio import create_async_engine
|
||||||
from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column, relationship
|
from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column, relationship
|
||||||
|
|
||||||
engine = create_async_engine("sqlite+aiosqlite:///DB/database.db", echo=True)
|
engine = create_async_engine("sqlite+aiosqlite:///DB/database.db")
|
||||||
|
|
||||||
'''remember as a boilerplate, or just cp/pst'''
|
'''remember as a boilerplate, or just cp/pst'''
|
||||||
class Model(DeclarativeBase):
|
class Model(DeclarativeBase):
|
||||||
|
|||||||
Reference in New Issue
Block a user