# 入站审计:一行请求一条,写失败只记日志,不把业务请求打成 500。 from __future__ import annotations import json import logging from typing import Any logger = logging.getLogger("rpa_oms.inbound.audit") _INSERT_SQL = """ INSERT INTO rpa_oms_inbound_audit ( api_no, http_code, request_json, response_json, error_text, token_masked ) VALUES ( :api_no, :http_code, :request_json, :response_json, :error_text, :token_masked ) """ _ERROR_MAX = 4000 # 有 token 就打成 ***,没有则空。不把明文写进审计。 def mask_token(token: str | None) -> str | None: if not token: return None return "***" # 把入参或出参收成 JSON 字符串,datetime / Decimal 用 str。 def dumps(payload: Any) -> str: return json.dumps(payload, ensure_ascii=False, default=str) # 内部错误原文截到 VARCHAR2(4000)。空串当没有。 def clip_error(text: str | None) -> str | None: if not text: return None if len(text) > _ERROR_MAX: return text[:_ERROR_MAX] return text # 往审计表插一行。任何异常都吞掉并记日志。 def write_audit( conn, *, api_no: int, http_code: int, payload: Any, response: Any, error_text: str | None, token: str | None, ) -> None: binds = { "api_no": api_no, "http_code": http_code, "request_json": dumps(payload if payload is not None else {}), "response_json": dumps(response if response is not None else {}), "error_text": clip_error(error_text), "token_masked": mask_token(token), } try: with conn.cursor() as cur: cur.execute(_INSERT_SQL, binds) conn.commit() except Exception: logger.exception("inbound audit write failed") # 取出内部错误原文:优先 __cause__,供审计表用,不给出站。 def cause_text(exc: BaseException) -> str: cause = exc.__cause__ or exc return str(cause)