use logging

This commit is contained in:
ars
2026-06-15 03:29:42 +03:00
parent 25ca35c097
commit a321d64169
7 changed files with 91 additions and 11 deletions
+2
View File
@@ -1,6 +1,8 @@
import sys import sys
from nexus_sync.client.runtime import main from nexus_sync.client.runtime import main
from nexus_sync.utils import configure_logging
if __name__ == "__main__": if __name__ == "__main__":
configure_logging()
raise SystemExit(main(sys.argv[1:])) raise SystemExit(main(sys.argv[1:]))
+7 -5
View File
@@ -1,8 +1,8 @@
import json import json
import logging
import os import os
import platform import platform
import socket import socket
import sys
import urllib.error import urllib.error
import urllib.request import urllib.request
from dataclasses import dataclass from dataclasses import dataclass
@@ -27,6 +27,7 @@ CLIENT_ID_ENV = "NEXUS_SYNC_CLIENT_ID"
CLIENT_TOKEN_ENV = "NEXUS_SYNC_CLIENT_TOKEN" CLIENT_TOKEN_ENV = "NEXUS_SYNC_CLIENT_TOKEN"
CLIENT_VERSION = "0.1.0" CLIENT_VERSION = "0.1.0"
HEARTBEAT_PATH = "/api/v1/client/heartbeat" HEARTBEAT_PATH = "/api/v1/client/heartbeat"
logger = logging.getLogger(__name__)
class ClientConfigError(ValueError): class ClientConfigError(ValueError):
@@ -142,19 +143,20 @@ def main(argv: list[str] | None = None) -> int:
config = load_client_config() config = load_client_config()
result = run_once(config) result = run_once(config)
except (ClientConfigError, HeartbeatError, ValueError) as error: except (ClientConfigError, HeartbeatError, ValueError) as error:
print(f"nexus-sync client error: {error}", file=sys.stderr) logger.error("nexus-sync client error: %s", error)
return 1 return 1
if result is None: if result is None:
print("heartbeat accepted; no command") logger.info("heartbeat accepted; no command")
return 0 return 0
print( logger.info(
"command result: %s",
json.dumps( json.dumps(
result.model_dump(mode="json"), result.model_dump(mode="json"),
ensure_ascii=False, ensure_ascii=False,
separators=(",", ":"), separators=(",", ":"),
) ),
) )
return 0 return 0
+2
View File
@@ -1,9 +1,11 @@
import uvicorn import uvicorn
from nexus_sync.server.app import app from nexus_sync.server.app import app
from nexus_sync.utils import configure_logging
def main() -> None: def main() -> None:
configure_logging()
uvicorn.run(app, host="0.0.0.0", port=8000) uvicorn.run(app, host="0.0.0.0", port=8000)
+5
View File
@@ -0,0 +1,5 @@
from nexus_sync.utils.log_config import configure_logging
__all__ = [
"configure_logging",
]
+19
View File
@@ -0,0 +1,19 @@
import logging
import os
LOG_LEVEL_ENV = "NEXUS_SYNC_LOG_LEVEL"
DEFAULT_LOG_LEVEL = "INFO"
DEFAULT_LOG_FORMAT = "%(levelname)s %(name)s %(message)s"
def configure_logging(level: str | None = None) -> None:
raw_level = level or os.environ.get(LOG_LEVEL_ENV, DEFAULT_LOG_LEVEL)
log_level = getattr(logging, raw_level.upper(), None)
if not isinstance(log_level, int):
raise ValueError(f"invalid log level: {raw_level}")
logging.basicConfig(
level=log_level,
format=DEFAULT_LOG_FORMAT,
)
logging.getLogger().setLevel(log_level)
+28 -6
View File
@@ -294,19 +294,19 @@ def test_run_once_sends_previous_command_result() -> None:
assert seen["last_command_result"] == previous_result assert seen["last_command_result"] == previous_result
def test_main_returns_non_zero_for_missing_config(monkeypatch, capsys) -> None: def test_main_returns_non_zero_for_missing_config(monkeypatch, caplog) -> None:
monkeypatch.delenv(SERVER_URL_ENV, raising=False) monkeypatch.delenv(SERVER_URL_ENV, raising=False)
monkeypatch.delenv(CLIENT_ID_ENV, raising=False) monkeypatch.delenv(CLIENT_ID_ENV, raising=False)
monkeypatch.delenv(CLIENT_TOKEN_ENV, raising=False) monkeypatch.delenv(CLIENT_TOKEN_ENV, raising=False)
exit_code = main([]) exit_code = main([])
captured = capsys.readouterr()
assert exit_code == 1 assert exit_code == 1
assert SERVER_URL_ENV in captured.err assert SERVER_URL_ENV in caplog.text
def test_main_prints_success_without_command(monkeypatch, capsys) -> None: def test_main_logs_success_without_command(monkeypatch, caplog) -> None:
caplog.set_level("INFO")
monkeypatch.setenv(SERVER_URL_ENV, "https://nexus.example.test") monkeypatch.setenv(SERVER_URL_ENV, "https://nexus.example.test")
monkeypatch.setenv(CLIENT_ID_ENV, "macbook-pro-01") monkeypatch.setenv(CLIENT_ID_ENV, "macbook-pro-01")
monkeypatch.setenv(CLIENT_TOKEN_ENV, "client-token") monkeypatch.setenv(CLIENT_TOKEN_ENV, "client-token")
@@ -314,9 +314,31 @@ def test_main_prints_success_without_command(monkeypatch, capsys) -> None:
exit_code = main([]) exit_code = main([])
captured = capsys.readouterr()
assert exit_code == 0 assert exit_code == 0
assert captured.out == "heartbeat accepted; no command\n" assert "heartbeat accepted; no command" in caplog.text
def test_main_logs_command_result(monkeypatch, caplog) -> None:
caplog.set_level("INFO")
monkeypatch.setenv(SERVER_URL_ENV, "https://nexus.example.test")
monkeypatch.setenv(CLIENT_ID_ENV, "macbook-pro-01")
monkeypatch.setenv(CLIENT_TOKEN_ENV, "client-token")
monkeypatch.setattr(
"nexus_sync.client.runtime.run_once",
lambda _config: CommandResult(
command_id="cmd_01JY3H8V8W8P3FXDR3S2BM7M6B",
status=CommandResultStatus.SUCCEEDED,
return_code=0,
stdout="host\n",
stderr="",
),
)
exit_code = main([])
assert exit_code == 0
assert "command result:" in caplog.text
assert '"command_id":"cmd_01JY3H8V8W8P3FXDR3S2BM7M6B"' in caplog.text
class _Response: class _Response:
+28
View File
@@ -0,0 +1,28 @@
import logging
import pytest
from nexus_sync.utils.log_config import LOG_LEVEL_ENV, configure_logging
def test_configure_logging_uses_default_info_level(monkeypatch) -> None:
monkeypatch.delenv(LOG_LEVEL_ENV, raising=False)
configure_logging()
assert logging.getLogger().getEffectiveLevel() == logging.INFO
def test_configure_logging_reads_level_from_env(monkeypatch) -> None:
monkeypatch.setenv(LOG_LEVEL_ENV, "debug")
configure_logging()
assert logging.getLogger().getEffectiveLevel() == logging.DEBUG
def test_configure_logging_rejects_invalid_level(monkeypatch) -> None:
monkeypatch.setenv(LOG_LEVEL_ENV, "verbose")
with pytest.raises(ValueError, match="invalid log level"):
configure_logging()