"""Publish agent run logs: HTML visualize → OSS upload → MySQL record."""
from __future__ import annotations
import logging
from pathlib import Path
from typing import TYPE_CHECKING
from supply_agent.logging.logger import slugify_agent_name
from supply_agent.logging.visualize import generate_visualization
from supply_infra.config import get_infra_settings
from supply_infra.db.repositories.oss_log_repo import OssLogRepository
from supply_infra.db.session import get_session
from supply_infra.oss.client import OssClient
if TYPE_CHECKING:
from supply_agent.logging.logger import AgentLogger
_log = logging.getLogger("supply_infra.agent_logging.publish")
def publish_run_artifacts_to_oss(agent_logger: AgentLogger) -> str | None:
"""
After a run finishes: generate HTML, upload .log/.jsonl/.html to OSS,
and insert a row into ``oss_logs``.
Returns the public HTML URL, or None if skipped / failed.
Failures are logged and do not raise (so agent runs are not broken).
"""
settings = get_infra_settings()
if not settings.log_oss_upload_enabled:
return None
if not settings.aliyun_oss_access_key_id or not settings.aliyun_oss_access_key_secret:
_log.debug("OSS credentials missing; skip log publish")
return None
log_file = agent_logger.log_file
jsonl_file = agent_logger.jsonl_file
if not log_file or not log_file.exists():
_log.warning("No log file to publish")
return None
try:
source = jsonl_file if jsonl_file and jsonl_file.exists() else log_file
html_path = generate_visualization(source)
agent_slug = slugify_agent_name(agent_logger.agent_name) or "agent"
log_name = html_path.stem
client = OssClient(settings)
files = [p for p in (log_file, jsonl_file, html_path) if p and Path(p).exists()]
html_url: str | None = None
for path in files:
key = client.object_key(agent_slug, Path(path).name)
url = client.upload_file(path, key)
if Path(path).suffix.lower() == ".html":
html_url = url
_log.info("Uploaded %s → %s", path.name, url)
if not html_url:
raise RuntimeError("HTML was not uploaded")
with get_session() as session:
OssLogRepository(session).create(
log_name=log_name,
agent_name=agent_logger.agent_name or agent_slug,
oss_path=html_url,
)
_log.info("oss_logs saved: log_name=%s url=%s", log_name, html_url)
print(f"[publish] 可视化已上传: {html_url}")
return html_url
except Exception:
_log.exception("Failed to publish run artifacts")
print("[publish] 日志上传/入库失败,详见日志;不影响本次 Agent 结果")
return None