diff --git a/app/__init__.py b/app/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/app/__main__.py b/app/__main__.py deleted file mode 100644 index 0ae0fbb..0000000 --- a/app/__main__.py +++ /dev/null @@ -1,7 +0,0 @@ -if __name__ == '__main__': - try: - from main import run - except ImportError: - from .main import run - - run() diff --git a/app/core/router.py b/app/core/router.py new file mode 100644 index 0000000..0d1a300 --- /dev/null +++ b/app/core/router.py @@ -0,0 +1,20 @@ +from fastapi import APIRouter, Request + +from fastapi.responses import HTMLResponse +from jinja2 import Environment, FileSystemLoader, select_autoescape + +router = APIRouter() + +# Inicjalizacja Jinja2 +templates_env = Environment( + loader=FileSystemLoader("app/templates"), + autoescape=select_autoescape(["html", "xml"]), +) + + +# Przykładowy endpoint HTML +@router.get("/", response_class=HTMLResponse) +async def index(request: Request) -> HTMLResponse: + template = templates_env.get_template("index.html") + html = template.render(title="Strona główna", request=request) + return HTMLResponse(content=html) diff --git a/app/main.py b/app/main.py index 4b13b8a..7eb04a5 100644 --- a/app/main.py +++ b/app/main.py @@ -1,41 +1,68 @@ -from fastapi import FastAPI, Request -from fastapi.responses import HTMLResponse -from jinja2 import Environment, FileSystemLoader, select_autoescape +import logging + +from fastapi import FastAPI -from app.api.v1 import router as api_v1_router from app.config import get_settings from app.core.core import WebhookProcessor - -# Inicjalizacja Jinja2 -templates_env = Environment( - loader=FileSystemLoader("app/templates"), - autoescape=select_autoescape(["html", "xml"]), -) - -app = FastAPI(title="Karl", version="0.1.0") - -# Rejestracja routera API pod /api/v1 -app.include_router(api_v1_router, prefix="/api/v1", tags=["v1"]) -# app.add_event_handler() - -webhook_service = WebhookProcessor() -print(webhook_service.health) +from app.util.logging import LoggingHandler, ExternalLoggingHandler -# Przykładowy endpoint HTML -@app.get("/", response_class=HTMLResponse) -async def index(request: Request) -> HTMLResponse: - template = templates_env.get_template("index.html") - html = template.render(title="Strona główna", request=request) - return HTMLResponse(content=html) +class KarlApplication: + def __init__(self) -> None: + self._set_logging() + app = FastAPI(title="Karl", version="0.1.0") + self._set_routes(app) + self._set_events(app) + self._init_services() + pass + + def _set_logging(self): + logging.basicConfig(level=logging.INFO, handlers=[LoggingHandler()]) + + loggers = ( + "uvicorn", + "uvicorn.access", + "uvicorn.error", + "fastapi", + "asyncio", + "starlette", + ) + external_handler = ExternalLoggingHandler() + for logger_name in loggers: + logging_logger = logging.getLogger(logger_name) + logging_logger.handlers = [external_handler] + logging_logger.propagate = False + + def _set_routes(self, app: FastAPI): + from app.core.router import router as core_router + app.include_router(core_router) + from app.api.v1 import router as api_v1_router + app.include_router(api_v1_router, prefix="/api/v1", tags=["v1"]) + pass + + def _set_events(self, app: FastAPI): + pass + + def _init_services(self): + logger = logging.getLogger(__name__) + + webhook_service = WebhookProcessor() + logger.info(webhook_service.health) -def run() -> None: +def app(): + return KarlApplication() + + +if __name__ == "__main__": import uvicorn + settings = get_settings() uvicorn.run( "app.main:app", + factory=True, host=settings.app.host, port=settings.app.port, reload=settings.app.reload, + log_config=None, ) diff --git a/app/util/logging.py b/app/util/logging.py new file mode 100644 index 0000000..29992b6 --- /dev/null +++ b/app/util/logging.py @@ -0,0 +1,59 @@ +from logging import Formatter, StreamHandler + + +class NamingCache: + def __init__(self): + self._cache = {} + + def __getitem__(self, key): + if key not in self._cache: + self._cache[key] = self.shorten(key) + return self._cache[key] + + @staticmethod + def shorten(logger_name: str) -> str: + target_length = 18 + if len(logger_name) > target_length: + parts = logger_name.split('.') + part = 0 + while len(logger_name) > target_length: + if part == len(parts) - 1: + logger_name = f'...{logger_name[-(target_length - 3):]}' + break + parts[part] = parts[part][0] + logger_name = '.'.join(parts) + part += 1 + + return logger_name.ljust(target_length) + + +class ApplicationFormatter(Formatter): + def __init__(self, handler_prefix: str = ''): + super().__init__() + self._logger_names = NamingCache() + self._handler_prefix = handler_prefix + + def format(self, record): + from datetime import datetime + timestamp = datetime.fromtimestamp(record.created).isoformat(sep=' ', timespec='milliseconds') + level = record.levelname.replace('WARNING', 'WARN').rjust(5) + thread_name = record.threadName.replace(' (', '#').replace(')', '').rjust(16)[-16:] # TODO: NamingCache? + logger_name = self._logger_names[f"{self._handler_prefix}{record.name}"] + message = record.getMessage() + formatted = f"{timestamp} {level} [{thread_name}] {logger_name} : {message}" + + if record.exc_info: + formatted += "\n" + self.formatException(record.exc_info) + + return formatted + + +class LoggingHandler(StreamHandler): + def __init__(self): + super().__init__() + self.setFormatter(ApplicationFormatter(handler_prefix='karl.')) + +class ExternalLoggingHandler(StreamHandler): + def __init__(self): + super().__init__() + self.setFormatter(ApplicationFormatter()) diff --git a/run.sh b/run.sh index c65ef75..8d2e2b9 100644 --- a/run.sh +++ b/run.sh @@ -1 +1 @@ -uv run uvicorn app.main:app --reload +uvicorn app.main:app --factory --reload