Files
recipe-maintainer/lib/log.py
autonomic-bot f283a371bb recipe-maintainer: public snapshot (secrets + deployment plans removed, single commit)
Sanitized single-commit public mirror of recipe-maintainer.
- Removed test-ssh/.testenv (live creds); added test-ssh/.testenv.example placeholders.
- Removed plans/ and planned-updates/ (deployment-planning docs) so no client/
  deployment domains appear in the public repo.
- All other secret stores were already gitignored.
- docs.coopcloud.tech retained as a submodule (public upstream).
2026-06-16 20:18:24 +00:00

107 lines
3.6 KiB
Python

"""Structured logging for skill operations.
Each skill invocation creates a SkillLogger that captures commands,
their output, and contextual messages. On save(), the log is written
to logs/<skill>-<recipe>-<date>.md in markdown format.
"""
from datetime import datetime
from pathlib import Path
from lib.config import paths
class SkillLogger:
"""Captures structured output from a skill invocation."""
def __init__(self, skill_name: str, recipe_name: str | None = None):
self.skill_name = skill_name
self.recipe_name = recipe_name
self.started = datetime.now()
self._entries: list[dict] = []
def step(self, description: str) -> None:
"""Log a major step in the skill execution."""
self._entries.append({"type": "step", "text": description})
print(f"\n=== {description} ===", flush=True)
def command(self, cmd: str, output: str, returncode: int) -> None:
"""Log a command and its result."""
self._entries.append({
"type": "command",
"cmd": cmd,
"output": output,
"returncode": returncode,
})
def info(self, message: str) -> None:
"""Log an informational message."""
self._entries.append({"type": "info", "text": message})
print(f" {message}", flush=True)
def warn(self, message: str) -> None:
"""Log a warning."""
self._entries.append({"type": "warn", "text": message})
print(f" WARNING: {message}", flush=True)
def error(self, message: str) -> None:
"""Log an error."""
self._entries.append({"type": "error", "text": message})
print(f" ERROR: {message}", flush=True)
def save(self) -> Path:
"""Write the log to logs/<skill>-<recipe>-<date>.md."""
date_str = self.started.strftime("%Y-%m-%d")
if self.recipe_name:
filename = f"{self.skill_name}-{self.recipe_name}-{date_str}.md"
else:
filename = f"{self.skill_name}-{date_str}.md"
log_dir = paths.LOGS_DIR
log_dir.mkdir(parents=True, exist_ok=True)
log_path = log_dir / filename
lines = [
f"# {self.skill_name}",
"",
]
if self.recipe_name:
lines.append(f"Recipe: {self.recipe_name}")
lines.append("")
lines.append(
f"Started: {self.started.strftime('%Y-%m-%d %H:%M:%S')}"
)
lines.append(
f"Finished: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}"
)
lines.append("")
for entry in self._entries:
if entry["type"] == "step":
lines.append(f"## {entry['text']}")
lines.append("")
elif entry["type"] == "command":
lines.append(f"```")
lines.append(f"$ {entry['cmd']}")
if entry["output"]:
lines.append(entry["output"])
lines.append(f"```")
if entry["returncode"] != 0:
lines.append(f"Exit code: {entry['returncode']}")
lines.append("")
elif entry["type"] == "info":
lines.append(f"{entry['text']}")
lines.append("")
elif entry["type"] == "warn":
lines.append(f"**WARNING:** {entry['text']}")
lines.append("")
elif entry["type"] == "error":
lines.append(f"**ERROR:** {entry['text']}")
lines.append("")
with open(log_path, "w") as f:
f.write("\n".join(lines))
print(f"\n Log saved: {log_path}", flush=True)
return log_path