vm-manager: Added log console printing on vm start. Added python logging module
All checks were successful
checks-impure / test (pull_request) Successful in 1m34s
checks / test (pull_request) Successful in 2m41s

This commit is contained in:
Luis Hebendanz 2024-02-02 12:04:30 +07:00
parent 14917b7d56
commit 16562946fe
10 changed files with 64 additions and 40 deletions

View File

@ -117,10 +117,10 @@ def main() -> None:
parser.print_help() parser.print_help()
if args.debug: if args.debug:
setup_logging(logging.DEBUG) setup_logging(logging.DEBUG, root_log_name=__name__.split(".")[0])
log.debug("Debug log activated") log.debug("Debug log activated")
else: else:
setup_logging(logging.INFO) setup_logging(logging.INFO, root_log_name=__name__.split(".")[0])
if not hasattr(args, "func"): if not hasattr(args, "func"):
return return

View File

@ -66,9 +66,9 @@ def get_caller() -> str:
return ret return ret
def setup_logging(level: Any) -> None: def setup_logging(level: Any, root_log_name: str = __name__.split(".")[0]) -> None:
# Get the root logger and set its level # Get the root logger and set its level
main_logger = logging.getLogger("clan_cli") main_logger = logging.getLogger(root_log_name)
main_logger.setLevel(level) main_logger.setLevel(level)
# Create and add the default handler # Create and add the default handler

View File

@ -102,7 +102,6 @@ class Machine:
system = config["system"] system = config["system"]
attr = f'clanInternals.machines."{system}".{self.name}.{attr}' attr = f'clanInternals.machines."{system}".{self.name}.{attr}'
print(f"attr: {attr}")
if attr in self.eval_cache and not refresh: if attr in self.eval_cache and not refresh:
return self.eval_cache[attr] return self.eval_cache[attr]
@ -115,9 +114,8 @@ class Machine:
else: else:
flake = self.flake flake = self.flake
print(f"evaluating {flake}#{attr}")
cmd = nix_eval([f"{flake}#{attr}"]) cmd = nix_eval([f"{flake}#{attr}"])
print(f"cmd: {cmd}")
output = run(cmd).stdout.strip() output = run(cmd).stdout.strip()
self.eval_cache[attr] = output self.eval_cache[attr] = output
return output return output

View File

