99import json
1010import logging
1111import os
12+ import threading
1213from datetime import datetime , timezone
1314from pathlib import Path
1415from typing import Any , Callable , Optional
@@ -51,6 +52,9 @@ def __init__(
5152 self ._include_output = include_output
5253 self ._max_output_chars = max_output_chars
5354 self ._ensure_dir ()
55+ self ._lock = threading .Lock ()
56+ # Single long-lived handle; reopened lazily if it gets rotated out.
57+ self ._fh = None
5458
5559 def _ensure_dir (self ) -> None :
5660 """Create parent directory if it doesn't exist."""
@@ -59,12 +63,29 @@ def _ensure_dir(self) -> None:
5963
6064 def _write (self , entry : dict ) -> None :
6165 """Append a JSON line to the audit log."""
66+ line = json .dumps (entry , default = str ) + "\n "
6267 try :
63- with open (self ._log_path , "a" , encoding = "utf-8" ) as f :
64- f .write (json .dumps (entry , default = str ) + "\n " )
68+ with self ._lock :
69+ # Lazy initialize file handle
70+ if self ._fh is None :
71+ self ._fh = open (self ._log_path , "a" , encoding = "utf-8" )
72+ self ._fh .write (line )
73+ self ._fh .flush ()
74+ os .fsync (self ._fh .fileno ()) # optional, for crash-durability
6575 except OSError as e :
6676 logger .error ("[praisonai.security.audit] Failed to write audit log: %s" , e )
6777
78+ def close (self ) -> None :
79+ """Close the audit log file handle."""
80+ with self ._lock :
81+ if self ._fh is not None :
82+ try :
83+ self ._fh .close ()
84+ except OSError :
85+ pass
86+ finally :
87+ self ._fh = None
88+
6889 def create_after_tool_hook (self ) -> Callable :
6990 """
7091 Create an AFTER_TOOL hook function.
0 commit comments