§
    ‰eîi45  ã                   óä  — 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 )zÇSet 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    ú0/home/div/.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Ô3€OÝˆÐ :¸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 )Ú_ComponentFilterz¸Only 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   € ØŒ{×%Ò% d¤nÑ5Ô5Ð5r   )Ú__name__Ú
__module__Ú__qualname__Ú__doc__r   Ústrr6   r,   Ú	LogRecordÚboolr>   Ú__classcell__©r:   s   @r   r1   r1   ~   s~   ø€ € € € € ðð ð) ¨#¤ð )°4ð )ð )ð )ð )ð )ð )ð6˜WÔ.ð 6°4ð 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                 ó²  — t           r|s| pt          ¦   «         }|dz  S | 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         ¦  «        ¬¦  «         |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)Ú_logging_initializedr   ÚmkdirÚ_read_logging_configÚupperr$   r,   r\   Úagent.redactr`   Ú	getLoggerÚ_add_rotating_handlerÚ_LOG_FORMATÚWARNINGr1   ÚCOMPONENT_PREFIXESra   Ú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 ð  Eð ØÐ/�oÑ/Ô/ˆØ�f‰}ÐàÐ+�/Ñ+Ô+€DØ�V‰m€GØ‡M‚M˜$¨€MÑ.Ô.Ð.õ +?Ñ*@Ô*@Ñ'€Iˆ|˜ZàÐ2˜yÐ2¨F×9Ò9Ñ;Ô;€JÝ•G˜Z­¬Ñ6Ô6€EØÐ1 Ð1°°TÑ9¸DÑ@€IØÐ-˜jÐ-¨A€Gð 0Ð/Ð/Ð/Ð/Ð/åÔÑÔ€Dõ ØØ�+ÑØØØØ$Ð$¥[Ñ1Ô1ðñ ô ð õ ØØ�,ÑÝŒoØ!ØØ$Ð$¥[Ñ1Ô1ðñ ô ð ð ˆyÒÐÝØØ�mÑ#Ý”,Ø%ØØ(Ð(­Ñ5Ô5Ý'Õ(:¸9Ô(EÑFÔFð	
ñ 	
ô 	
ð 	
ð „z•W”^Ò#Ð# t¤z°EÒ'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 )
z�Enable 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)rj   r`   r,   rk   ÚhandlersÚ
isinstanceÚStreamHandlerr   r$   rq   ÚDEBUGÚsetFormatterÚ_LOG_FORMAT_VERBOSEr}   Ú
addHandlerra   rr   rn   r\   )r`   rz   ÚhÚhandlerr<   s        r   Úsetup_verbose_loggingrˆ     s`  € ð
 0Ð/Ð/Ð/Ð/Ð/åÔÑÔ€Dð Œ]ð ð ˆÝ�a�Ô.Ñ/Ô/ð 	½
À1ÕFYÑ8ZÔ8Zð 	Ý�qÐ+¨UÑ3Ô3ð Ø��øåÔ#Ñ%Ô%€GØ×Ò•W”]Ñ#Ô#Ð#Ø×ÒÐ+Ð+Õ,?ÈÐTÑTÔTÑUÔUÐUØ"€GÔØ‡O‚O�GÑÔÐð „z•G”MÒ!Ð!Ø�Š•g”mÑ$Ô$Ð$õ ð :ð :ˆÝÔ˜$ÑÔ×(Ò(­¬Ñ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__5  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_managed:  sX   € ØŒ=ð 	ðÝ”˜Ô*¨EÑ2Ô2Ð2Ð2Ð2øÝð ð ð Ø��ðøøøð	ð 	s   ‰% ¥
3²3c                 óp   •— t          ¦   «                              ¦   «         }|                      ¦   «          |S r4   )r5   Ú_openr”   )r9   Ústreamr:   s     €r   r–   z!_ManagedRotatingFileHandler._openA  s+   ø€ Ý‘”—’‘”ˆØ×ÒÑ Ô Ð Øˆr   c                 óp   •— t          ¦   «                              ¦   «          |                      ¦   «          d S r4   )r5   Ú
doRolloverr”   )r9   r:   s    €r   r™   z&_ManagedRotatingFileHandler.doRolloverF  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$   Úparentrg   rŠ   rC   rq   rƒ   Ú	addFilterr…   )
rš   r›   ra   rb   rT   rc   re   ÚresolvedÚexistingr‡   s
             r   rl   rl   K  s  € ð& �|Š|‰~Œ~€HØ”Oð ð ˆå�xÕ!4Ñ5Ô5ð	å•W˜X ~°rÑ:Ô:Ñ;Ô;×CÒCÑEÔEÈÒQÐQàˆFˆFøà„K×Ò˜d¨TÐÑ2Ô2Ð2Ý)ÝˆD‰	Œ	˜I°<Øðñ ô €Gð ×Ò�UÑÔÐØ×Ò˜Ñ#Ô#Ð#ØÐØ×Ò˜*Ñ%Ô%Ð%Ø
×Ò�gÑÔÐÐÐr   c                  óÊ  — 	 ddl } t          ¦   «         }|                     ¦   «         rªt          |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   rh   rh   r  s1  € ð
ØˆˆˆÝ%Ñ'Ô'ˆØ×ÒÑÔð 		Ý�k 3°Ð9Ñ9Ô9ð .¸QØ—n’n QÑ'Ô'Ð-¨2�ð.ð .ð .ñ .ô .ð .ð .ð .ð .ð .ð .øøøð .ð .ð .ð .à—g’g˜i¨Ñ,Ô,ˆGÝ˜'¥4Ñ(Ô(ð à—K’K Ñ(Ô(Ø—K’K Ñ.Ô.Ø—K’K Ñ/Ô/ðð øøõ
 ð ð ð ØˆðøøøàÐ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   rf   Úlocalr   rm   r„   rr   rC   r   r   r/   ÚFilterr1   ro   ÚintrE   r{   rˆ   rŠ   ÚLoggerÚ	Formatterrl   rh   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ð 6�w”~ñ 6ô 6ð 6ð$ ØBØØ Øðð Ð ð #'Ø#Ø!%Ø"&ØØðhð hð hà˜$”ðhð ˜Œ}ðhð ˜#”ð	hð
 ˜3”-ðhð �3Œ-ðhð ðhð 
ðhð hð hð hðV;ð ;ð ;ð ;ðH!ð !ð !ð !ð !Ð"5ñ !ô !ð !ðP ,0ð$ð $ð $ØŒNð$à
ð$ð ð	$ð
 ð$ð ð$ð Ô ð$ð ˜œÔ(ð$ð 
ð$ð $ð $ð $ðNð ð ð ð r   