105 lines
3.4 KiB
Python
105 lines
3.4 KiB
Python
|
|
from __future__ import annotations
|
||
|
|
|
||
|
|
import json
|
||
|
|
from datetime import datetime
|
||
|
|
from typing import Any, Dict, Optional
|
||
|
|
|
||
|
|
import pymysql
|
||
|
|
|
||
|
|
from config import Config
|
||
|
|
|
||
|
|
|
||
|
|
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": datetime.now().isoformat(),
|
||
|
|
}, 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),
|
||
|
|
datetime.now(),
|
||
|
|
),
|
||
|
|
)
|
||
|
|
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
|