
    
Bj4                        d Z ddlZddlZddlZddlmZ ddlmZ ddlm	Z	m
Z
 ddlmZmZ da ej                         ZdZd	Zd
ZdeddfdZd,dZd,dZ e         G d dej0                        ZddddddZdddddddde	e   de	e   de	e   de	e   de	e   dedefdZd,d Z G d! d"e      Zdd#d$ej@                  d%ed&ed'eded(ejB                  d)e	ej0                     ddfd*Z"d+ Z#y)-u!  Centralized logging setup for Hermes Agent.

Provides a single ``setup_logging()`` entry point that both the CLI and
gateway call early in their startup path.  All log files live under
``~/.hermes/logs/`` (profile-aware via ``get_hermes_home()``).

Log files produced:
    agent.log   — INFO+, all agent/tool/session activity (the main log)
    errors.log  — WARNING+, errors and warnings only (quick triage)
    gateway.log — INFO+, gateway-only events (created when mode="gateway")

All files use ``RotatingFileHandler`` with ``RedactingFormatter`` so
secrets are never written to disk.

Component separation:
    gateway.log only receives records from ``gateway.*`` loggers —
    platform adapters, session management, slash commands, delivery.
    agent.log remains the catch-all (everything goes there).

Session context:
    Call ``set_session_context(session_id)`` at the start of a conversation
    and ``clear_session_context()`` when done.  All log lines emitted on
    that thread will include ``[session_id]`` for filtering/correlation.
    N)RotatingFileHandler)Path)OptionalSequence)get_config_pathget_hermes_homeFz>%(asctime)s %(levelname)s%(session_tag)s %(name)s: %(message)szC%(asctime)s - %(name)s - %(levelname)s%(session_tag)s - %(message)s)openaizopenai._base_clienthttpxhttpcoreasynciohpackzhpack.hpackgrpcmodalurllib3zurllib3.connectionpool
websocketscharset_normalizermarkdown_it
session_idreturnc                     | t         _        y)zSet the session ID for the current thread.

    All subsequent log records on this thread will include ``[session_id]``
    in the formatted output.  Call at the start of ``run_conversation()``.
    N_session_contextr   )r   s    3/home/ubuntu/.hermes/hermes-agent/hermes_logging.pyset_session_contextr   H   s     #-    c                      dt         _        y)z,Clear the session ID for the current thread.Nr    r   r   clear_session_contextr   Q   s    "&r   c                      t        j                         t        dd      ryfd} d| _        t        j                  |        y)ub  Replace the global LogRecord factory with one that adds ``session_tag``.

    Unlike a ``logging.Filter`` on a handler or logger, the record factory
    runs for EVERY record in the process — including records that propagate
    from child loggers and records handled by third-party handlers.  This
    guarantees ``%(session_tag)s`` is always available in format strings,
    eliminating the KeyError that would occur if a handler used our format
    without having a ``_SessionFilter`` attached.

    Idempotent — checks for a marker attribute to avoid double-wrapping if
    the module is reloaded.
    _hermes_session_injectorFNc                  f     | i |}t        t        dd       }|rd| d|_        |S d|_        |S )Nr   z [] )getattrr   session_tag)argskwargsrecordsidcurrent_factorys       r   _session_record_factoryz@_install_session_record_factory.<locals>._session_record_factoryk   sG     $1&1&d;,/r#a[ 68r   T)logginggetLogRecordFactoryr$   r    setLogRecordFactory)r+   r*   s    @r   _install_session_record_factoryr/   Z   sB     113O :EB 8<4 78r   c                   V     e Zd ZdZdee   ddf fdZdej                  de	fdZ
 xZS )_ComponentFilterzOnly pass records whose logger name starts with one of *prefixes*.

    Used to route gateway-specific records to ``gateway.log`` while
    keeping ``agent.log`` as the catch-all.
    prefixesr   Nc                 B    t         |           t        |      | _        y N)super__init__tuple	_prefixes)selfr2   	__class__s     r   r6   z_ComponentFilter.__init__   s    xr   r(   c                 L    |j                   j                  | j                        S r4   )name
