New router intelligence modules (26 files): alert_ingest/store, audit_store, architecture_pressure, backlog_generator/store, cost_analyzer, data_governance, dependency_scanner, drift_analyzer, incident_* (5 files), llm_enrichment, platform_priority_digest, provider_budget, release_check_runner, risk_* (6 files), signature_state_store, sofiia_auto_router, tool_governance New services: - sofiia-console: Dockerfile, adapters/, monitor/nodes/ops/voice modules, launchd, react static - memory-service: integration_endpoints, integrations, voice_endpoints, static UI - aurora-service: full app suite (analysis, job_store, orchestrator, reporting, schemas, subagents) - sofiia-supervisor: new supervisor service - aistalk-bridge-lite: Telegram bridge lite - calendar-service: CalDAV calendar service with reminders - mlx-stt-service / mlx-tts-service: Apple Silicon speech services - binance-bot-monitor: market monitor service - node-worker: STT/TTS memory providers New tools (9): agent_email, browser_tool, contract_tool, observability_tool, oncall_tool, pr_reviewer_tool, repo_tool, safe_code_executor, secure_vault New crews: agromatrix_crew (10 modules: depth_classifier, doc_facts, doc_focus, farm_state, light_reply, llm_factory, memory_manager, proactivity, reflection_engine, session_context, style_adapter, telemetry) Tests: 85+ test files for all new modules Made-with: Cursor
118 lines
4.0 KiB
Python
118 lines
4.0 KiB
Python
"""
|
||
Telemetry helpers для Humanized Stepan v2.7.2.
|
||
|
||
Забезпечує єдиний тег AGX_STEPAN_METRIC на всіх ключових лог-рядках
|
||
і PII-safe анонімізацію ідентифікаторів.
|
||
|
||
Grep у проді:
|
||
grep "AGX_STEPAN_METRIC" /logs/gateway.log
|
||
|
||
Формат рядка:
|
||
AGX_STEPAN_METRIC <event> key=value key2=value2
|
||
|
||
PII-safe:
|
||
- Ключі з pii_keys (default: {"user_id","chat_id"}) автоматично анонімізуються:
|
||
user_id=h:3f9a12b4c7 (sha256 перших 10 hex-символів)
|
||
- Дає можливість корелювати події одного користувача без прямого витоку.
|
||
- Не є криптографічним захистом проти таргетованого знання.
|
||
|
||
Правила серіалізації:
|
||
- bool → "true" / "false"
|
||
- int/float → str
|
||
- list → елементи через кому
|
||
- dict → компактний JSON
|
||
- None → "null"
|
||
- Нічого з секретів/токенів не передавати у kv.
|
||
"""
|
||
|
||
from __future__ import annotations
|
||
|
||
import hashlib
|
||
import json
|
||
import logging
|
||
from typing import Any
|
||
|
||
TELEMETRY_TAG = "AGX_STEPAN_METRIC"
|
||
|
||
# Ключі, які автоматично анонімізуються у tlog()
|
||
_DEFAULT_PII_KEYS: frozenset[str] = frozenset({"user_id", "chat_id"})
|
||
|
||
|
||
def anonymize_id(value: str | None) -> str | None:
|
||
"""
|
||
Повертає PII-safe псевдонім для ідентифікатора.
|
||
|
||
Правила:
|
||
- None → None
|
||
- Пусте рядок → повернути як є (нема що хешувати)
|
||
- Інакше: "h:" + sha256(value)[:10]
|
||
|
||
Формат стабільний: завжди 12 символів ("h:" + 10 hex).
|
||
Колізії теоретично можливі, але практично нереальні для user_id-просторів.
|
||
|
||
Приклади:
|
||
anonymize_id("123456789") → "h:3f9a12b4c7"
|
||
anonymize_id(None) → None
|
||
anonymize_id("") → ""
|
||
"""
|
||
if value is None:
|
||
return None
|
||
if not value:
|
||
return value
|
||
try:
|
||
digest = hashlib.sha256(value.encode()).hexdigest()
|
||
return f"h:{digest[:10]}"
|
||
except Exception:
|
||
return "h:error"
|
||
|
||
|
||
def _fmt_value(v: Any) -> str:
|
||
if isinstance(v, bool):
|
||
return str(v).lower()
|
||
if isinstance(v, (int, float)):
|
||
return str(v)
|
||
if v is None:
|
||
return "null"
|
||
if isinstance(v, list):
|
||
return ",".join(str(i) for i in v)
|
||
if isinstance(v, dict):
|
||
return json.dumps(v, ensure_ascii=False, separators=(",", ":"))
|
||
return str(v)
|
||
|
||
|
||
def tlog(
|
||
logger: logging.Logger,
|
||
msg: str,
|
||
level: int = logging.INFO,
|
||
pii_keys: frozenset[str] | set[str] = _DEFAULT_PII_KEYS,
|
||
**kv: Any,
|
||
) -> None:
|
||
"""
|
||
Логує рядок з уніфікованим тегом AGX_STEPAN_METRIC і PII-safe анонімізацією.
|
||
|
||
Приклади:
|
||
tlog(logger, "depth", depth="light", reason="greeting")
|
||
→ "AGX_STEPAN_METRIC depth depth=light reason=greeting"
|
||
|
||
tlog(logger, "memory_save", user_id="123456789", ok=True)
|
||
→ "AGX_STEPAN_METRIC memory_save user_id=h:3f9a12b4c7 ok=true"
|
||
|
||
Ключі в pii_keys автоматично анонімізуються через anonymize_id().
|
||
Безпечний: всі помилки форматування ігноруються — fallback без kv.
|
||
"""
|
||
try:
|
||
parts: list[str] = []
|
||
for k, v in kv.items():
|
||
if k in pii_keys:
|
||
anon = anonymize_id(str(v) if v is not None else None)
|
||
parts.append(f"{k}={_fmt_value(anon)}")
|
||
else:
|
||
parts.append(f"{k}={_fmt_value(v)}")
|
||
kv_str = " ".join(parts)
|
||
line = f"{TELEMETRY_TAG} {msg}"
|
||
if kv_str:
|
||
line = f"{line} {kv_str}"
|
||
except Exception:
|
||
line = f"{TELEMETRY_TAG} {msg}"
|
||
logger.log(level, line)
|