Files
wehub-resource-sync d88fd01084
CI / test (3.10) (push) Failing after 1s
CI / test (3.12) (push) Failing after 0s
CI / skillgen-check (push) Failing after 0s
CI / security-scan (push) Failing after 0s
chore: import upstream snapshot with attribution
2026-07-13 12:09:14 +08:00

81 lines
2.8 KiB
Python

"""Query logging for graphify — append-only JSONL, fail-silent."""
from __future__ import annotations
import json
import os
import re
import time
from datetime import datetime, timezone
from pathlib import Path
from typing import Any
_NODES_RE = re.compile(r"(\d+)\s+nodes?\s+found")
def _log_path() -> Path | None:
# Opt-in only (#1797). The log records every query/path/explain question and
# corpus path (and full responses if GRAPHIFY_QUERY_LOG_RESPONSES) in a
# plaintext file under ~/.cache — outside any repo's .gitignore/retention. A
# default-on record of proprietary queries contradicts graphify's on-device,
# no-telemetry posture, so it is OFF unless explicitly enabled:
# GRAPHIFY_QUERY_LOG=<path> log to that path, or
# GRAPHIFY_QUERY_LOG_ENABLE=1 log to ~/.cache/graphify-queries.log.
# GRAPHIFY_QUERY_LOG_DISABLE=1 still forces it off (back-compat, wins).
if os.environ.get("GRAPHIFY_QUERY_LOG_DISABLE", "").lower() in ("1", "true", "yes"):
return None
override = os.environ.get("GRAPHIFY_QUERY_LOG", "").strip()
if override:
return Path(override).expanduser()
if os.environ.get("GRAPHIFY_QUERY_LOG_ENABLE", "").lower() in ("1", "true", "yes"):
return Path.home() / ".cache" / "graphify-queries.log"
return None
def _log_responses() -> bool:
return os.environ.get("GRAPHIFY_QUERY_LOG_RESPONSES", "").lower() in ("1", "true", "yes")
def nodes_from_result(result: str) -> int | None:
m = _NODES_RE.search(result or "")
return int(m.group(1)) if m else None
def log_query(
*,
kind: str,
question: str,
corpus: str,
result: str | None = None,
nodes_returned: int | None = None,
duration_ms: float | None = None,
**extra: Any,
) -> None:
"""Append one JSONL record to the query log. Never raises."""
try:
path = _log_path()
if path is None:
return
if nodes_returned is None and result is not None:
nodes_returned = nodes_from_result(result)
rec: dict[str, Any] = {
"ts": datetime.now(timezone.utc).isoformat(),
"kind": kind,
"question": question,
"corpus": corpus,
"nodes_returned": nodes_returned,
}
if result is not None:
rec["result_chars"] = len(result)
if duration_ms is not None:
rec["duration_ms"] = round(duration_ms, 3)
for k, v in extra.items():
if v is not None:
rec[k] = v
if result is not None and _log_responses():
rec["response"] = result
path.parent.mkdir(parents=True, exist_ok=True)
with path.open("a", encoding="utf-8") as fh:
fh.write(json.dumps(rec, ensure_ascii=False) + "\n")
except Exception:
pass