startswithr8   )r9   r(   s     r   filterz_ComponentFilter.filter   s    {{%%dnn55r   )__name__
__module____qualname____doc__r   strr6   r,   	LogRecordboolr>   __classcell__r:   s   @r   r1   r1   ~   s8    )# )4 )6W.. 64 6r   r1   )gateway)agent	run_agentmodel_toolsbatch_runner)tools)
hermes_clicli)cron)rH   rI   rM   rO   rP   )hermes_home	log_levelmax_size_mbbackup_countmodeforcerQ   rR   rS   rT   rU   rV   c                 h   | xs
 t               }|dz  }|j                  dd       t               \  }}	}
|xs |xs dj                         }t	        t
        |t
        j                        }|xs |	xs ddz  dz  }|xs |
xs d}dd	lm} t        j                         }t        ||d
z  ||| |t                     t        ||dz  t
        j                  dd |t                     |dk(  r=t        ||dz  t
        j                  dd |t              t        t        d                t        r|s|S |j                   t
        j"                  k(  s|j                   |kD  r|j%                  |       t&        D ]4  }t        j                  |      j%                  t
        j                         6 da|S )u  Configure the Hermes logging subsystem.

    Safe to call multiple times — the second call is a no-op unless
    *force* is ``True``.

    Parameters
    ----------
    hermes_home
        Override for the Hermes home directory.  Falls back to
        ``get_hermes_home()`` (profile-aware).
    log_level
        Minimum level for the ``agent.log`` file handler.  Accepts any
        standard Python level name (``"DEBUG"``, ``"INFO"``, ``"WARNING"``).
        Defaults to ``"INFO"`` or the value from config.yaml ``logging.level``.
    max_size_mb
        Maximum size of each log file in megabytes before rotation.
        Defaults to 5 or the value from config.yaml ``logging.max_size_mb``.
    backup_count
        Number of rotated backup files to keep.
        Defaults to 3 or the value from config.yaml ``logging.backup_count``.
    mode
        Caller context: ``"cli"``, ``"gateway"``, ``"cron"``.
        When ``"gateway"``, an additional ``gateway.log`` file is created
        that receives only gateway-component records.
    force
        Re-run setup even if it has already been called.

    Returns
    -------
    Path
        The ``logs/`` directory where files are written.
    logsTparentsexist_okINFO   i      r   RedactingFormatterz	agent.log)level	max_bytesrT   	formatterz
errors.logi       rH   zgateway.logi  P )ra   rb   rT   rc   
log_filter)r   mkdir_read_logging_configupperr$   r,   r\   agent.redactr`   	getLogger_add_rotating_handler_LOG_FORMATWARNINGr1   COMPONENT_PREFIXES_logging_initializedra   NOTSETsetLevel_NOISY_LOGGERS)rQ   rR   rS   rT   rU   rV   homelog_dir	cfg_levelcfg_max_size
cfg_backup
level_namera   rb   backupsr`   rootr<   s                     r   setup_loggingr{      s   T +/+DVmGMM$M. +?*@'I|Z2y2F99;JGZ6E11T9D@I-j-AG 0D +$[1 ,oo!$[1 ym#,,%(5'(:9(EF	
 E zzW^^#tzzE'9e  :$((9:  Nr   c                     ddl m}  t        j                         }|j                  D ]=  }t        |t        j                        st        |t              r/t        |dd      s= y t        j                         }|j                  t        j                         |j                   | t        d             d|_        |j                  |       |j                  t        j                  kD  r|j                  t        j                         t         D ]4  }t        j                  |      j                  t        j"                         6 t        j                  d	      j                  t        j$                         y)
zEnable DEBUG-level console logging for ``--verbose`` / ``-v`` mode.

    Called by ``AIAgent.__init__()`` when ``verbose_logging=True``.
    r   r_   _hermes_verboseFNz%H:%M:%S)datefmtTz
rex-deploy)ri   r`   r,   rj   handlers
isinstanceStreamHandlerr   r$   rq   DEBUGsetFormatter_LOG_FORMAT_VERBOSEr}   
addHandlerra   rr   rm   r\   )r`   rz   hhandlerr<   s        r   setup_verbose_loggingr     s   
 0D ]] a../