@ -1,15 +1,21 @@
import argparse import argparse
import logging
from clan_cli.clan_uri import ClanURI from clan_cli.clan_uri import ClanURI
from clan_cli.custom_logger import setup_logging
from clan_vm_manager.models.interfaces import ClanConfig from clan_vm_manager.models.interfaces import ClanConfig
from .app import MainApplication from .app import MainApplication
log = logging.getLogger(__name__)
def main() -> None: def main() -> None:
parser = argparse.ArgumentParser(description="clan-vm-manager") parser = argparse.ArgumentParser(description="clan-vm-manager")
parser.add_argument("--debug", action="store_true", help="enable debug mode")
# Add join subcommand # Add join subcommand
subparser = parser.add_subparsers( subparser = parser.add_subparsers(
title="command", title="command",
@ -23,6 +29,15 @@ def main() -> None:
# Executed when no command is given # Executed when no command is given
parser.set_defaults(func=show_overview) parser.set_defaults(func=show_overview)
args = parser.parse_args() args = parser.parse_args()
if args.debug:
setup_logging("DEBUG", root_log_name=__name__.split(".")[0])
else:
setup_logging("INFO", root_log_name=__name__.split(".")[0])
log.debug("Debug logging enabled")
log.info("Info logging enabled")
args.func(args) args.func(args)

View File

@ -1,5 +1,6 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
import logging
from typing import Literal from typing import Literal
import gi import gi
@ -11,10 +12,12 @@ from gi.repository import Adw
Severity = Literal["Error"] | Literal["Warning"] | Literal["Info"] | str Severity = Literal["Error"] | Literal["Warning"] | Literal["Info"] | str
log = logging.getLogger(__name__)
def show_error_dialog(error: ClanError, severity: Severity | None = "Error") -> None: def show_error_dialog(error: ClanError, severity: Severity | None = "Error") -> None:
message = str(error) message = str(error)
dialog = Adw.MessageDialog(parent=None, heading=severity, body=message) dialog = Adw.MessageDialog(parent=None, heading=severity, body=message)
print("error:", message) log.error(message)
dialog.add_response("ok", "ok") dialog.add_response("ok", "ok")
dialog.choose() dialog.choose()

View File

@ -1,3 +1,4 @@
import logging
import os import os
import signal import signal
import sys import sys
@ -14,6 +15,8 @@ import dataclasses
import multiprocessing as mp import multiprocessing as mp
from collections.abc import Callable from collections.abc import Callable
log = logging.getLogger(__name__)
# Kill the new process and all its children by sending a SIGTERM signal to the process group # Kill the new process and all its children by sending a SIGTERM signal to the process group
def _kill_group(proc: mp.Process) -> None: def _kill_group(proc: mp.Process) -> None:
@ -21,7 +24,7 @@ def _kill_group(proc: mp.Process) -> None:
if proc.is_alive() and pid: if proc.is_alive() and pid:
os.killpg(pid, signal.SIGTERM) os.killpg(pid, signal.SIGTERM)
else: else:
print(f"Process {proc.name} with pid {pid} is already dead", file=sys.stderr) log.warning(f"Process {proc.name} with pid {pid} is already dead")
@dataclasses.dataclass(frozen=True) @dataclasses.dataclass(frozen=True)
@ -127,7 +130,7 @@ def spawn(
# Print some information # Print some information
cmd = f"tail -f {out_file}" cmd = f"tail -f {out_file}"
print(f"Connect to stdout with: {cmd}") log.info(f"Connect to stdout with: {cmd}")
# Return the process # Return the process
mp_proc = MPProcess(name=proc_name, proc=proc, out_file=out_file) mp_proc = MPProcess(name=proc_name, proc=proc, out_file=out_file)

View File

@ -1,3 +1,4 @@
import logging
from collections.abc import Callable from collections.abc import Callable
from typing import Any from typing import Any
@ -12,6 +13,8 @@ gi.require_version("Gtk", "4.0")
gi.require_version("Adw", "1") gi.require_version("Adw", "1")
from gi.repository import Gio, GObject from gi.repository import Gio, GObject
log = logging.getLogger(__name__)
class JoinValue(GObject.Object): class JoinValue(GObject.Object):
# TODO: custom signals for async join # TODO: custom signals for async join
@ -56,7 +59,7 @@ class Join:
# TODO: remove the item that was accepted join from this list # TODO: remove the item that was accepted join from this list
# and call the success function. (The caller is responsible for handling the success) # and call the success function. (The caller is responsible for handling the success)
try: try:
print(f"trying to join: {item.url}") log.info(f"trying to join: {item.url}")
history = add_history(item.url) history = add_history(item.url)
cb(history) cb(history)

View File

@ -1,8 +1,8 @@
import os
import tempfile import tempfile
import weakref import weakref
from collections.abc import Generator
from pathlib import Path from pathlib import Path
from typing import Any, ClassVar from typing import IO, Any, ClassVar
import gi import gi
from clan_cli import vms from clan_cli import vms
@ -81,6 +81,8 @@ class VM(GObject.Object):
self.data = data self.data = data
self.process = MPProcess("dummy", mp.Process(), Path("./dummy")) self.process = MPProcess("dummy", mp.Process(), Path("./dummy"))
self._watcher_id: int = 0 self._watcher_id: int = 0
self._logs_id: int = 0
self._log_file: IO[str] | None = None
self.status = status self.status = status
self._last_liveness: bool = False self._last_liveness: bool = False
self.log_dir = tempfile.TemporaryDirectory( self.log_dir = tempfile.TemporaryDirectory(
@ -116,7 +118,6 @@ class VM(GObject.Object):
# Every 50ms check if the VM is still running # Every 50ms check if the VM is still running
self._watcher_id = GLib.timeout_add(50, self._vm_watcher_task) self._watcher_id = GLib.timeout_add(50, self._vm_watcher_task)
if self._watcher_id == 0: if self._watcher_id == 0:
log.error("Failed to add watcher")
raise ClanError("Failed to add watcher") raise ClanError("Failed to add watcher")
def _vm_watcher_task(self) -> bool: def _vm_watcher_task(self) -> bool:
@ -127,24 +128,38 @@ class VM(GObject.Object):
# If the VM was running and now it is not, remove the watcher # If the VM was running and now it is not, remove the watcher
if prev_liveness and not self.is_running(): if prev_liveness and not self.is_running():
print("===>Removing watcher") log.debug("Removing VM watcher")
return GLib.SOURCE_REMOVE return GLib.SOURCE_REMOVE
return GLib.SOURCE_CONTINUE return GLib.SOURCE_CONTINUE
def _start_logs_task(self, obj: Any, vm: Any, _vm: Any) -> None: def _start_logs_task(self, obj: Any, vm: Any) -> None:
print("Starting log task") if self.is_running():
self._logs_id = GLib.timeout_add(50, self._get_logs_task) log.debug(f"Starting logs watcher on file: {self.process.out_file}")
self._logs_id = GLib.timeout_add(50, self._get_logs_task)
else:
log.debug("Not starting logs watcher")
def _get_logs_task(self) -> bool: def _get_logs_task(self) -> bool:
if not self.process.out_file.exists(): if not self.process.out_file.exists():
log.error(f"Log file {self.process.out_file} does not exist") return GLib.SOURCE_CONTINUE
return GLib.SOURCE_REMOVE
if not self._log_file:
try:
self._log_file = open(self.process.out_file)
except Exception as ex:
log.exception(ex)
self._log_file = None
return GLib.SOURCE_REMOVE
if not self.is_running(): if not self.is_running():
log.info("VM is not running") log.debug("Removing logs watcher")
self._log_file = None
return GLib.SOURCE_REMOVE return GLib.SOURCE_REMOVE
print(self.read_whole_log()) line = os.read(self._log_file.fileno(), 4096)
if len(line) != 0:
print(line.decode("utf-8"), end="", flush=True)
return GLib.SOURCE_CONTINUE return GLib.SOURCE_CONTINUE
def is_running(self) -> bool: def is_running(self) -> bool:
@ -156,20 +171,10 @@ class VM(GObject.Object):
def stop(self) -> None: def stop(self) -> None:
log.info("Stopping VM") log.info("Stopping VM")
if not self.is_running(): if not self.is_running():
log.error("VM already stopped")
return return
self.process.kill_group() self.process.kill_group()
def read_line_log(self) -> Generator[str, None, None]:
with open(self.process.out_file) as f:
while True:
line = f.readline()
if not line:
break
yield line
return None
def read_whole_log(self) -> str: def read_whole_log(self) -> str:
if not self.process.out_file.exists(): if not self.process.out_file.exists():
log.error(f"Log file {self.process.out_file} does not exist") log.error(f"Log file {self.process.out_file} does not exist")

View File

@ -1,3 +1,4 @@
import logging
from collections.abc import Callable from collections.abc import Callable
from functools import partial from functools import partial
@ -12,6 +13,8 @@ from gi.repository import Adw, Gdk, Gio, GObject, Gtk
from clan_vm_manager.models.use_vms import VM, VMS, ClanGroup, Clans from clan_vm_manager.models.use_vms import VM, VMS, ClanGroup, Clans
log = logging.getLogger(__name__)
def create_boxed_list( def create_boxed_list(
model: Gio.ListStore, render_row: Callable[[Gtk.ListBox, GObject], Gtk.Widget] model: Gio.ListStore, render_row: Callable[[Gtk.ListBox, GObject], Gtk.Widget]
@ -194,8 +197,6 @@ class ClanList(Gtk.Box):
self.join_boxed_list.add_css_class("no-shadow") self.join_boxed_list.add_css_class("no-shadow")
def on_row_toggle(self, vm: VM, row: Adw.SwitchRow, state: bool) -> None: def on_row_toggle(self, vm: VM, row: Adw.SwitchRow, state: bool) -> None:
print("Toggled", vm.data.flake.flake_attr, "active:", row.get_active())
if row.get_active(): if row.get_active():
row.set_state(False) row.set_state(False)
vm.start() vm.start()
@ -208,9 +209,5 @@ class ClanList(Gtk.Box):
switch.set_active(vm.is_running()) switch.set_active(vm.is_running())
switch.set_state(vm.is_running()) switch.set_state(vm.is_running())
exitc = vm.process.proc.exitcode exitc = vm.process.proc.exitcode
print("VM exited with code:", exitc)
if not vm.is_running() and exitc != 0: if not vm.is_running() and exitc != 0:
print("VM exited with error. Exitcode:", exitc) log.error(f"VM exited with error. Exitcode: {exitc}")
print("==========VM LOGS=========")
print(vm.read_whole_log())
# self.show_error_dialog(vm.read_log())