feat(logging): per-scan `{run_dir}/strix.log` with scan/agent context tagging
Every scan now writes a complete log file at ``{run_dir}/strix.log``
captured from the moment ``run_dir`` is resolved through teardown.
Stdlib ``logging`` only — no parallel framework.
New ``strix/telemetry/logging.py``:
* ``setup_scan_logging(run_dir, debug=)`` attaches a ``FileHandler``
(DEBUG, all ``strix.*``) plus a ``StreamHandler`` (ERROR by
default; DEBUG via ``STRIX_DEBUG=1``).
* ``ContextVar``-backed ``scan_id`` and ``agent_id`` injected by a
``Filter`` so every line is auto-tagged across asyncio tasks
without callers passing them explicitly.
* Third-party noise (``httpx``, ``litellm``, ``openai``,
``anthropic``, ``urllib3``, ``httpcore``) capped at WARNING.
* Returns a teardown handle for ``finally`` cleanup.
Wiring:
* ``orchestration/scan.py`` calls ``setup_scan_logging`` once per
scan after ``run_dir`` resolves; sets scan_id; tears down in
``finally``. Adds INFO logs for sandbox bring-up + scan
start/end.
* ``orchestration/hooks.py`` sets/clears ``agent_id`` ContextVar in
``on_agent_start`` / ``on_agent_end`` and emits INFO for agent
lifecycle, DEBUG for every tool start/end and LLM call.
* ``interface/main.py`` drops the ``setLevel(ERROR)`` silencer.
Coverage expanded across ~20 files (orchestration, agents, runtime,
llm, tools, interface, config, skills) with INFO for lifecycle and
DEBUG for verbose detail. Per the system instructions in
``logger.warning(f"…{e}")`` were converted to module logger calls.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -550,6 +550,15 @@ async def create_agent(
|
||||
async with bus._lock:
|
||||
bus.tasks[child_id] = task_handle
|
||||
|
||||
logger.info(
|
||||
"create_agent: spawned %s (%s) parent=%s skills=%d task_len=%d",
|
||||
child_id,
|
||||
name,
|
||||
parent_id or "-",
|
||||
len(skills or []),
|
||||
len(task or ""),
|
||||
)
|
||||
|
||||
return json.dumps(
|
||||
{
|
||||
"success": True,
|
||||
@@ -659,6 +668,14 @@ async def agent_finish(
|
||||
)
|
||||
parent_notified = True
|
||||
|
||||
logger.info(
|
||||
"agent_finish: %s success=%s findings=%d parent_notified=%s",
|
||||
me,
|
||||
success,
|
||||
len(findings or []),
|
||||
parent_notified,
|
||||
)
|
||||
|
||||
return json.dumps(
|
||||
{
|
||||
"success": True,
|
||||
|
||||
@@ -59,14 +59,21 @@ def _do_finish(
|
||||
technical_analysis=technical_analysis.strip(),
|
||||
recommendations=recommendations.strip(),
|
||||
)
|
||||
vuln_count = len(tracer.vulnerability_reports)
|
||||
except (ImportError, AttributeError) as e:
|
||||
logger.exception("finish_scan persistence failed")
|
||||
return {"success": False, "message": f"Failed to complete scan: {e!s}"}
|
||||
else:
|
||||
logger.info(
|
||||
"finish_scan: completed scan with %d vulnerability report(s)",
|
||||
vuln_count,
|
||||
)
|
||||
return {
|
||||
"success": True,
|
||||
"scan_completed": True,
|
||||
"message": "Scan completed successfully",
|
||||
"vulnerabilities_found": len(tracer.vulnerability_reports),
|
||||
"vulnerabilities_found": vuln_count,
|
||||
}
|
||||
except (ImportError, AttributeError) as e:
|
||||
return {"success": False, "message": f"Failed to complete scan: {e!s}"}
|
||||
|
||||
|
||||
@function_tool(timeout=60)
|
||||
|
||||
@@ -247,6 +247,7 @@ async def python_action(
|
||||
sentinel = "__STRIX_PY_RESULT_" + uuid.uuid4().hex + "__"
|
||||
user_code_b64 = base64.b64encode(code.encode("utf-8")).decode("ascii")
|
||||
driver = _DRIVER_TEMPLATE.format(sentinel=sentinel, user_code_b64=user_code_b64)
|
||||
logger.info("python_action: invoking driver (code_len=%d, timeout=%ds)", len(code), timeout)
|
||||
# /tmp inside the sandbox container is single-user (pentester) and
|
||||
# disposable per scan; the multi-user race B108/S108 warns about
|
||||
# doesn't apply.
|
||||
|
||||
@@ -275,8 +275,16 @@ async def _do_create( # noqa: PLR0912
|
||||
code_locations=parsed_locations,
|
||||
)
|
||||
except (ImportError, AttributeError) as e:
|
||||
logger.exception("create_vulnerability_report persistence failed")
|
||||
return {"success": False, "message": f"Failed to create vulnerability report: {e!s}"}
|
||||
else:
|
||||
logger.info(
|
||||
"Vulnerability report created: id=%s severity=%s cvss=%.1f title=%s",
|
||||
report_id,
|
||||
severity,
|
||||
cvss_score,
|
||||
title,
|
||||
)
|
||||
return {
|
||||
"success": True,
|
||||
"message": f"Vulnerability report '{title}' created successfully",
|
||||
|
||||
@@ -4,6 +4,7 @@ from __future__ import annotations
|
||||
|
||||
import asyncio
|
||||
import json
|
||||
import logging
|
||||
from typing import Any
|
||||
|
||||
import requests
|
||||
@@ -12,6 +13,9 @@ from agents import RunContextWrapper, function_tool
|
||||
from strix.config import load_settings
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
_SYSTEM_PROMPT = """You are assisting a cybersecurity agent specialized in vulnerability scanning
|
||||
and security assessment running on Kali Linux. When responding to search queries:
|
||||
|
||||
@@ -40,11 +44,13 @@ security implications and details."""
|
||||
def _do_search(query: str) -> dict[str, Any]:
|
||||
api_key = load_settings().integrations.perplexity_api_key
|
||||
if not api_key:
|
||||
logger.warning("web_search invoked without PERPLEXITY_API_KEY configured")
|
||||
return {
|
||||
"success": False,
|
||||
"message": "PERPLEXITY_API_KEY environment variable not set",
|
||||
"results": [],
|
||||
}
|
||||
logger.info("web_search query (len=%d): %s", len(query), query[:120])
|
||||
|
||||
url = "https://api.perplexity.ai/chat/completions"
|
||||
headers = {"Authorization": f"Bearer {api_key}", "Content-Type": "application/json"}
|
||||
@@ -61,16 +67,20 @@ def _do_search(query: str) -> dict[str, Any]:
|
||||
response.raise_for_status()
|
||||
content = response.json()["choices"][0]["message"]["content"]
|
||||
except requests.exceptions.Timeout:
|
||||
logger.warning("web_search timed out")
|
||||
return {"success": False, "message": "Request timed out", "results": []}
|
||||
except requests.exceptions.RequestException as e:
|
||||
logger.exception("web_search API request failed")
|
||||
return {"success": False, "message": f"API request failed: {e!s}", "results": []}
|
||||
except KeyError as e:
|
||||
logger.exception("web_search response shape unexpected")
|
||||
return {
|
||||
"success": False,
|
||||
"message": f"Unexpected API response format: missing {e!s}",
|
||||
"results": [],
|
||||
}
|
||||
except Exception as e: # noqa: BLE001
|
||||
except Exception as e:
|
||||
logger.exception("web_search failed")
|
||||
return {"success": False, "message": f"Web search failed: {e!s}", "results": []}
|
||||
else:
|
||||
return {
|
||||
|
||||
Reference in New Issue
Block a user