ForcePilot/backend/package/yuxi/channels/adapters/structured_logger_adapter.py
Kris 00092c818e chore: 批量代码优化与规范完善
本次提交包含多项代码优化与规范修正:
1. 文档与注释优化:修正注释术语、补充注解与FR编号
2. 代码格式调整:统一空格、换行与缩进规范
3. 类型与接口完善:补充__all__导出、修正返回类型注解
4. 错误处理增强:新增领域错误类与校验逻辑
5. 依赖与导入调整:修复路径引用、统一时区导入
6. 协议与契约更新:完善接口文档与一致性注解
2026-07-03 19:18:13 +08:00

185 lines
6.1 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""StructuredLoggerAdapter实现 LoggerPort复用 loguru logger。
- 复用 yuxi.utils.logging_config.logger
- 日志必须携带 trace_id
- 敏感字段脱敏委托 MaskingPort前 4 位 + ***,强敏感字段全量 ***
- 日志故障不得阻断主流程,但通过 stderr 兜底输出,
避免日志系统故障完全无声
依赖边界:只依赖 yuxi.channels.contract端口 + DTO
yuxi.utils.logging_configlogger。脱敏字段表与算法不在此处维护
统一由 ``MaskingPort`` 委托 ``yuxi.utils.crypto`` 维护(单一事实源)。
"""
from __future__ import annotations
import sys
from typing import Any
from loguru import logger as loguru_logger
from yuxi.channels.contract.dtos.logger import LogLevel
from yuxi.channels.contract.ports.driven.logger_port import LoggerPort
from yuxi.channels.contract.ports.driven.masking_port import MaskingPort
__all__ = ["StructuredLoggerAdapter"]
class StructuredLoggerAdapter(LoggerPort):
"""结构化日志被驱动适配器。
复用 loguru ``logger``,通过 ``logger.bind(trace_id=...)`` 注入链路追踪
上下文。敏感字段脱敏委托 ``MaskingPort.maskPartial``,递归处理
dict / list 嵌套(修复原自实现不递归导致 config={...} 整体泄露的漏洞)。
所有方法为 ``async def``,故障时捕获异常并通过 ``stderr`` 兜底输出,
不阻断主流程。
"""
def __init__(self, masking_port: MaskingPort) -> None:
"""初始化结构化日志适配器。
Args:
masking_port: 脱敏端口,用于日志 kwargs 的敏感字段脱敏。
"""
self._masking = masking_port
def _mask_sensitive_fields(self, kwargs: dict[str, Any]) -> dict[str, Any]:
"""脱敏 kwargs 中的敏感字段(委托 MaskingPort.maskPartial
递归处理嵌套 dict / list强敏感字段password / api_key /
webhook_url 等)全量遮蔽为 ``"***"``普通敏感字段token /
app_secret 等)保留前 4 位 + ``"***"``。
@pre
- kwargs 为 dict[str, Any]
@post
- 返回脱敏后的新 dict不修改原对象
@failure
- 无(脱敏为纯内存计算,故障由调用方兜底)
@consistency
- 无状态stateless纯委托不修改任何持久化状态
"""
return self._masking.maskPartial(kwargs)
def _emit(
self,
level: str,
message: str,
*,
trace_id: str | None,
**kwargs: Any,
) -> None:
"""实际执行 loguru 日志写入,失败时 stderr 兜底。
集中处理 loguru 调用与异常兜底,避免每个日志方法重复 try/except。
loguru 故障时通过 ``sys.stderr.write`` 输出一行精简信息,确保日志
系统故障可被运维感知。
"""
try:
masked = self._mask_sensitive_fields(kwargs)
bound = loguru_logger.bind(trace_id=trace_id) if trace_id else loguru_logger
bound.log(level, message, **masked)
except Exception as exc:
sys.stderr.write(f"[logger-failed] level={level} trace_id={trace_id} message={message} error={exc}\n")
async def log(
self,
level: LogLevel,
message: str,
*,
trace_id: str | None = None,
**kwargs: Any,
) -> None:
"""按指定级别记录日志,携带 trace_id 与脱敏后的上下文。
Args:
level: 日志级别。
message: 日志消息。
trace_id: 链路追踪 ID可选
**kwargs: 附加上下文(敏感字段自动脱敏)。
"""
self._emit(level.value.upper(), message, trace_id=trace_id, **kwargs)
async def debug(
self,
message: str,
*,
trace_id: str | None = None,
**kwargs: Any,
) -> None:
"""记录 DEBUG 级别日志。
Args:
message: 日志消息。
trace_id: 链路追踪 ID可选
**kwargs: 附加上下文(敏感字段自动脱敏)。
"""
self._emit("DEBUG", message, trace_id=trace_id, **kwargs)
async def info(
self,
message: str,
*,
trace_id: str | None = None,
**kwargs: Any,
) -> None:
"""记录 INFO 级别日志。
Args:
message: 日志消息。
trace_id: 链路追踪 ID可选
**kwargs: 附加上下文(敏感字段自动脱敏)。
"""
self._emit("INFO", message, trace_id=trace_id, **kwargs)
async def warn(
self,
message: str,
*,
trace_id: str | None = None,
**kwargs: Any,
) -> None:
"""记录 WARN 级别日志。
Args:
message: 日志消息。
trace_id: 链路追踪 ID可选
**kwargs: 附加上下文(敏感字段自动脱敏)。
"""
self._emit("WARNING", message, trace_id=trace_id, **kwargs)
async def error(
self,
message: str,
*,
trace_id: str | None = None,
**kwargs: Any,
) -> None:
"""记录 ERROR 级别日志。
Args:
message: 日志消息。
trace_id: 链路追踪 ID可选
**kwargs: 附加上下文(敏感字段自动脱敏)。
"""
self._emit("ERROR", message, trace_id=trace_id, **kwargs)
async def exception(
self,
message: str,
*,
trace_id: str | None = None,
exc_info: Exception | None = None,
**kwargs: Any,
) -> None:
"""记录 ERROR 级别日志,附带完整堆栈。"""
try:
masked = self._mask_sensitive_fields(kwargs)
bound = loguru_logger.bind(trace_id=trace_id) if trace_id else loguru_logger
bound.opt(exception=exc_info).log("ERROR", message, **masked)
except Exception as exc:
sys.stderr.write(f"[logger-failed] level=ERROR trace_id={trace_id} message={message} error={exc}\n")