feat: component-separated logging with session context and filtering (#7991)

* feat: component-separated logging with session context and filtering

Phase 1 — Gateway log isolation:
- gateway.log now only receives records from gateway.* loggers
  (platform adapters, session management, slash commands, delivery)
- agent.log remains the catch-all (all components)
- errors.log remains WARNING+ catch-all
- Moved gateway.log handler creation from gateway/run.py into
  hermes_logging.setup_logging(mode='gateway') with _ComponentFilter

Phase 2 — Session ID injection:
- Added set_session_context(session_id) / clear_session_context() API
  using threading.local() for per-thread session tracking
- _SessionFilter enriches every log record with session_tag attribute
- Log format: '2026-04-11 10:23:45 INFO [session_id] logger.name: msg'
- Session context set at start of run_conversation() in run_agent.py
- Thread-isolated: gateway conversations on different threads don't leak

Phase 3 — Component filtering in hermes logs:
- Added --component flag: hermes logs --component gateway|agent|tools|cli|cron
- COMPONENT_PREFIXES maps component names to logger name prefixes
- Works with all existing filters (--level, --session, --since, -f)
- Logger name extraction handles both old and new log formats

Files changed:
- hermes_logging.py: _SessionFilter, _ComponentFilter, COMPONENT_PREFIXES,
  set/clear_session_context(), gateway.log creation in setup_logging()
- gateway/run.py: removed redundant gateway.log handler (now in hermes_logging)
- run_agent.py: set_session_context() at start of run_conversation()
- hermes_cli/logs.py: --component filter, logger name extraction
- hermes_cli/main.py: --component argument on logs subparser

Addresses community request for component-separated, filterable logging.
Zero changes to existing logger names — __name__ already provides hierarchy.

* fix: use LogRecord factory instead of per-handler _SessionFilter

The _SessionFilter approach required attaching a filter to every handler
we create. Any handler created outside our _add_rotating_handler (like
the gateway stderr handler, or third-party handlers) would crash with
KeyError: 'session_tag' if it used our format string.

Replace with logging.setLogRecordFactory() which injects session_tag
into every LogRecord at creation time — process-global, zero per-handler
wiring needed. The factory is installed at import time (before
setup_logging) so session_tag is available from the moment hermes_logging
is imported.

- Idempotent: marker attribute prevents double-wrapping on module reload
- Chains with existing factory: won't break third-party record factories
- Removes _SessionFilter from _add_rotating_handler and setup_verbose_logging
- Adds tests: record factory injection, idempotency, arbitrary handler compat
This commit is contained in:
Teknium 2026-04-11 17:23:36 -07:00 committed by GitHub
parent 723b5bec85
commit fd73937ec8
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 728 additions and 230 deletions

View file

@ -8458,23 +8458,11 @@ async def start_gateway(config: Optional[GatewayConfig] = None, replace: bool =
except Exception:
pass
# Centralized logging — agent.log (INFO+) and errors.log (WARNING+).
# Centralized logging — agent.log (INFO+), errors.log (WARNING+),
# and gateway.log (INFO+, gateway-component records only).
# Idempotent, so repeated calls from AIAgent.__init__ won't duplicate.
from hermes_logging import setup_logging
log_dir = setup_logging(hermes_home=_hermes_home, mode="gateway")
# Gateway-specific rotating log — captures all gateway-level messages
# (session management, platform adapters, slash commands, etc.).
from agent.redact import RedactingFormatter
from hermes_logging import _add_rotating_handler
_add_rotating_handler(
logging.getLogger(),
log_dir / 'gateway.log',
level=logging.INFO,
max_bytes=5 * 1024 * 1024,
backup_count=3,
formatter=RedactingFormatter('%(asctime)s %(levelname)s %(name)s: %(message)s'),
)
setup_logging(hermes_home=_hermes_home, mode="gateway")
# Optional stderr handler — level driven by -v/-q flags on the CLI.
# verbosity=None (-q/--quiet): no stderr output