fix(audit): threading.Lock on AuditLog.log + close (P1 bug)
detection thread and async heartbeat call log() concurrently. Without a lock, two threads can both see today != _current_date and double-open the file, corrupting the handle. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import threading
|
||||
from datetime import datetime, date
|
||||
from pathlib import Path
|
||||
from typing import Callable, IO
|
||||
@@ -16,28 +17,28 @@ class AuditLog:
|
||||
self._clock: Callable[[], datetime] = clock or datetime.now
|
||||
self._current_date: date | None = None
|
||||
self._fh: IO[str] | None = None
|
||||
self._lock = threading.Lock()
|
||||
|
||||
def log(self, event: dict) -> None:
|
||||
now = self._clock()
|
||||
today = now.date()
|
||||
|
||||
if today != self._current_date:
|
||||
self._open(today)
|
||||
|
||||
if "ts" not in event:
|
||||
event = {**event, "ts": now.isoformat()}
|
||||
|
||||
assert self._fh is not None
|
||||
self._fh.write(json.dumps(event, separators=(",", ":")) + "\n")
|
||||
with self._lock:
|
||||
if today != self._current_date:
|
||||
self._open(today)
|
||||
assert self._fh is not None
|
||||
self._fh.write(json.dumps(event, separators=(",", ":")) + "\n")
|
||||
|
||||
def close(self) -> None:
|
||||
if self._fh is not None:
|
||||
try:
|
||||
self._fh.close()
|
||||
except Exception:
|
||||
pass
|
||||
finally:
|
||||
self._fh = None
|
||||
with self._lock:
|
||||
if self._fh is not None:
|
||||
try:
|
||||
self._fh.close()
|
||||
except Exception:
|
||||
pass
|
||||
finally:
|
||||
self._fh = None
|
||||
|
||||
@property
|
||||
def current_path(self) -> Path:
|
||||
|
||||
Reference in New Issue
Block a user