mirror of
https://github.com/bunny-lab-io/Borealis.git
synced 2025-10-26 15:21:57 -06:00
Refine Engine bootstrap logging
This commit is contained in:
11
Data/Engine/__init__.py
Normal file
11
Data/Engine/__init__.py
Normal file
@@ -0,0 +1,11 @@
|
||||
"""Borealis Engine package.
|
||||
|
||||
This namespace contains the next-generation server implementation.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
__all__ = [
|
||||
"bootstrapper",
|
||||
"server",
|
||||
]
|
||||
56
Data/Engine/bootstrapper.py
Normal file
56
Data/Engine/bootstrapper.py
Normal file
@@ -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()
|
||||
5
Data/Engine/builders/__init__.py
Normal file
5
Data/Engine/builders/__init__.py
Normal file
@@ -0,0 +1,5 @@
|
||||
"""Builder utilities for constructing immutable Engine aggregates."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
__all__: list[str] = []
|
||||
12
Data/Engine/config/__init__.py
Normal file
12
Data/Engine/config/__init__.py
Normal file
@@ -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",
|
||||
]
|
||||
87
Data/Engine/config/environment.py
Normal file
87
Data/Engine/config/environment.py
Normal file
@@ -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"]
|
||||
71
Data/Engine/config/logging.py
Normal file
71
Data/Engine/config/logging.py
Normal file
@@ -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"]
|
||||
5
Data/Engine/domain/__init__.py
Normal file
5
Data/Engine/domain/__init__.py
Normal file
@@ -0,0 +1,5 @@
|
||||
"""Pure value objects and enums for the Borealis Engine."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
__all__: list[str] = []
|
||||
5
Data/Engine/integrations/__init__.py
Normal file
5
Data/Engine/integrations/__init__.py
Normal file
@@ -0,0 +1,5 @@
|
||||
"""External system adapters for the Borealis Engine."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
__all__: list[str] = []
|
||||
11
Data/Engine/interfaces/__init__.py
Normal file
11
Data/Engine/interfaces/__init__.py
Normal file
@@ -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",
|
||||
]
|
||||
18
Data/Engine/interfaces/http/__init__.py
Normal file
18
Data/Engine/interfaces/http/__init__.py
Normal file
@@ -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"]
|
||||
34
Data/Engine/interfaces/ws/__init__.py
Normal file
34
Data/Engine/interfaces/ws/__init__.py
Normal file
@@ -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"]
|
||||
5
Data/Engine/repositories/__init__.py
Normal file
5
Data/Engine/repositories/__init__.py
Normal file
@@ -0,0 +1,5 @@
|
||||
"""Persistence adapters for the Borealis Engine."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
__all__: list[str] = []
|
||||
51
Data/Engine/server.py
Normal file
51
Data/Engine/server.py
Normal file
@@ -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"]
|
||||
5
Data/Engine/services/__init__.py
Normal file
5
Data/Engine/services/__init__.py
Normal file
@@ -0,0 +1,5 @@
|
||||
"""Application services for the Borealis Engine."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
__all__: list[str] = []
|
||||
Reference in New Issue
Block a user