Merge pull request #1251 from NousResearch/hermes/hermes-f7e92273
fix: prevent logging handler accumulation in gateway mode
This commit is contained in:
33
run_agent.py
33
run_agent.py
@@ -407,19 +407,30 @@ class AIAgent:
|
||||
|
||||
# Persistent error log -- always writes WARNING+ to ~/.hermes/logs/errors.log
|
||||
# so tool failures, API errors, etc. are inspectable after the fact.
|
||||
from agent.redact import RedactingFormatter
|
||||
_error_log_dir = _hermes_home / "logs"
|
||||
_error_log_dir.mkdir(parents=True, exist_ok=True)
|
||||
_error_log_path = _error_log_dir / "errors.log"
|
||||
# In gateway mode, each incoming message creates a new AIAgent instance,
|
||||
# while the root logger is process-global. Re-adding the same errors.log
|
||||
# handler would cause each warning/error line to be written multiple times.
|
||||
from logging.handlers import RotatingFileHandler
|
||||
_error_file_handler = RotatingFileHandler(
|
||||
_error_log_path, maxBytes=2 * 1024 * 1024, backupCount=2,
|
||||
root_logger = logging.getLogger()
|
||||
error_log_dir = _hermes_home / "logs"
|
||||
error_log_path = error_log_dir / "errors.log"
|
||||
resolved_error_log_path = error_log_path.resolve()
|
||||
has_errors_log_handler = any(
|
||||
isinstance(handler, RotatingFileHandler)
|
||||
and Path(getattr(handler, "baseFilename", "")).resolve() == resolved_error_log_path
|
||||
for handler in root_logger.handlers
|
||||
)
|
||||
_error_file_handler.setLevel(logging.WARNING)
|
||||
_error_file_handler.setFormatter(RedactingFormatter(
|
||||
'%(asctime)s %(levelname)s %(name)s: %(message)s',
|
||||
))
|
||||
logging.getLogger().addHandler(_error_file_handler)
|
||||
if not has_errors_log_handler:
|
||||
from agent.redact import RedactingFormatter
|
||||
error_log_dir.mkdir(parents=True, exist_ok=True)
|
||||
error_file_handler = RotatingFileHandler(
|
||||
error_log_path, maxBytes=2 * 1024 * 1024, backupCount=2,
|
||||
)
|
||||
error_file_handler.setLevel(logging.WARNING)
|
||||
error_file_handler.setFormatter(RedactingFormatter(
|
||||
'%(asctime)s %(levelname)s %(name)s: %(message)s',
|
||||
))
|
||||
root_logger.addHandler(error_file_handler)
|
||||
|
||||
if self.verbose_logging:
|
||||
logging.basicConfig(
|
||||
|
||||
@@ -6,13 +6,17 @@ are made.
|
||||
"""
|
||||
|
||||
import json
|
||||
import logging
|
||||
import re
|
||||
import uuid
|
||||
from logging.handlers import RotatingFileHandler
|
||||
from pathlib import Path
|
||||
from types import SimpleNamespace
|
||||
from unittest.mock import MagicMock, patch
|
||||
|
||||
import pytest
|
||||
|
||||
import run_agent
|
||||
from honcho_integration.client import HonchoClientConfig
|
||||
from run_agent import AIAgent, _inject_honcho_turn_context
|
||||
from agent.prompt_builder import DEFAULT_AGENT_IDENTITY
|
||||
@@ -70,7 +74,7 @@ def agent_with_memory_tool():
|
||||
patch("run_agent.OpenAI"),
|
||||
):
|
||||
a = AIAgent(
|
||||
api_key="test-key-1234567890",
|
||||
api_key="test-k...7890",
|
||||
quiet_mode=True,
|
||||
skip_context_files=True,
|
||||
skip_memory=True,
|
||||
@@ -79,6 +83,60 @@ def agent_with_memory_tool():
|
||||
return a
|
||||
|
||||
|
||||
def test_aiagent_reuses_existing_errors_log_handler():
|
||||
"""Repeated AIAgent init should not accumulate duplicate errors.log handlers."""
|
||||
root_logger = logging.getLogger()
|
||||
original_handlers = list(root_logger.handlers)
|
||||
error_log_path = (run_agent._hermes_home / "logs" / "errors.log").resolve()
|
||||
|
||||
try:
|
||||
for handler in list(root_logger.handlers):
|
||||
root_logger.removeHandler(handler)
|
||||
|
||||
error_log_path.parent.mkdir(parents=True, exist_ok=True)
|
||||
preexisting_handler = RotatingFileHandler(
|
||||
error_log_path,
|
||||
maxBytes=2 * 1024 * 1024,
|
||||
backupCount=2,
|
||||
)
|
||||
root_logger.addHandler(preexisting_handler)
|
||||
|
||||
with (
|
||||
patch(
|
||||
"run_agent.get_tool_definitions",
|
||||
return_value=_make_tool_defs("web_search"),
|
||||
),
|
||||
patch("run_agent.check_toolset_requirements", return_value={}),
|
||||
patch("run_agent.OpenAI"),
|
||||
):
|
||||
AIAgent(
|
||||
api_key="test-k...7890",
|
||||
quiet_mode=True,
|
||||
skip_context_files=True,
|
||||
skip_memory=True,
|
||||
)
|
||||
AIAgent(
|
||||
api_key="test-k...7890",
|
||||
quiet_mode=True,
|
||||
skip_context_files=True,
|
||||
skip_memory=True,
|
||||
)
|
||||
|
||||
matching_handlers = [
|
||||
handler for handler in root_logger.handlers
|
||||
if isinstance(handler, RotatingFileHandler)
|
||||
and error_log_path == Path(handler.baseFilename).resolve()
|
||||
]
|
||||
assert len(matching_handlers) == 1
|
||||
finally:
|
||||
for handler in list(root_logger.handlers):
|
||||
root_logger.removeHandler(handler)
|
||||
if handler not in original_handlers:
|
||||
handler.close()
|
||||
for handler in original_handlers:
|
||||
root_logger.addHandler(handler)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Helper to build mock assistant messages (API response objects)
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
Reference in New Issue
Block a user