1FY8Zq+U3
 ##%GW]]#+,?TU"GOOG zzGMM!gmm$  :$((9: l#,,W\\:r   c                   <     e Zd ZdZ fdZd Z fdZ fdZ xZS )_ManagedRotatingFileHandleru  RotatingFileHandler that ensures group-writable perms in managed mode.

    In managed mode (NixOS), the stateDir uses setgid (2770) so new files
    inherit the hermes group. However, both _open() (initial creation) and
    doRollover() create files via open(), which uses the process umask —
    typically 0022, producing 0644. This subclass applies chmod 0660 after
    both operations so the gateway and interactive users can share log files.
    c                 H    ddl m}  |       | _        t        |   |i | y )Nr   )
is_managed)hermes_cli.configr   _managedr5   r6   )r9   r&   r'   r   r:   s       r   r6   z$_ManagedRotatingFileHandler.__init__4  s!    0"$)&)r   c                 ~    | j                   r"	 t        j                  | j                  d       y y # t        $ r Y y w xY w)Ni  )r   oschmodbaseFilenameOSError)r9   s    r   _chmod_if_managedz-_ManagedRotatingFileHandler._chmod_if_managed9  s;    ==**E2   s    0 	<<c                 D    t         |          }| j                          |S r4   )r5   _openr   )r9   streamr:   s     r   r   z!_ManagedRotatingFileHandler._open@  s     r   c                 B    t         |           | j                          y r4   )r5   
doRolloverr   )r9   r:   s    r   r   z&_ManagedRotatingFileHandler.doRolloverE  s     r   )	r?   r@   rA   rB   r6   r   r   r   rF   rG   s   @r   r   r   *  s!    *

! !r   r   )re   loggerpathra   rb   rc   re   c                   |j                         }| j                  D ]<  }t        |t              st	        t        |dd            j                         |k(  s< y |j                  j                  dd       t        t        |      ||d      }	|	j                  |       |	j                  |       ||	j                  |       | j                  |	       y)a  Add a ``RotatingFileHandler`` to *logger*, skipping if one already
    exists for the same resolved file path (idempotent).

    Parameters
    ----------
    log_filter
        Optional filter to attach to the handler (e.g. ``_ComponentFilter``
        for gateway.log).
    r   r#   NTrY   utf-8)maxBytesbackupCountencoding)resolver   r   r   r   r$   parentrf   r   rC   rq   r   	addFilterr   )
r   r   ra   rb   rT   rc   re   resolvedexistingr   s
             r   rk   rk   J  s    & ||~HOO x!45WX~r:;CCEQ 	KKdT2)D	I<G U#*%
gr   c                  x   	 ddl } t               }|j                         rt        |dd      5 }| j	                  |      xs i }ddd       j                  di       }t        |t              r2|j                  d      |j                  d      |j                  d	      fS y
# 1 sw Y   ^xY w# t        $ r Y y
w xY w)u   Best-effort read of ``logging.*`` from config.yaml.

    Returns ``(level, max_size_mb, backup_count)`` — any may be ``None``.
    r   Nrr   )r   r,   ra   rS   rT   )NNN)	yamlr   existsopen	safe_loadgetr   dict	Exception)r   config_pathfcfglog_cfgs        r   rg   rg   q  s    
%'k39 .QnnQ'-2.ggi,G'4(KK(KK.KK/  . .  s)   ,B- B!AB- !B*&B- -	B98B9)r   N)$rB   r,   r   	threadinglogging.handlersr   pathlibr   typingr   r   hermes_constantsr   r   ro   localr   rl   r   rr   rC   r   r   r/   Filterr1   rn   intrE   r{   r   r   Logger	Formatterrk   rg   r   r   r   <module>r      s  2  	  0  % =
   #9??$ 
 O[ ,-C -D -'9:   !6w~~ 6$ B   #'#!%"&g$g }g #	g
 3-g 3-g g 
gT;H!"5 !P ,0$NN$
$ 	$
 $ $   $ ($ 
$Nr   