新增trace_ctx实现链路ID上下文管理与日志注入,添加boot.py启动管理器、startup.py启动任务执行器、backoff.py退避策略工具以及trace.py启动链路追踪模块,完善channel运行时基础能力
159 lines
4.8 KiB
Python
159 lines
4.8 KiB
Python
from __future__ import annotations
|
|
|
|
import time as _time
|
|
from dataclasses import dataclass, field
|
|
from enum import StrEnum
|
|
from typing import Any
|
|
|
|
|
|
class PhaseStatus(StrEnum):
|
|
PENDING = "pending"
|
|
RUNNING = "running"
|
|
OK = "ok"
|
|
FAILED = "failed"
|
|
SKIPPED = "skipped"
|
|
|
|
|
|
class GatewayReadyState(StrEnum):
|
|
NOT_READY = "not_ready"
|
|
READY = "ready"
|
|
DEGRADED = "degraded"
|
|
FAILED = "failed"
|
|
|
|
|
|
@dataclass
|
|
class PhaseMark:
|
|
name: str
|
|
status: PhaseStatus = PhaseStatus.PENDING
|
|
started_at: float | None = None
|
|
finished_at: float | None = None
|
|
|
|
@property
|
|
def elapsed_ms(self) -> float | None:
|
|
if self.started_at and self.finished_at:
|
|
return (self.finished_at - self.started_at) * 1000
|
|
return None
|
|
|
|
|
|
@dataclass
|
|
class StartupTrace:
|
|
"""启动追踪器 — 记录系统启动各阶段耗时。"""
|
|
|
|
started_at: float = 0.0
|
|
completed_at: float = 0.0
|
|
failed_at: float = 0.0
|
|
error: str | None = None
|
|
phases: list[dict[str, Any]] = field(default_factory=list)
|
|
|
|
_phase_marks: list[PhaseMark] = field(default_factory=list, repr=False)
|
|
_active_phase: str | None = field(default=None, repr=False)
|
|
ready_state: GatewayReadyState = GatewayReadyState.NOT_READY
|
|
trace_id: str = ""
|
|
|
|
def start(self) -> None:
|
|
self.started_at = _time.time()
|
|
self.phases.clear()
|
|
|
|
def add_phase(self, name: str, duration_ms: float, *, success: bool = True, error: str | None = None) -> None:
|
|
self.phases.append(
|
|
{
|
|
"name": name,
|
|
"duration_ms": round(duration_ms, 2),
|
|
"success": success,
|
|
"error": error,
|
|
}
|
|
)
|
|
|
|
def complete(self) -> None:
|
|
self.completed_at = _time.time()
|
|
|
|
def fail(self, error: str) -> None:
|
|
self.failed_at = _time.time()
|
|
self.error = error
|
|
|
|
@property
|
|
def duration(self) -> float:
|
|
if self.completed_at:
|
|
return self.completed_at - self.started_at
|
|
if self.failed_at:
|
|
return self.failed_at - self.started_at
|
|
return _time.time() - self.started_at
|
|
|
|
@property
|
|
def success(self) -> bool:
|
|
return self.completed_at > 0 and self.failed_at == 0
|
|
|
|
def to_dict(self) -> dict[str, Any]:
|
|
return {
|
|
"trace_id": self.trace_id,
|
|
"started_at": self.started_at,
|
|
"completed_at": self.completed_at,
|
|
"failed_at": self.failed_at,
|
|
"duration_seconds": round(self.duration, 3),
|
|
"success": self.success,
|
|
"ready_state": self.ready_state.value,
|
|
"error": self.error,
|
|
"phases": list(self.phases),
|
|
}
|
|
|
|
def start_phase(self, name: str) -> None:
|
|
mark = PhaseMark(name=name, status=PhaseStatus.RUNNING, started_at=_time.time())
|
|
self._upsert_mark(mark)
|
|
self._active_phase = name
|
|
|
|
def complete_phase(self, name: str, ok: bool = True, error: str | None = None) -> None:
|
|
mark = self._find_mark(name)
|
|
if mark is None:
|
|
return
|
|
mark.status = PhaseStatus.OK if ok else PhaseStatus.FAILED
|
|
mark.finished_at = _time.time()
|
|
self._active_phase = None
|
|
duration_ms = mark.elapsed_ms or 0
|
|
self.add_phase(name, duration_ms, success=ok, error=error)
|
|
|
|
def skip_phase(self, name: str) -> None:
|
|
mark = self._find_mark(name)
|
|
if mark is not None:
|
|
mark.status = PhaseStatus.SKIPPED
|
|
else:
|
|
mark = PhaseMark(name=name, status=PhaseStatus.SKIPPED)
|
|
self._phase_marks.append(mark)
|
|
self._active_phase = None
|
|
|
|
def finalize(self, ready_state: GatewayReadyState) -> None:
|
|
self.ready_state = ready_state
|
|
if not self.completed_at and not self.failed_at:
|
|
self.completed_at = _time.time()
|
|
|
|
def summary(self) -> dict[str, Any]:
|
|
return {
|
|
"trace_id": self.trace_id,
|
|
"ready_state": self.ready_state.value,
|
|
"total_elapsed_ms": round(self.duration * 1000, 2) if self.duration else None,
|
|
"phases": [
|
|
{
|
|
"name": p.name,
|
|
"status": p.status.value,
|
|
"elapsed_ms": round(p.elapsed_ms, 2) if p.elapsed_ms else None,
|
|
}
|
|
for p in self._phase_marks
|
|
],
|
|
"errors": [
|
|
e["error"] for e in self.phases if not e.get("success") and e.get("error")
|
|
],
|
|
}
|
|
|
|
def _upsert_mark(self, mark: PhaseMark) -> None:
|
|
existing = self._find_mark(mark.name)
|
|
if existing:
|
|
idx = self._phase_marks.index(existing)
|
|
self._phase_marks[idx] = mark
|
|
else:
|
|
self._phase_marks.append(mark)
|
|
|
|
def _find_mark(self, name: str) -> PhaseMark | None:
|
|
for p in self._phase_marks:
|
|
if p.name == name:
|
|
return p
|
|
return None
|