from __future__ import annotations import json from typing import Any, Dict, Optional import pymysql from config import Config from services.common.datetime_utils import DateTimeGenerator class StructuredLogger: def __init__(self): cfg = Config.get_section("logging_mysql") self.enabled = str(cfg.get("enabled", "false")).lower() in ("1", "true", "yes") self.host = cfg.get("host", "127.0.0.1") self.port = int(cfg.get("port", 3306)) self.user = cfg.get("user", "root") self.password = cfg.get("password", "") self.database = cfg.get("database", "more_dots") self.table = cfg.get("table", "structured_logs") self.connect_timeout = int(cfg.get("connect_timeout", 5)) self._inited = False def _get_conn(self): return pymysql.connect( host=self.host, port=self.port, user=self.user, password=self.password, database=self.database, charset="utf8mb4", autocommit=True, connect_timeout=self.connect_timeout, ) def _ensure_table(self) -> None: if self._inited or not self.enabled: return sql = f""" CREATE TABLE IF NOT EXISTS {self.table} ( id BIGINT PRIMARY KEY AUTO_INCREMENT, trace_id VARCHAR(64) NOT NULL, level VARCHAR(16) NOT NULL, event VARCHAR(128) NOT NULL, error_code VARCHAR(64) NULL, payload JSON NULL, created_at DATETIME NOT NULL ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; """ try: with self._get_conn() as conn: with conn.cursor() as cur: cur.execute(sql) self._inited = True except Exception: # 开发阶段容错,避免日志失败影响主流程 self.enabled = False def log(self, level: str, event: str, trace_id: str, payload: Optional[Dict[str, Any]] = None, error_code: Optional[str] = None) -> None: print(json.dumps({ "trace_id": trace_id, "level": level, "event": event, "error_code": error_code, "payload": payload or {}, "created_at": DateTimeGenerator.now().iso_str, }, ensure_ascii=False)) if not self.enabled: return self._ensure_table() if not self.enabled: return insert_sql = f"INSERT INTO {self.table}(trace_id, level, event, error_code, payload, created_at) VALUES(%s,%s,%s,%s,%s,%s)" try: with self._get_conn() as conn: with conn.cursor() as cur: cur.execute( insert_sql, ( trace_id, level, event, error_code, json.dumps(payload or {}, ensure_ascii=False), DateTimeGenerator.now().db_datetime, ), ) except Exception: # 开发阶段容错,避免日志失败影响主流程 return _GLOBAL_STRUCTURED_LOGGER: Optional[StructuredLogger] = None def get_structured_logger() -> StructuredLogger: global _GLOBAL_STRUCTURED_LOGGER if _GLOBAL_STRUCTURED_LOGGER is None: _GLOBAL_STRUCTURED_LOGGER = StructuredLogger() return _GLOBAL_STRUCTURED_LOGGER