From a668baa4d0a638241e609edc82503458a5518ae9 Mon Sep 17 00:00:00 2001 From: Bill Date: Fri, 2 Jan 2026 12:37:19 -0500 Subject: [PATCH] feat(logging): add log line formatter --- src/grist_mcp/logging.py | 28 +++++++++++++++++++++ tests/unit/test_logging.py | 50 +++++++++++++++++++++++++++++++++++++- 2 files changed, 77 insertions(+), 1 deletion(-) diff --git a/src/grist_mcp/logging.py b/src/grist_mcp/logging.py index a8f1b0c..ca92ed5 100644 --- a/src/grist_mcp/logging.py +++ b/src/grist_mcp/logging.py @@ -1,5 +1,7 @@ """Logging configuration and utilities.""" +from datetime import datetime + def extract_stats(tool_name: str, arguments: dict, result: dict) -> str: """Extract meaningful stats from tool call based on tool type.""" @@ -53,3 +55,29 @@ def truncate_token(token: str) -> str: if len(token) <= 8: return "***" return f"{token[:3]}...{token[-3:]}" + + +def format_tool_log( + agent_name: str, + token: str, + tool: str, + document: str | None, + stats: str, + status: str, + duration_ms: int, + error_message: str | None = None, +) -> str: + """Format a tool call log line. + + Format: YYYY-MM-DD HH:MM:SS | agent (token) | tool | doc | stats | status | duration + """ + timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S") + truncated = truncate_token(token) + doc = document if document else "-" + + line = f"{timestamp} | {agent_name} ({truncated}) | {tool} | {doc} | {stats} | {status} | {duration_ms}ms" + + if error_message: + line += f"\n {error_message}" + + return line diff --git a/tests/unit/test_logging.py b/tests/unit/test_logging.py index c750936..c21943e 100644 --- a/tests/unit/test_logging.py +++ b/tests/unit/test_logging.py @@ -1,6 +1,6 @@ """Unit tests for logging module.""" -from grist_mcp.logging import truncate_token, extract_stats +from grist_mcp.logging import truncate_token, extract_stats, format_tool_log class TestTruncateToken: @@ -71,3 +71,51 @@ class TestExtractStats: def test_unknown_tool(self): assert extract_stats("unknown_tool", {}, {}) == "-" + + +class TestFormatToolLog: + def test_success_format(self): + line = format_tool_log( + agent_name="dev-agent", + token="abcdefghijklmnop", + tool="get_records", + document="sales", + stats="42 records", + status="success", + duration_ms=125, + ) + assert "dev-agent" in line + assert "abc...nop" in line + assert "get_records" in line + assert "sales" in line + assert "42 records" in line + assert "success" in line + assert "125ms" in line + # Check pipe-delimited format + assert line.count("|") == 6 + + def test_no_document(self): + line = format_tool_log( + agent_name="dev-agent", + token="abcdefghijklmnop", + tool="list_documents", + document=None, + stats="3 docs", + status="success", + duration_ms=45, + ) + assert "| - |" in line + + def test_error_format(self): + line = format_tool_log( + agent_name="dev-agent", + token="abcdefghijklmnop", + tool="add_records", + document="inventory", + stats="5 records", + status="error", + duration_ms=89, + error_message="Grist API error: Invalid column 'foo'", + ) + assert "error" in line + assert "\n Grist API error: Invalid column 'foo'" in line