112 lines
3.6 KiB
Python
112 lines
3.6 KiB
Python
from __future__ import annotations
|
|
|
|
import logging
|
|
from logging.config import dictConfig
|
|
from pathlib import Path
|
|
from typing import cast
|
|
|
|
import structlog
|
|
|
|
from core.config.settings import PROJECT_ROOT, RuntimeSettings, Settings, config
|
|
from core.logging.formatters import (
|
|
build_plain_formatter,
|
|
build_processor_formatter,
|
|
ensure_message_key,
|
|
)
|
|
from core.logging.filters import build_sensitive_data_processor
|
|
from core.logging.handlers import build_file_handler_config
|
|
|
|
|
|
def _ensure_log_dirs(runtime: RuntimeSettings) -> None:
|
|
_resolve_log_path(runtime.log_dir).mkdir(parents=True, exist_ok=True)
|
|
_resolve_log_path(runtime.log_error_dir).mkdir(parents=True, exist_ok=True)
|
|
|
|
|
|
def _resolve_log_path(path: str) -> Path:
|
|
candidate = Path(path)
|
|
if candidate.is_absolute():
|
|
return candidate
|
|
return PROJECT_ROOT / candidate
|
|
|
|
|
|
def build_logging_config(runtime: RuntimeSettings) -> dict[str, object]:
|
|
log_dir = _resolve_log_path(runtime.log_dir)
|
|
error_dir = _resolve_log_path(runtime.log_error_dir)
|
|
formatter_name = "json" if runtime.log_json else "plain"
|
|
|
|
file_handler = build_file_handler_config(
|
|
runtime,
|
|
file_path=log_dir / runtime.log_file_name,
|
|
level=runtime.log_level,
|
|
formatter=formatter_name,
|
|
)
|
|
error_handler = build_file_handler_config(
|
|
runtime,
|
|
file_path=error_dir / runtime.log_error_file_name,
|
|
level="ERROR",
|
|
formatter=formatter_name,
|
|
filters=["error_only"],
|
|
)
|
|
|
|
return {
|
|
"version": 1,
|
|
"disable_existing_loggers": False,
|
|
"filters": {
|
|
"error_only": {
|
|
"()": "core.logging.filters.ErrorLevelFilter",
|
|
}
|
|
},
|
|
"formatters": {
|
|
"json": {
|
|
"()": build_processor_formatter,
|
|
"sensitive_fields": runtime.log_sensitive_fields,
|
|
},
|
|
"plain": {
|
|
"()": build_plain_formatter,
|
|
"sensitive_fields": runtime.log_sensitive_fields,
|
|
},
|
|
},
|
|
"handlers": {
|
|
"file": file_handler,
|
|
"error": error_handler,
|
|
},
|
|
"root": {
|
|
"handlers": ["file", "error"],
|
|
"level": runtime.log_level,
|
|
},
|
|
}
|
|
|
|
|
|
def configure_logging(settings: Settings | None = None) -> None:
|
|
active_settings = settings if settings is not None else cast(Settings, config)
|
|
runtime = active_settings.runtime
|
|
|
|
try:
|
|
_ensure_log_dirs(runtime)
|
|
dictConfig(build_logging_config(runtime))
|
|
except (OSError, ValueError) as exc:
|
|
logging.basicConfig(level=runtime.log_level)
|
|
logging.getLogger(__name__).error("Logging setup failed", exc_info=exc)
|
|
|
|
structlog.configure(
|
|
processors=[
|
|
structlog.contextvars.merge_contextvars,
|
|
structlog.processors.add_log_level,
|
|
structlog.processors.TimeStamper(fmt="iso", utc=True),
|
|
structlog.processors.CallsiteParameterAdder(
|
|
parameters=[
|
|
structlog.processors.CallsiteParameter.MODULE,
|
|
structlog.processors.CallsiteParameter.FUNC_NAME,
|
|
structlog.processors.CallsiteParameter.LINENO,
|
|
]
|
|
),
|
|
build_sensitive_data_processor(runtime.log_sensitive_fields),
|
|
ensure_message_key,
|
|
structlog.processors.format_exc_info,
|
|
structlog.processors.UnicodeDecoder(),
|
|
structlog.stdlib.ProcessorFormatter.wrap_for_formatter,
|
|
],
|
|
logger_factory=structlog.stdlib.LoggerFactory(),
|
|
cache_logger_on_first_use=True,
|
|
)
|