74 lines
1.9 KiB
Python
74 lines
1.9 KiB
Python
# 入站审计:一行请求一条,写失败只记日志,不把业务请求打成 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)
|