Add comprehensive structured logging system
Features: - JSON-formatted logs for easy parsing and analysis - Rotating log files (prevents disk space issues) * ajarbot.log: All events, 10MB rotation, 5 backups * errors.log: Errors only, 5MB rotation, 3 backups * tools.log: Tool execution tracking, 10MB rotation, 3 backups Tool Execution Tracking: - Every tool call logged with inputs, outputs, duration - Success/failure status tracking - Performance metrics (execution time in milliseconds) - Error messages captured with full context Logging Integration: - tools.py: All tool executions automatically logged - Structured logger classes with context preservation - Console output (human-readable) + file logs (JSON) - Separate error log for quick issue identification Log Analysis: - JSON format enables programmatic analysis - Easy to search for patterns (max tokens, iterations, etc.) - Performance tracking (slow tools, failure rates) - Historical debugging with full context Documentation: - LOGGING.md: Complete usage guide - Log analysis examples with jq commands - Error pattern reference - Maintenance and integration instructions Benefits: - Quick error diagnosis with separate errors.log - Performance monitoring and optimization - Historical analysis for troubleshooting - Automatic log rotation (max 95MB total) Updated .gitignore to exclude logs/ directory Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
106
tools.py
106
tools.py
@@ -341,6 +341,12 @@ TOOL_DEFINITIONS = [
|
||||
|
||||
def execute_tool(tool_name: str, tool_input: Dict[str, Any], healing_system: Any = None) -> str:
|
||||
"""Execute a tool and return the result as a string."""
|
||||
import time
|
||||
from logging_config import get_tool_logger
|
||||
|
||||
logger = get_tool_logger()
|
||||
start_time = time.time()
|
||||
|
||||
try:
|
||||
# MCP tools (zettelkasten + web_fetch) - route to mcp_tools.py
|
||||
MCP_TOOLS = {
|
||||
@@ -375,37 +381,61 @@ def execute_tool(tool_name: str, tool_input: Dict[str, Any], healing_system: Any
|
||||
# Convert result to string if needed
|
||||
if isinstance(result, dict):
|
||||
if "error" in result:
|
||||
return f"Error: {result['error']}"
|
||||
error_msg = f"Error: {result['error']}"
|
||||
duration_ms = (time.time() - start_time) * 1000
|
||||
logger.log_tool_call(
|
||||
tool_name=tool_name,
|
||||
inputs=tool_input,
|
||||
success=False,
|
||||
error=error_msg,
|
||||
duration_ms=duration_ms
|
||||
)
|
||||
return error_msg
|
||||
elif "content" in result:
|
||||
return result["content"]
|
||||
result_str = result["content"]
|
||||
else:
|
||||
return str(result)
|
||||
return str(result)
|
||||
result_str = str(result)
|
||||
else:
|
||||
result_str = str(result)
|
||||
|
||||
# Log successful execution
|
||||
duration_ms = (time.time() - start_time) * 1000
|
||||
logger.log_tool_call(
|
||||
tool_name=tool_name,
|
||||
inputs=tool_input,
|
||||
success=True,
|
||||
result=result_str,
|
||||
duration_ms=duration_ms
|
||||
)
|
||||
return result_str
|
||||
|
||||
# File tools (traditional handlers - kept for backward compatibility)
|
||||
# Execute traditional tool and capture result
|
||||
result_str = None
|
||||
|
||||
if tool_name == "read_file":
|
||||
return _read_file(tool_input["file_path"])
|
||||
result_str = _read_file(tool_input["file_path"])
|
||||
elif tool_name == "write_file":
|
||||
return _write_file(tool_input["file_path"], tool_input["content"])
|
||||
result_str = _write_file(tool_input["file_path"], tool_input["content"])
|
||||
elif tool_name == "edit_file":
|
||||
return _edit_file(
|
||||
result_str = _edit_file(
|
||||
tool_input["file_path"],
|
||||
tool_input["old_text"],
|
||||
tool_input["new_text"],
|
||||
)
|
||||
elif tool_name == "list_directory":
|
||||
path = tool_input.get("path", ".")
|
||||
return _list_directory(path)
|
||||
result_str = _list_directory(path)
|
||||
elif tool_name == "run_command":
|
||||
command = tool_input["command"]
|
||||
working_dir = tool_input.get("working_dir", ".")
|
||||
return _run_command(command, working_dir)
|
||||
result_str = _run_command(command, working_dir)
|
||||
elif tool_name == "get_weather":
|
||||
location = tool_input.get("location", "Phoenix, US")
|
||||
return _get_weather(location)
|
||||
result_str = _get_weather(location)
|
||||
# Gmail tools
|
||||
elif tool_name == "send_email":
|
||||
return _send_email(
|
||||
result_str = _send_email(
|
||||
to=tool_input["to"],
|
||||
subject=tool_input["subject"],
|
||||
body=tool_input["body"],
|
||||
@@ -413,25 +443,25 @@ def execute_tool(tool_name: str, tool_input: Dict[str, Any], healing_system: Any
|
||||
reply_to_message_id=tool_input.get("reply_to_message_id"),
|
||||
)
|
||||
elif tool_name == "read_emails":
|
||||
return _read_emails(
|
||||
result_str = _read_emails(
|
||||
query=tool_input.get("query", ""),
|
||||
max_results=tool_input.get("max_results", 10),
|
||||
include_body=tool_input.get("include_body", False),
|
||||
)
|
||||
elif tool_name == "get_email":
|
||||
return _get_email(
|
||||
result_str = _get_email(
|
||||
message_id=tool_input["message_id"],
|
||||
format_type=tool_input.get("format", "text"),
|
||||
)
|
||||
# Calendar tools
|
||||
elif tool_name == "read_calendar":
|
||||
return _read_calendar(
|
||||
result_str = _read_calendar(
|
||||
days_ahead=tool_input.get("days_ahead", 7),
|
||||
calendar_id=tool_input.get("calendar_id", "primary"),
|
||||
max_results=tool_input.get("max_results", 20),
|
||||
)
|
||||
elif tool_name == "create_calendar_event":
|
||||
return _create_calendar_event(
|
||||
result_str = _create_calendar_event(
|
||||
summary=tool_input["summary"],
|
||||
start_time=tool_input["start_time"],
|
||||
end_time=tool_input["end_time"],
|
||||
@@ -440,13 +470,13 @@ def execute_tool(tool_name: str, tool_input: Dict[str, Any], healing_system: Any
|
||||
calendar_id=tool_input.get("calendar_id", "primary"),
|
||||
)
|
||||
elif tool_name == "search_calendar":
|
||||
return _search_calendar(
|
||||
result_str = _search_calendar(
|
||||
query=tool_input["query"],
|
||||
calendar_id=tool_input.get("calendar_id", "primary"),
|
||||
)
|
||||
# Contacts tools
|
||||
elif tool_name == "create_contact":
|
||||
return _create_contact(
|
||||
result_str = _create_contact(
|
||||
given_name=tool_input["given_name"],
|
||||
family_name=tool_input.get("family_name", ""),
|
||||
email=tool_input.get("email", ""),
|
||||
@@ -454,17 +484,51 @@ def execute_tool(tool_name: str, tool_input: Dict[str, Any], healing_system: Any
|
||||
notes=tool_input.get("notes"),
|
||||
)
|
||||
elif tool_name == "list_contacts":
|
||||
return _list_contacts(
|
||||
result_str = _list_contacts(
|
||||
max_results=tool_input.get("max_results", 100),
|
||||
query=tool_input.get("query"),
|
||||
)
|
||||
elif tool_name == "get_contact":
|
||||
return _get_contact(
|
||||
result_str = _get_contact(
|
||||
resource_name=tool_input["resource_name"],
|
||||
)
|
||||
|
||||
# Log successful traditional tool execution
|
||||
if result_str is not None:
|
||||
duration_ms = (time.time() - start_time) * 1000
|
||||
logger.log_tool_call(
|
||||
tool_name=tool_name,
|
||||
inputs=tool_input,
|
||||
success=True,
|
||||
result=result_str,
|
||||
duration_ms=duration_ms
|
||||
)
|
||||
return result_str
|
||||
else:
|
||||
return f"Error: Unknown tool '{tool_name}'"
|
||||
duration_ms = (time.time() - start_time) * 1000
|
||||
error_msg = f"Error: Unknown tool '{tool_name}'"
|
||||
logger.log_tool_call(
|
||||
tool_name=tool_name,
|
||||
inputs=tool_input,
|
||||
success=False,
|
||||
error=error_msg,
|
||||
duration_ms=duration_ms
|
||||
)
|
||||
return error_msg
|
||||
except Exception as e:
|
||||
duration_ms = (time.time() - start_time) * 1000
|
||||
error_msg = str(e)
|
||||
|
||||
# Log the error
|
||||
logger.log_tool_call(
|
||||
tool_name=tool_name,
|
||||
inputs=tool_input,
|
||||
success=False,
|
||||
error=error_msg,
|
||||
duration_ms=duration_ms
|
||||
)
|
||||
|
||||
# Capture in healing system if available
|
||||
if healing_system:
|
||||
healing_system.capture_error(
|
||||
error=e,
|
||||
@@ -472,7 +536,7 @@ def execute_tool(tool_name: str, tool_input: Dict[str, Any], healing_system: Any
|
||||
intent=f"Executing {tool_name} tool",
|
||||
context={"tool_name": tool_name, "input": tool_input},
|
||||
)
|
||||
return f"Error executing {tool_name}: {str(e)}"
|
||||
return f"Error executing {tool_name}: {error_msg}"
|
||||
|
||||
|
||||
# Maximum characters of tool output to return (prevents token explosion)
|
||||
|
||||
Reference in New Issue
Block a user