From 29c5edd932cdd0555b6b84134f4f9f6b15963b14 Mon Sep 17 00:00:00 2001 From: Nicole Rappe Date: Wed, 22 Oct 2025 04:31:07 -0600 Subject: [PATCH] Refine Engine bootstrap logging --- Data/Engine/__init__.py | 11 ++++ Data/Engine/bootstrapper.py | 56 ++++++++++++++++ Data/Engine/builders/__init__.py | 5 ++ Data/Engine/config/__init__.py | 12 ++++ Data/Engine/config/environment.py | 87 +++++++++++++++++++++++++ Data/Engine/config/logging.py | 71 ++++++++++++++++++++ Data/Engine/domain/__init__.py | 5 ++ Data/Engine/integrations/__init__.py | 5 ++ Data/Engine/interfaces/__init__.py | 11 ++++ Data/Engine/interfaces/http/__init__.py | 18 +++++ Data/Engine/interfaces/ws/__init__.py | 34 ++++++++++ Data/Engine/repositories/__init__.py | 5 ++ Data/Engine/server.py | 51 +++++++++++++++ Data/Engine/services/__init__.py | 5 ++ 14 files changed, 376 insertions(+) create mode 100644 Data/Engine/__init__.py create mode 100644 Data/Engine/bootstrapper.py create mode 100644 Data/Engine/builders/__init__.py create mode 100644 Data/Engine/config/__init__.py create mode 100644 Data/Engine/config/environment.py create mode 100644 Data/Engine/config/logging.py create mode 100644 Data/Engine/domain/__init__.py create mode 100644 Data/Engine/integrations/__init__.py create mode 100644 Data/Engine/interfaces/__init__.py create mode 100644 Data/Engine/interfaces/http/__init__.py create mode 100644 Data/Engine/interfaces/ws/__init__.py create mode 100644 Data/Engine/repositories/__init__.py create mode 100644 Data/Engine/server.py create mode 100644 Data/Engine/services/__init__.py diff --git a/Data/Engine/__init__.py b/Data/Engine/__init__.py new file mode 100644 index 0000000..afc216c --- /dev/null +++ b/Data/Engine/__init__.py @@ -0,0 +1,11 @@ +"""Borealis Engine package. + +This namespace contains the next-generation server implementation. +""" + +from __future__ import annotations + +__all__ = [ + "bootstrapper", + "server", +] diff --git a/Data/Engine/bootstrapper.py b/Data/Engine/bootstrapper.py new file mode 100644 index 0000000..eabd728 --- /dev/null +++ b/Data/Engine/bootstrapper.py @@ -0,0 +1,56 @@ +"""Entrypoint for the Borealis Engine server.""" + +from __future__ import annotations + +from dataclasses import dataclass +from typing import Optional + +from flask import Flask + +from .config import EngineSettings, configure_logging, load_environment +from .interfaces import create_socket_server, register_http_interfaces +from .server import create_app + + +@dataclass(frozen=True, slots=True) +class EngineRuntime: + """Aggregated runtime context produced by :func:`bootstrap`.""" + + app: Flask + settings: EngineSettings + socketio: Optional[object] + + +def bootstrap() -> EngineRuntime: + """Construct the Flask application and supporting infrastructure.""" + + settings = load_environment() + logger = configure_logging(settings) + logger.info("bootstrap-started") + app = create_app(settings) + register_http_interfaces(app) + socketio = create_socket_server(app, settings) + logger.info("bootstrap-complete") + return EngineRuntime(app=app, settings=settings, socketio=socketio) + + +def main() -> None: + runtime = bootstrap() + socketio = runtime.socketio + if socketio is not None: + socketio.run( # type: ignore[call-arg] + runtime.app, + host=runtime.settings.host, + port=runtime.settings.port, + debug=runtime.settings.debug, + ) + else: + runtime.app.run( + host=runtime.settings.host, + port=runtime.settings.port, + debug=runtime.settings.debug, + ) + + +if __name__ == "__main__": # pragma: no cover - manual execution + main() diff --git a/Data/Engine/builders/__init__.py b/Data/Engine/builders/__init__.py new file mode 100644 index 0000000..2608419 --- /dev/null +++ b/Data/Engine/builders/__init__.py @@ -0,0 +1,5 @@ +"""Builder utilities for constructing immutable Engine aggregates.""" + +from __future__ import annotations + +__all__: list[str] = [] diff --git a/Data/Engine/config/__init__.py b/Data/Engine/config/__init__.py new file mode 100644 index 0000000..8f382e9 --- /dev/null +++ b/Data/Engine/config/__init__.py @@ -0,0 +1,12 @@ +"""Configuration primitives for the Borealis Engine.""" + +from __future__ import annotations + +from .environment import EngineSettings, load_environment +from .logging import configure_logging + +__all__ = [ + "EngineSettings", + "load_environment", + "configure_logging", +] diff --git a/Data/Engine/config/environment.py b/Data/Engine/config/environment.py new file mode 100644 index 0000000..ad8e8b8 --- /dev/null +++ b/Data/Engine/config/environment.py @@ -0,0 +1,87 @@ +"""Environment detection for the Borealis Engine.""" + +from __future__ import annotations + +import os +from dataclasses import dataclass +from pathlib import Path +from typing import Iterable, Tuple + + +@dataclass(frozen=True, slots=True) +class EngineSettings: + """Immutable container describing the Engine runtime configuration.""" + + project_root: Path + database_path: Path + static_root: Path + cors_allowed_origins: Tuple[str, ...] + secret_key: str + debug: bool + host: str + port: int + + @property + def logs_root(self) -> Path: + """Return the directory where Engine-specific logs should live.""" + + return self.project_root / "Logs" / "Server" + + +def _resolve_project_root() -> Path: + candidate = os.getenv("BOREALIS_ROOT") + if candidate: + return Path(candidate).expanduser().resolve() + return Path(__file__).resolve().parents[2] + + +def _resolve_database_path(project_root: Path) -> Path: + candidate = os.getenv("BOREALIS_DATABASE_PATH") + if candidate: + return Path(candidate).expanduser().resolve() + return (project_root / "database.db").resolve() + + +def _resolve_static_root(project_root: Path) -> Path: + candidate = os.getenv("BOREALIS_STATIC_ROOT") + if candidate: + return Path(candidate).expanduser().resolve() + return (project_root / "Data" / "Server" / "dist").resolve() + + +def _parse_origins(raw: str | None) -> Tuple[str, ...]: + if not raw: + return ("*",) + parts: Iterable[str] = (segment.strip() for segment in raw.split(",")) + filtered = tuple(part for part in parts if part) + return filtered or ("*",) + + +def load_environment() -> EngineSettings: + """Load Engine settings from environment variables and filesystem hints.""" + + project_root = _resolve_project_root() + database_path = _resolve_database_path(project_root) + static_root = _resolve_static_root(project_root) + cors_allowed_origins = _parse_origins(os.getenv("BOREALIS_CORS_ALLOWED_ORIGINS")) + secret_key = os.getenv("BOREALIS_FLASK_SECRET_KEY", "change-me") + debug = os.getenv("BOREALIS_DEBUG", "false").lower() in {"1", "true", "yes", "on"} + host = os.getenv("BOREALIS_HOST", "127.0.0.1") + try: + port = int(os.getenv("BOREALIS_PORT", "5000")) + except ValueError: + port = 5000 + + return EngineSettings( + project_root=project_root, + database_path=database_path, + static_root=static_root, + cors_allowed_origins=cors_allowed_origins, + secret_key=secret_key, + debug=debug, + host=host, + port=port, + ) + + +__all__ = ["EngineSettings", "load_environment"] diff --git a/Data/Engine/config/logging.py b/Data/Engine/config/logging.py new file mode 100644 index 0000000..f07b764 --- /dev/null +++ b/Data/Engine/config/logging.py @@ -0,0 +1,71 @@ +"""Logging bootstrap helpers for the Borealis Engine.""" + +from __future__ import annotations + +import logging +from logging.handlers import TimedRotatingFileHandler +from pathlib import Path + +from .environment import EngineSettings + + +_ENGINE_LOGGER_NAME = "borealis.engine" +_SERVICE_NAME = "engine" +_DEFAULT_FORMAT = "%(asctime)s-" + _SERVICE_NAME + "-%(message)s" + + +def _handler_already_attached(logger: logging.Logger, log_path: Path) -> bool: + for handler in logger.handlers: + if isinstance(handler, TimedRotatingFileHandler): + handler_path = Path(getattr(handler, "baseFilename", "")) + if handler_path == log_path: + return True + return False + + +def _build_handler(log_path: Path) -> TimedRotatingFileHandler: + handler = TimedRotatingFileHandler( + log_path, + when="midnight", + backupCount=30, + encoding="utf-8", + ) + handler.setLevel(logging.INFO) + handler.setFormatter(logging.Formatter(_DEFAULT_FORMAT)) + return handler + + +def configure_logging(settings: EngineSettings) -> logging.Logger: + """Configure a rotating log handler for the Engine.""" + + logs_root = settings.logs_root + logs_root.mkdir(parents=True, exist_ok=True) + log_path = logs_root / "engine.log" + + logger = logging.getLogger(_ENGINE_LOGGER_NAME) + logger.setLevel(logging.INFO if not settings.debug else logging.DEBUG) + + if not _handler_already_attached(logger, log_path): + handler = _build_handler(log_path) + logger.addHandler(handler) + logger.propagate = False + + # Also ensure the root logger follows suit so third-party modules inherit the handler. + root_logger = logging.getLogger() + if not _handler_already_attached(root_logger, log_path): + handler = _build_handler(log_path) + root_logger.addHandler(handler) + if root_logger.level == logging.WARNING: + # Default level is WARNING; lower it to INFO so our handler captures application messages. + root_logger.setLevel(logging.INFO if not settings.debug else logging.DEBUG) + + # Quieten overly chatty frameworks unless debugging is explicitly requested. + if not settings.debug: + logging.getLogger("werkzeug").setLevel(logging.WARNING) + logging.getLogger("engineio").setLevel(logging.WARNING) + logging.getLogger("socketio").setLevel(logging.WARNING) + + return logger + + +__all__ = ["configure_logging"] diff --git a/Data/Engine/domain/__init__.py b/Data/Engine/domain/__init__.py new file mode 100644 index 0000000..3bcd0ef --- /dev/null +++ b/Data/Engine/domain/__init__.py @@ -0,0 +1,5 @@ +"""Pure value objects and enums for the Borealis Engine.""" + +from __future__ import annotations + +__all__: list[str] = [] diff --git a/Data/Engine/integrations/__init__.py b/Data/Engine/integrations/__init__.py new file mode 100644 index 0000000..f2037f9 --- /dev/null +++ b/Data/Engine/integrations/__init__.py @@ -0,0 +1,5 @@ +"""External system adapters for the Borealis Engine.""" + +from __future__ import annotations + +__all__: list[str] = [] diff --git a/Data/Engine/interfaces/__init__.py b/Data/Engine/interfaces/__init__.py new file mode 100644 index 0000000..1db05a4 --- /dev/null +++ b/Data/Engine/interfaces/__init__.py @@ -0,0 +1,11 @@ +"""Interface adapters (HTTP, WebSocket, etc.) for the Borealis Engine.""" + +from __future__ import annotations + +from .http import register_http_interfaces +from .ws import create_socket_server + +__all__ = [ + "register_http_interfaces", + "create_socket_server", +] diff --git a/Data/Engine/interfaces/http/__init__.py b/Data/Engine/interfaces/http/__init__.py new file mode 100644 index 0000000..75d4b5f --- /dev/null +++ b/Data/Engine/interfaces/http/__init__.py @@ -0,0 +1,18 @@ +"""HTTP interface registration for the Borealis Engine.""" + +from __future__ import annotations + +from flask import Flask + + +def register_http_interfaces(app: Flask) -> None: + """Attach HTTP blueprints to *app*. + + The implementation is intentionally minimal for the initial scaffolding. + """ + + # Future phases will import and register blueprints here. + return None + + +__all__ = ["register_http_interfaces"] diff --git a/Data/Engine/interfaces/ws/__init__.py b/Data/Engine/interfaces/ws/__init__.py new file mode 100644 index 0000000..84c7476 --- /dev/null +++ b/Data/Engine/interfaces/ws/__init__.py @@ -0,0 +1,34 @@ +"""WebSocket interface factory for the Borealis Engine.""" + +from __future__ import annotations + +from typing import Optional + +from flask import Flask + +from ...config import EngineSettings + +try: # pragma: no cover - import guard + from flask_socketio import SocketIO +except Exception: # pragma: no cover - optional dependency + SocketIO = None # type: ignore[assignment] + + +def create_socket_server(app: Flask, settings: EngineSettings) -> Optional[SocketIO]: + """Create a Socket.IO server bound to *app* if dependencies are available.""" + + if SocketIO is None: + return None + + cors_allowed = settings.cors_allowed_origins or ("*",) + socketio = SocketIO( + app, + cors_allowed_origins=cors_allowed, + async_mode=None, + logger=False, + engineio_logger=False, + ) + return socketio + + +__all__ = ["create_socket_server"] diff --git a/Data/Engine/repositories/__init__.py b/Data/Engine/repositories/__init__.py new file mode 100644 index 0000000..cacb075 --- /dev/null +++ b/Data/Engine/repositories/__init__.py @@ -0,0 +1,5 @@ +"""Persistence adapters for the Borealis Engine.""" + +from __future__ import annotations + +__all__: list[str] = [] diff --git a/Data/Engine/server.py b/Data/Engine/server.py new file mode 100644 index 0000000..dbbdd62 --- /dev/null +++ b/Data/Engine/server.py @@ -0,0 +1,51 @@ +"""Flask application factory for the Borealis Engine.""" + +from __future__ import annotations + +from pathlib import Path + +from flask import Flask +from flask_cors import CORS +from werkzeug.middleware.proxy_fix import ProxyFix + +from .config import EngineSettings + + +def _resolve_static_folder(static_root: Path) -> tuple[str | None, str]: + if static_root.exists(): + return str(static_root), "/" + return None, "/static" + + +def create_app(settings: EngineSettings) -> Flask: + """Create the Flask application instance for the Engine.""" + + static_folder, static_url_path = _resolve_static_folder(settings.static_root) + app = Flask( + __name__, + static_folder=static_folder, + static_url_path=static_url_path, + ) + + app.config.update( + SECRET_KEY=settings.secret_key, + JSON_SORT_KEYS=False, + SESSION_COOKIE_HTTPONLY=True, + SESSION_COOKIE_SECURE=not settings.debug, + SESSION_COOKIE_SAMESITE="Lax", + ENGINE_DATABASE_PATH=str(settings.database_path), + ) + + # Respect upstream proxy headers when Borealis is hosted behind a TLS terminator. + app.wsgi_app = ProxyFix(app.wsgi_app, x_for=1, x_proto=1, x_host=1) # type: ignore[assignment] + + CORS( + app, + resources={r"/*": {"origins": list(settings.cors_allowed_origins)}}, + supports_credentials=True, + ) + + return app + + +__all__ = ["create_app"] diff --git a/Data/Engine/services/__init__.py b/Data/Engine/services/__init__.py new file mode 100644 index 0000000..1b31cb3 --- /dev/null +++ b/Data/Engine/services/__init__.py @@ -0,0 +1,5 @@ +"""Application services for the Borealis Engine.""" + +from __future__ import annotations + +__all__: list[str] = []