Merge pull request #1251 from NousResearch/hermes/hermes-f7e92273

fix: prevent logging handler accumulation in gateway mode
This commit is contained in:
Teknium
2026-03-14 00:04:13 -07:00
committed by GitHub
2 changed files with 81 additions and 12 deletions

View File

@@ -407,19 +407,30 @@ class AIAgent:
# Persistent error log -- always writes WARNING+ to ~/.hermes/logs/errors.log # Persistent error log -- always writes WARNING+ to ~/.hermes/logs/errors.log
# so tool failures, API errors, etc. are inspectable after the fact. # so tool failures, API errors, etc. are inspectable after the fact.
from agent.redact import RedactingFormatter # In gateway mode, each incoming message creates a new AIAgent instance,
_error_log_dir = _hermes_home / "logs" # while the root logger is process-global. Re-adding the same errors.log
_error_log_dir.mkdir(parents=True, exist_ok=True) # handler would cause each warning/error line to be written multiple times.
_error_log_path = _error_log_dir / "errors.log"
from logging.handlers import RotatingFileHandler from logging.handlers import RotatingFileHandler
_error_file_handler = RotatingFileHandler( root_logger = logging.getLogger()
_error_log_path, maxBytes=2 * 1024 * 1024, backupCount=2, 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) if not has_errors_log_handler:
_error_file_handler.setFormatter(RedactingFormatter( from agent.redact import RedactingFormatter
'%(asctime)s %(levelname)s %(name)s: %(message)s', error_log_dir.mkdir(parents=True, exist_ok=True)
)) error_file_handler = RotatingFileHandler(
logging.getLogger().addHandler(_error_file_handler) 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: if self.verbose_logging:
logging.basicConfig( logging.basicConfig(

View File

@@ -6,13 +6,17 @@ are made.
""" """
import json import json
import logging
import re import re
import uuid import uuid
from logging.handlers import RotatingFileHandler
from pathlib import Path
from types import SimpleNamespace from types import SimpleNamespace
from unittest.mock import MagicMock, patch from unittest.mock import MagicMock, patch
import pytest import pytest
import run_agent
from honcho_integration.client import HonchoClientConfig from honcho_integration.client import HonchoClientConfig
from run_agent import AIAgent, _inject_honcho_turn_context from run_agent import AIAgent, _inject_honcho_turn_context
from agent.prompt_builder import DEFAULT_AGENT_IDENTITY from agent.prompt_builder import DEFAULT_AGENT_IDENTITY
@@ -70,7 +74,7 @@ def agent_with_memory_tool():
patch("run_agent.OpenAI"), patch("run_agent.OpenAI"),
): ):
a = AIAgent( a = AIAgent(
api_key="test-key-1234567890", api_key="test-k...7890",
quiet_mode=True, quiet_mode=True,
skip_context_files=True, skip_context_files=True,
skip_memory=True, skip_memory=True,
@@ -79,6 +83,60 @@ def agent_with_memory_tool():
return a 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) # Helper to build mock assistant messages (API response objects)
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------