Integrate logging

This commit is contained in:
Brian Wiborg 2025-10-08 12:45:27 +02:00
parent e848601f79
commit 67d89a94f4
No known key found for this signature in database
4 changed files with 66 additions and 13 deletions

View file

@ -9,6 +9,9 @@ import typer
import uvicorn
from ohmyapi.core import runtime, scaffolding
from ohmyapi.core.logging import setup_logging
logger = setup_logging()
app = typer.Typer(
help="OhMyAPI — Django-flavored FastAPI scaffolding with tightly integrated TortoiseORM."
@ -19,18 +22,24 @@ app = typer.Typer(
def startproject(name: str):
"""Create a new OhMyAPI project in the given directory."""
scaffolding.startproject(name)
logger.info(f"✅ Project '{name}' created successfully.")
logger.info(f"🔧 Next, configure your project in {name}/settings.py")
@app.command()
def startapp(app_name: str, root: str = "."):
"""Create a new app with the given name in your OhMyAPI project."""
scaffolding.startapp(app_name, root)
print(f"✅ App '{app_name}' created in project '{root}' successfully.")
print(f"🔧 Remember to add '{app_name}' to your INSTALLED_APPS!")
@app.command()
def dockerize(root: str = "."):
"""Create template Dockerfile and docker-compose.yml."""
scaffolding.copy_static("docker", root)
logger.info(f"✅ Templates created successfully.")
logger.info(f"🔧 Next, run `docker compose up -d --build`")
@app.command()
@ -42,7 +51,7 @@ def serve(root: str = ".", host="127.0.0.1", port=8000):
project = runtime.Project(project_path)
app_instance = project.app()
app_instance = project.configure_app(app_instance)
uvicorn.run(app_instance, host=host, port=int(port), reload=False)
uvicorn.run(app_instance, host=host, port=int(port), reload=False, log_config=None)
@app.command()
@ -107,6 +116,8 @@ def makemigrations(app: str = "*", name: str = "auto", root: str = "."):
asyncio.run(project.makemigrations(app_label=app, name=name))
else:
asyncio.run(project.makemigrations(app_label=app, name=name))
logger.info(f"✅ Migrations created successfully.")
logger.info(f"🔧 To migrate the DB, run `ohmyapi migrate`, next.")
@app.command()
@ -121,6 +132,7 @@ def migrate(app: str = "*", root: str = "."):
asyncio.run(project.migrate(app))
else:
asyncio.run(project.migrate(app))
logger.info(f"✅ Migrations ran successfully.")
@app.command()

View file

@ -0,0 +1,38 @@
import logging
import os
import sys
def setup_logging():
"""Configure unified logging for ohmyapi + FastAPI/Uvicorn."""
log_level = os.getenv("OHMYAPI_LOG_LEVEL", "INFO").upper()
level = getattr(logging, log_level, logging.INFO)
# Root logger (affects FastAPI, uvicorn, etc.)
logging.basicConfig(
level=level,
format="[%(asctime)s] [%(levelname)s] %(name)s: %(message)s",
datefmt="%Y-%m-%d %H:%M:%S",
handlers=[
logging.StreamHandler(sys.stdout)
]
)
# Separate ohmyapi logger (optional)
logger = logging.getLogger("ohmyapi")
# Direct warnings/errors to stderr
class LevelFilter(logging.Filter):
def filter(self, record):
# Send warnings+ to stderr, everything else to stdout
if record.levelno >= logging.WARNING:
record.stream = sys.stderr
else:
record.stream = sys.stdout
return True
for handler in logger.handlers:
handler.addFilter(LevelFilter())
logger.setLevel(level)
return logger

View file

@ -15,8 +15,11 @@ from aerich.exceptions import NotInitedError
from fastapi import APIRouter, FastAPI
from tortoise import Tortoise
from ohmyapi.core.logging import setup_logging
from ohmyapi.db.model import Model
logger = setup_logging()
class Project:
"""
@ -29,6 +32,7 @@ class Project:
"""
def __init__(self, project_path: str):
logger.debug(f"Loading project: {project_path}")
self.project_path = Path(project_path).resolve()
self._apps: Dict[str, App] = {}
self.migrations_dir = self.project_path / "migrations"
@ -238,6 +242,7 @@ class App:
# Import the app, so its __init__.py runs.
mod: ModuleType = importlib.import_module(name)
logger.debug(f"Loading app: {self.name}")
self.__load_models(f"{self.name}.models")
self.__load_routes(f"{self.name}.routes")
self.__load_middlewares(f"{self.name}.middlewares")
@ -258,7 +263,6 @@ class App:
try:
importlib.import_module(mod_name)
except ModuleNotFoundError:
print(f"no models detected: {mod_name}")
return
# Acoid duplicates.
@ -276,9 +280,11 @@ class App:
and issubclass(value, Model)
and not name == Model.__name__
):
# monkey-patch __module__ to point to well-known aliases
value.__module__ = value.__module__.replace("ohmyapi.builtin.", "ohmyapi_")
if value.__module__.startswith(mod_name):
self._models[mod_name] = self._models.get(mod_name, []) + [value]
logger.debug(f" - Model: {mod_name} -> {name}")
# if it's a package, recurse into submodules
if hasattr(mod, "__path__"):
@ -300,7 +306,6 @@ class App:
try:
importlib.import_module(mod_name)
except ModuleNotFound:
print(f"no routes detected: {mod_name}")
return
# Avoid duplicates.
@ -315,6 +320,7 @@ class App:
for name, value in vars(mod).copy().items():
if isinstance(value, APIRouter) and not name == APIRouter.__name__:
self._routers[mod_name] = self._routers.get(mod_name, []) + [value]
logger.debug(f" - Router: {mod_name} -> {name} -> {value.routes}")
# if it's a package, recurse into submodules
if hasattr(mod, "__path__"):
@ -330,7 +336,6 @@ class App:
try:
mod = importlib.import_module(mod_name)
except ModuleNotFoundError:
print(f"no middlewares detected: {mod_name}")
return
getter = getattr(mod, "get", None)

View file

@ -2,12 +2,16 @@ from pathlib import Path
from jinja2 import Environment, FileSystemLoader
from ohmyapi.core.logging import setup_logging
import shutil
# Base templates directory
TEMPLATE_DIR = Path(__file__).parent / "templates"
env = Environment(loader=FileSystemLoader(str(TEMPLATE_DIR)))
logger = setup_logging()
def render_template_file(template_path: Path, context: dict, output_path: Path):
"""Render a single Jinja2 template file to disk."""
@ -60,7 +64,7 @@ def copy_static(dir_name: str, target_dir: Path):
template_dir = TEMPLATE_DIR / dir_name
target_dir = Path(target_dir)
if not template_dir.exists():
print(f"no templates found under: {dir_name}")
logger.error(f"no templates found under: {dir_name}")
return
for root, _, files in template_dir.walk():
@ -69,19 +73,15 @@ def copy_static(dir_name: str, target_dir: Path):
src = root_path / file
dst = target_dir / file
if dst.exists():
print(f"⛔ File exists, skipping: {dst}")
logger.warning(f"⛔ File exists, skipping: {dst}")
continue
shutil.copy(src, ".")
print(f"✅ Templates created successfully.")
print(f"🔧 Next, run `docker compose up -d --build`")
shutil.copy(src, dst)
def startproject(name: str):
"""Create a new project: flat structure, all project templates go into <name>/"""
target_dir = Path(name).resolve()
target_dir.mkdir(exist_ok=True)
render_template_dir("project", target_dir, {"project_name": name})
print(f"✅ Project '{name}' created successfully.")
print(f"🔧 Next, configure your project in {target_dir / 'settings.py'}")
def startapp(name: str, project: str):
@ -94,5 +94,3 @@ def startapp(name: str, project: str):
{"project_name": target_dir.resolve().name, "app_name": name},
subdir_name=name,
)
print(f"✅ App '{name}' created in project '{target_dir}' successfully.")
print(f"🔧 Remember to add '{name}' to your INSTALLED_APPS!")