Preserve live gateway log during compaction
This commit is contained in:
@@ -574,6 +574,7 @@ GATEWAY_LISTENER_ACTIVE_LANES = set()
|
|||||||
GATEWAY_LISTENER_UNSTABLE_UNTIL = 0.0
|
GATEWAY_LISTENER_UNSTABLE_UNTIL = 0.0
|
||||||
GATEWAY_LISTENER_STABILITY_LOCK = threading.RLock()
|
GATEWAY_LISTENER_STABILITY_LOCK = threading.RLock()
|
||||||
AUTH_RENEW_LOCK = threading.Lock()
|
AUTH_RENEW_LOCK = threading.Lock()
|
||||||
|
GATEWAY_LOG_LOCK = threading.Lock()
|
||||||
GATEWAY_VAULT_SETUP_THREAD_STARTED = False
|
GATEWAY_VAULT_SETUP_THREAD_STARTED = False
|
||||||
GATEWAY_PERIODIC_LOG_STATE = {}
|
GATEWAY_PERIODIC_LOG_STATE = {}
|
||||||
WS_CLOSE_DETAILS = {}
|
WS_CLOSE_DETAILS = {}
|
||||||
@@ -595,7 +596,8 @@ def gateway_log(event, level="info", **fields):
|
|||||||
payload[key] = value
|
payload[key] = value
|
||||||
else:
|
else:
|
||||||
payload[key] = str(value)
|
payload[key] = str(value)
|
||||||
print(json.dumps(payload, separators=(",", ":"), sort_keys=True), file=sys.stderr, flush=True)
|
with GATEWAY_LOG_LOCK:
|
||||||
|
print(json.dumps(payload, separators=(",", ":"), sort_keys=True), file=sys.stderr, flush=True)
|
||||||
|
|
||||||
|
|
||||||
def gateway_periodic_log(key, interval_seconds, event, level="info", **fields):
|
def gateway_periodic_log(key, interval_seconds, event, level="info", **fields):
|
||||||
@@ -629,15 +631,17 @@ def compact_log_file(path, max_bytes):
|
|||||||
newline_index = chunk.find(b"\n")
|
newline_index = chunk.find(b"\n")
|
||||||
if newline_index >= 0 and newline_index + 1 < len(chunk):
|
if newline_index >= 0 and newline_index + 1 < len(chunk):
|
||||||
chunk = chunk[newline_index + 1 :]
|
chunk = chunk[newline_index + 1 :]
|
||||||
tmp_path = file_path.with_suffix(file_path.suffix + ".tmp")
|
|
||||||
try:
|
try:
|
||||||
tmp_path.write_bytes(chunk)
|
# Keep the inode stable: the daemon's stdout/stderr already point to
|
||||||
tmp_path.replace(file_path)
|
# this file. Replacing the pathname would leave the live process
|
||||||
|
# writing to a deleted inode and make `nap gateway logs` stale.
|
||||||
|
with GATEWAY_LOG_LOCK, file_path.open("r+b") as handle:
|
||||||
|
handle.seek(0)
|
||||||
|
handle.write(chunk)
|
||||||
|
handle.truncate()
|
||||||
|
handle.flush()
|
||||||
except OSError:
|
except OSError:
|
||||||
try:
|
return
|
||||||
tmp_path.unlink(missing_ok=True)
|
|
||||||
except OSError:
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
def start_gateway_log_compactor_thread():
|
def start_gateway_log_compactor_thread():
|
||||||
|
|||||||
@@ -0,0 +1,27 @@
|
|||||||
|
import importlib.util
|
||||||
|
import pathlib
|
||||||
|
|
||||||
|
|
||||||
|
def load_module():
|
||||||
|
script_path = pathlib.Path(__file__).resolve().parents[1] / "resources" / "scripts" / "napseer_mcp_server.py"
|
||||||
|
spec = importlib.util.spec_from_file_location("napseer_gateway_log_compaction_test", script_path)
|
||||||
|
module = importlib.util.module_from_spec(spec)
|
||||||
|
assert spec and spec.loader
|
||||||
|
spec.loader.exec_module(module)
|
||||||
|
return module
|
||||||
|
|
||||||
|
|
||||||
|
def test_log_compaction_preserves_live_writer_inode(tmp_path):
|
||||||
|
mod = load_module()
|
||||||
|
log_path = tmp_path / "gateway.log"
|
||||||
|
log_path.write_bytes((b"old-line\n" * 200) + b"recent-line\n")
|
||||||
|
inode_before = log_path.stat().st_ino
|
||||||
|
|
||||||
|
with log_path.open("ab", buffering=0) as live_writer:
|
||||||
|
mod.compact_log_file(log_path, 128)
|
||||||
|
live_writer.write(b"after-compaction\n")
|
||||||
|
|
||||||
|
assert log_path.stat().st_ino == inode_before
|
||||||
|
content = log_path.read_bytes()
|
||||||
|
assert content.endswith(b"after-compaction\n")
|
||||||
|
assert b"recent-line\n" in content
|
||||||
Reference in New Issue
Block a user