from __future__ import annotations import json import logging import sys from typing import Any from datahub.timeutil import isoformat _SECRET_KEYS = ( "token", "password", "secret", "key", "authorization", "credential", "tushare_token", "datahub_token", "encryption_key", "cookie", ) def _redact(value: Any, key: str = "") -> Any: lowered = key.lower() if any(part in lowered for part in _SECRET_KEYS): return "***" if isinstance(value, dict): return {str(item_key): _redact(item_value, str(item_key)) for item_key, item_value in value.items()} if isinstance(value, list): return [_redact(item) for item in value] return value class JsonFormatter(logging.Formatter): def format(self, record: logging.LogRecord) -> str: payload: dict[str, Any] = { "ts": isoformat(), "level": record.levelname, "logger": record.name, "message": record.getMessage(), } extra = getattr(record, "hub", None) if isinstance(extra, dict): payload.update(_redact(extra)) if record.exc_info: payload["exc"] = self.formatException(record.exc_info) return json.dumps(payload, ensure_ascii=False, default=str) def configure_logging(level: str = "INFO") -> logging.Logger: logger = logging.getLogger("datahub") if logger.handlers: return logger handler = logging.StreamHandler(sys.stdout) handler.setFormatter(JsonFormatter()) logger.addHandler(handler) logger.setLevel(getattr(logging, level.upper(), logging.INFO)) logger.propagate = False return logger def get_logger() -> logging.Logger: return logging.getLogger("datahub")