Files
xsl 12583e9739 feat(shared): cross-cutting plumbing and pure domain layer
Typed pydantic-settings config, structlog JSON logging with request/trace id
propagation and redaction of phone numbers, tokens and face keys, the unified
response envelope and error-code table from 架构文档 §9.2, async SQLAlchemy
session management, ORM models for the eight foundation tables, and the
OpenTelemetry/Prometheus wiring.

packages/domain stays pure logic -- snowflake ids, the quota three-phase
protocol and the tk_task state machine -- so it can be tested without a
database and is held to mypy strict.
2026-08-14 00:26:39 +08:00

76 lines
2.1 KiB
Python
Executable File

"""Request-scoped context propagated into logs and traces (架构文档 §15)."""
from __future__ import annotations
import uuid
from contextvars import ContextVar, Token
from dataclasses import dataclass
_request_id: ContextVar[str | None] = ContextVar("meida_request_id", default=None)
_trace_id: ContextVar[str | None] = ContextVar("meida_trace_id", default=None)
_user_id: ContextVar[int | None] = ContextVar("meida_user_id", default=None)
_task_id: ContextVar[int | None] = ContextVar("meida_task_id", default=None)
@dataclass(slots=True)
class ContextTokens:
request_id: Token[str | None]
trace_id: Token[str | None]
user_id: Token[int | None]
task_id: Token[int | None]
def new_request_id() -> str:
return uuid.uuid4().hex
def bind_context(
*,
request_id: str | None = None,
trace_id: str | None = None,
user_id: int | None = None,
task_id: int | None = None,
) -> ContextTokens:
return ContextTokens(
request_id=_request_id.set(request_id if request_id is not None else _request_id.get()),
trace_id=_trace_id.set(trace_id if trace_id is not None else _trace_id.get()),
user_id=_user_id.set(user_id if user_id is not None else _user_id.get()),
task_id=_task_id.set(task_id if task_id is not None else _task_id.get()),
)
def reset_context(tokens: ContextTokens) -> None:
_request_id.reset(tokens.request_id)
_trace_id.reset(tokens.trace_id)
_user_id.reset(tokens.user_id)
_task_id.reset(tokens.task_id)
def get_request_id() -> str | None:
return _request_id.get()
def get_trace_id() -> str | None:
return _trace_id.get()
def get_user_id() -> int | None:
return _user_id.get()
def get_task_id() -> int | None:
return _task_id.get()
def context_snapshot() -> dict[str, str | int]:
snapshot: dict[str, str | int] = {}
for key, value in (
("request_id", _request_id.get()),
("trace_id", _trace_id.get()),
("user_id", _user_id.get()),
("task_id", _task_id.get()),
):
if value is not None:
snapshot[key] = value
return snapshot