
    i4                        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j                  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j!        d)e	ej                 ddfd*Z"d+ Z#dS )-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           _        dS )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           _        dS )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dS fd} d| _        t          j        |            dS )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                  b     | i |}t          t          dd           }|rd| dn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1&d;;,/7[#[[[[Rr   T)logginggetLogRecordFactoryr$   r    setLogRecordFactory)r+   r*   s    @r   _install_session_record_factoryr/   Z   si     133O :EBB      8<4 788888r   c                   R     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                 p    t                                                       t          |          | _        d S N)super__init__tuple	_prefixes)selfr2   	__class__s     r   r6   z_ComponentFilter.__init__   s*    xr   r(   c                 @    |j                             | j                  S r4   )name
startswithr8   )r9   r(   s     r   filterz_ComponentFilter.filter   s    {%%dn555r   )__name__
__module____qualname____doc__r   strr6   r,   	LogRecordboolr>   __classcell__r:   s   @r   r1   r1   ~   s~         )# )4 ) ) ) ) ) )6W. 64 6 6 6 6 6 6 6 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                    | pt                      }|dz  }|                    dd           t                      \  }}	}
|p|pd                                }t	          t
          |t
          j                  }|p|	pddz  dz  }|p|
pd}dd	lm} t          j	                    }t          ||d
z  ||| |t                               t          ||dz  t
          j        dd |t                               |dk    rIt          ||dz  t
          j        dd |t                    t          t          d                              t          r|s|S |j        t
          j        k    s|j        |k    r|                    |           t&          D ]3}t          j	        |                              t
          j                   4d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66E11T9D@I-j-AG 0/////D +$$[11    ,o!$$[11    ym#,%((55'(:9(EFF	
 	
 	
 	
  E  zW^##tzE'9'9e  : :$((9999Nr   c                     ddl m}  t          j                    }|j        D ]E}t          |t          j                  r)t          |t                    st          |dd          r dS Ft          j                    }|	                    t          j
                   |                     | t          d                     d|_        |                    |           |j        t          j
        k    r|	                    t          j
                   t           D ]3}t          j        |          	                    t          j                   4t          j        d	          	                    t          j                   dS )
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.// 	
1FY8Z8Z 	q+U33 #%%GW]###++,?TTTUUU"GOOG zGM!!gm$$$  : :$((999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                 d    ddl m}  |            | _         t                      j        |i | d S )Nr   )
is_managed)hermes_cli.configr   _managedr5   r6   )r9   r&   r'   r   r:   s       r   r6   z$_ManagedRotatingFileHandler.__init__4  sD    000000"
$)&)))))r   c                 p    | j         r.	 t          j        | j        d           d S # t          $ r Y d S w xY wd S )Ni  )r   oschmodbaseFilenameOSError)r9   s    r   _chmod_if_managedz-_ManagedRotatingFileHandler._chmod_if_managed9  sX    = 	*E22222   	 	s   % 
33c                 p    t                                                      }|                                  |S r4   )r5   _openr   )r9   streamr:   s     r   r   z!_ManagedRotatingFileHandler._open@  s+       r   c                 p    t                                                       |                                  d S r4   )r5   
doRolloverr   )r9   r:   s    r   r   z&_ManagedRotatingFileHandler.doRolloverE  s1         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        D ]N}t          |t                    r7t	          t          |dd                                                     |k    r dS O|j                            dd           t          t          |          ||d          }	|	
                    |           |	                    |           ||	                    |           |                     |	           dS )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O  x!455	WX~r::;;CCEEQQFFKdT222)D		I<  G U###*%%%
gr   c                     	 ddl } t                      }|                                rt          |dd          5 }|                     |          pi }ddd           n# 1 swxY w Y   |                    di           }t          |t                    r>|                    d          |                    d          |                    d	          fS n# t          $ r Y nw xY wd
S )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  s1   
%'' 		k3999 .QnnQ''-2. . . . . . . . . . . . . . .ggi,,G'4(( KK((KK..KK// 
    s5   8C AC A""C %A"&A+C 
C C )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 0 0 0 0 0       % % % % % % % % = = = = = = = =
   #9?$$ 
 O[ ,-C -D - - - -' ' ' '9 9 9 9:    ! ! !6 6 6 6 6w~ 6 6 6$ B    #'#!%"&g g g$g }g #	g
 3-g 3-g g 
g g g gT; ; ; ;H! ! ! ! !"5 ! ! !P ,0$ $ $N$
$ 	$
 $ $  $ ($ 
$ $ $ $N    r   