79 lines
2.4 KiB
Python
79 lines
2.4 KiB
Python
"""流程事件:stdout NDJSON / 文件 / HTTP webhook。"""
|
|
from __future__ import annotations
|
|
|
|
import json
|
|
import time
|
|
import urllib.error
|
|
import urllib.request
|
|
from pathlib import Path
|
|
from typing import Any, Dict, List, Optional, Union
|
|
|
|
|
|
def _now_iso() -> str:
|
|
return time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime())
|
|
|
|
|
|
def _http_json_post(url: str, body: Dict[str, Any], timeout: float = 30.0) -> None:
|
|
data = json.dumps(body, ensure_ascii=False).encode("utf-8")
|
|
request = urllib.request.Request(
|
|
url,
|
|
data=data,
|
|
headers={"Accept": "application/json", "Content-Type": "application/json"},
|
|
method="POST",
|
|
)
|
|
with urllib.request.urlopen(request, timeout=timeout) as resp:
|
|
resp.read()
|
|
|
|
|
|
class CallbackHub:
|
|
"""向 stdout / 文件 / HTTP webhook 广播流程事件。"""
|
|
|
|
def __init__(
|
|
self,
|
|
*,
|
|
callback_url: Optional[str] = None,
|
|
callback_file: Optional[Union[str, Path]] = None,
|
|
quiet: bool = False,
|
|
) -> None:
|
|
self.callback_url = callback_url
|
|
self.callback_file = Path(callback_file) if callback_file else None
|
|
self.quiet = quiet
|
|
self.events: List[Dict[str, Any]] = []
|
|
|
|
def emit(self, event: str, **payload: Any) -> Dict[str, Any]:
|
|
record = {
|
|
"event": event,
|
|
"ts": _now_iso(),
|
|
**payload,
|
|
}
|
|
self.events.append(record)
|
|
|
|
if not self.quiet:
|
|
print(json.dumps(record, ensure_ascii=False), flush=True)
|
|
|
|
if self.callback_file:
|
|
self.callback_file.parent.mkdir(parents=True, exist_ok=True)
|
|
self.callback_file.write_text(
|
|
json.dumps(
|
|
{"latest": record, "history": self.events},
|
|
ensure_ascii=False,
|
|
indent=2,
|
|
),
|
|
encoding="utf-8",
|
|
)
|
|
|
|
if self.callback_url:
|
|
try:
|
|
_http_json_post(self.callback_url, record, timeout=30.0)
|
|
except Exception as exc: # noqa: BLE001
|
|
if not self.quiet:
|
|
warn = {
|
|
"event": "callback_error",
|
|
"ts": _now_iso(),
|
|
"error": str(exc),
|
|
"callback_url": self.callback_url,
|
|
}
|
|
print(json.dumps(warn, ensure_ascii=False), flush=True)
|
|
|
|
return record
|