From 443d40d7b6f36f5daa5dfd81551ca57345be044a Mon Sep 17 00:00:00 2001 From: "MH.Dmitrii" Date: Mon, 24 Aug 2026 18:22:43 +0300 Subject: [PATCH] logging feature --- .gitignore | 2 + docker/compose-dev.yaml | 6 +++ main.py | 8 +++- poetry.lock | 14 +++++- pyproject.toml | 1 + src/logging/__init__.py | 9 ++++ src/logging/logger.py | 66 +++++++++++++++++++++++++++++ src/models/database_models/model.py | 2 +- 8 files changed, 104 insertions(+), 4 deletions(-) create mode 100644 src/logging/__init__.py create mode 100644 src/logging/logger.py diff --git a/.gitignore b/.gitignore index 4de5f17..c323f24 100644 --- a/.gitignore +++ b/.gitignore @@ -21,6 +21,8 @@ Thumbs.db *.env #db *.db +#logs +logs/ #Примеры документов input/ diff --git a/docker/compose-dev.yaml b/docker/compose-dev.yaml index 7846768..006b5ee 100644 --- a/docker/compose-dev.yaml +++ b/docker/compose-dev.yaml @@ -27,6 +27,9 @@ services: - type: bind source: ../uploads target: /home/excel-project/uploads + - type: bind + source: ../logs + target: /home/excel-project/logs networks: - backend ports: @@ -53,6 +56,9 @@ services: - type: bind source: ../uploads target: /home/excel-project/uploads + - type: bind + source: ../logs + target: /home/excel-project/logs networks: - backend restart: unless-stopped diff --git a/main.py b/main.py index af25e4e..19aa7c7 100644 --- a/main.py +++ b/main.py @@ -4,6 +4,7 @@ from pathlib import Path # import uvicorn 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.protected_user_action_routes import ( router as protected_user_action_routes, @@ -16,8 +17,10 @@ async def lifespan(app: FastAPI): yield print("shutting down") - + 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_user_action_routes) @@ -32,7 +35,8 @@ def create_dirs(): dirs_to_create=("./DB", "./uploads/upload", "./uploads/upload_bad", - "./uploads/upload_finished") + "./uploads/upload_finished", + "./logs") for x in dirs_to_create: Path(x).mkdir(parents=True, exist_ok=True) \ No newline at end of file diff --git a/poetry.lock b/poetry.lock index 3ad2723..ed3aef2 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,5 +1,17 @@ # 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]] name = "aiosqlite" version = "0.22.1" @@ -2263,4 +2275,4 @@ files = [ [metadata] lock-version = "2.1" python-versions = ">=3.13" -content-hash = "e1edf156eafb0e85fb3c090f3f17595ef56216f6bb9a2881f553619a19d83f3f" +content-hash = "ab9787e2afb0885dd340270dcdff2b44a81cbf34bc03fc9ac1e9ffb1fdf476f4" diff --git a/pyproject.toml b/pyproject.toml index b404a05..310aa65 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -23,6 +23,7 @@ dependencies = [ "python-multipart (>=0.0.32,<0.0.33)", "aiosqlite (>=0.22.1,<0.23.0)", "greenlet (>=3.5.4,<4.0.0)", + "aiofiles (>=25.1.0,<26.0.0)", ] [tool.poetry.group.dev.dependencies] diff --git a/src/logging/__init__.py b/src/logging/__init__.py new file mode 100644 index 0000000..46806e9 --- /dev/null +++ b/src/logging/__init__.py @@ -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()) \ No newline at end of file diff --git a/src/logging/logger.py b/src/logging/logger.py new file mode 100644 index 0000000..77a7d70 --- /dev/null +++ b/src/logging/logger.py @@ -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") diff --git a/src/models/database_models/model.py b/src/models/database_models/model.py index ddd0e19..a894999 100644 --- a/src/models/database_models/model.py +++ b/src/models/database_models/model.py @@ -15,7 +15,7 @@ from sqlalchemy import ( from sqlalchemy.ext.asyncio import create_async_engine 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''' class Model(DeclarativeBase): -- 2.54.0