Files
rose-ash/shared/log_config/setup.py
giles f42042ccb7
All checks were successful
Build and Deploy / build-and-deploy (push) Successful in 1m5s
Monorepo: consolidate 7 repos into one
Combines shared, blog, market, cart, events, federation, and account
into a single repository. Eliminates submodule sync, sibling model
copying at build time, and per-app CI orchestration.

Changes:
- Remove per-app .git, .gitmodules, .gitea, submodule shared/ dirs
- Remove stale sibling model copies from each app
- Update all 6 Dockerfiles for monorepo build context (root = .)
- Add build directives to docker-compose.yml
- Add single .gitea/workflows/ci.yml with change detection
- Add .dockerignore for monorepo build context
- Create __init__.py for federation and account (cross-app imports)
2026-02-24 19:44:17 +00:00

67 lines
2.0 KiB
Python

"""
Structured JSON logging for all Rose Ash apps.
Call configure_logging(app_name) once at app startup.
Use get_logger(name) anywhere to get a logger that outputs JSON to stdout.
"""
from __future__ import annotations
import json
import logging
import sys
from datetime import datetime, timezone
class JSONFormatter(logging.Formatter):
"""Format log records as single-line JSON objects."""
def __init__(self, app_name: str = ""):
super().__init__()
self.app_name = app_name
def format(self, record: logging.LogRecord) -> str:
entry = {
"timestamp": datetime.now(timezone.utc).isoformat(),
"level": record.levelname,
"app": self.app_name,
"logger": record.name,
"message": record.getMessage(),
}
# Include extra fields if set on the record
for key in ("event_type", "user_id", "request_id", "duration_ms"):
val = getattr(record, key, None)
if val is not None:
entry[key] = val
if record.exc_info and record.exc_info[0] is not None:
entry["exception"] = self.formatException(record.exc_info)
return json.dumps(entry, default=str)
_configured = False
def configure_logging(app_name: str, level: int = logging.INFO) -> None:
"""Set up structured JSON logging to stdout. Safe to call multiple times."""
global _configured
if _configured:
return
_configured = True
handler = logging.StreamHandler(sys.stdout)
handler.setFormatter(JSONFormatter(app_name=app_name))
root = logging.getLogger()
root.setLevel(level)
root.addHandler(handler)
# Quiet down noisy libraries
for name in ("httpx", "httpcore", "asyncio", "sqlalchemy.engine"):
logging.getLogger(name).setLevel(logging.WARNING)
def get_logger(name: str) -> logging.Logger:
"""Get a named logger. Uses the structured JSON format once configure_logging is called."""
return logging.getLogger(name)