Files
life-echo/api/app/core/log_events.py
2026-04-30 16:22:55 +08:00

133 lines
4.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.
"""结构化日志辅助:统一 ``event=`` 行格式与 Celery prerun 可提取的上下文字段。"""
from __future__ import annotations
from typing import Any
def format_log_event(event: str, **fields: Any) -> str:
"""
生成单行 ``event=...`` 日志正文:``event`` 固定首位;``msg`` 固定末位(若提供);其余键按字母序。
``None`` 与空字符串会跳过;浮点数默认保留一位小数(适用于 ``duration_ms``)。
"""
parts: list[str] = [f"event={event}"]
keys = sorted(k for k in fields if k != "msg")
ordered = list(keys)
if "msg" in fields:
ordered.append("msg")
for k in ordered:
v = fields[k]
if v is None:
continue
if isinstance(v, float):
parts.append(f"{k}={v:.1f}")
elif isinstance(v, bool):
parts.append(f"{k}={str(v).lower()}")
else:
s = str(v).strip()
if not s:
continue
parts.append(f"{k}={s}")
return " ".join(parts)
def correlation_bind_kwargs(
*,
user_id: str | None = None,
memoir_correlation_id: str | None = None,
correlation_id: str | None = None,
**more: str | None,
) -> dict[str, str]:
"""供 ``logger.bind(**...)````memoir_correlation_id`` 会以 ``correlation_id`` 写入(统一检索键)。"""
out: dict[str, str] = {}
uid = (user_id or "").strip()
if uid:
out["user_id"] = uid
cid = (correlation_id or memoir_correlation_id or "").strip()
if cid:
out["correlation_id"] = cid
for k, v in more.items():
if v is None:
continue
s = str(v).strip()
if s:
out[str(k)] = s
return out
# bind=True 任务的 positional 与字段名映射kwargs 优先,缺位再填)
_TASK_POSITIONAL_FIELDS: dict[str, tuple[str, ...]] = {
"app.tasks.memory_enrichment_tasks.embed_memory_source": ("user_id", "source_id"),
"app.tasks.memory_enrichment_tasks.enrich_memory_source": ("user_id", "source_id"),
"app.tasks.memory_compaction_tasks.memory_compaction_run": ("user_id",),
"app.tasks.chapter_compose_tasks.recompose_chapter": ("chapter_id",),
"app.tasks.memoir_quality_pass_tasks.memoir_quality_pass": ("user_id",),
"app.tasks.memoir_tasks.process_memoir_phase2": ("user_id", "chapter_category"),
"app.tasks.memoir_tasks.process_memoir_phase1": ("user_id",),
"app.tasks.memoir_tasks.generate_chapter_content": ("user_id", "stage"),
"app.tasks.chapter_cover_tasks.generate_chapter_cover": ("chapter_id",),
"app.tasks.story_image_tasks.generate_story_image": ("story_id",),
"app.tasks.story_title_tasks.generate_story_title_after_create": (
"story_id",
"chapter_category",
"oral_scope",
"user_id",
),
}
_KW_KEYS_COPY: tuple[str, ...] = (
"user_id",
"source_id",
"chapter_id",
"story_id",
"chapter_category",
"stage",
"oral_scope",
)
def celery_prerun_extras(
task_name: str | None,
args: tuple[Any, ...],
kwargs: dict[str, Any] | None,
) -> dict[str, str]:
"""
从 Celery ``task_prerun`` 的 args/kwargs 提取 ``user_id``、``correlation_id`` 等,
供 ``set_celery_log_extras`` 与任务体内 loguru 记录关联。
"""
out: dict[str, str] = {}
kw = dict(kwargs or {})
mcid = kw.get("memoir_correlation_id")
if mcid is not None:
s = str(mcid).strip()
if s:
out["correlation_id"] = s
for key in _KW_KEYS_COPY:
if key not in kw:
continue
val = kw[key]
if val is None:
continue
s = str(val).strip()
if s:
out[key] = s
name = (task_name or "").strip()
fields = _TASK_POSITIONAL_FIELDS.get(name)
if fields and args:
for i, field in enumerate(fields):
if i >= len(args):
break
if field in out:
continue
val = args[i]
if val is None:
continue
s = str(val).strip()
if s:
out[field] = s
return out