from __future__ import annotations import hashlib import json import logging import time from typing import Any, Dict, Optional def build_trace_id(*parts: str) -> str: raw = "|".join(str(p or "") for p in parts) digest = hashlib.md5(raw.encode("utf-8")).hexdigest() return digest[:16] def emit_activity( logger: logging.Logger, *, event: str, trace_id: str = "", customer_id: str = "", result: str = "ok", latency_ms: Optional[int] = None, **fields: Any, ) -> None: payload: Dict[str, Any] = { "trace_id": trace_id or "-", "customer_id": customer_id or "-", "event": event, "result": result, } if latency_ms is not None: payload["latency_ms"] = int(max(0, latency_ms)) for k, v in (fields or {}).items(): if isinstance(v, str): payload[k] = v[:400] else: payload[k] = v try: logger.info(f"[ACTIVITY] {json.dumps(payload, ensure_ascii=False)}") except Exception: logger.info(f"[ACTIVITY] {payload}") class ActivityTimer: def __init__( self, *, logger: logging.Logger, event: str, trace_id: str = "", customer_id: str = "", **fields: Any, ): self.logger = logger self.event = event self.trace_id = trace_id self.customer_id = customer_id self.fields = fields self.start = time.monotonic() def ok(self, **fields: Any) -> None: elapsed_ms = int((time.monotonic() - self.start) * 1000) merged = dict(self.fields) merged.update(fields) emit_activity( self.logger, event=self.event, trace_id=self.trace_id, customer_id=self.customer_id, result="ok", latency_ms=elapsed_ms, **merged, ) def fail(self, error: str, **fields: Any) -> None: elapsed_ms = int((time.monotonic() - self.start) * 1000) merged = dict(self.fields) merged.update(fields) emit_activity( self.logger, event=self.event, trace_id=self.trace_id, customer_id=self.customer_id, result="error", latency_ms=elapsed_ms, error=error, **merged, )