diff --git a/defaults/python/cli.py b/defaults/python/cli.py index 4d02d3de..a894c52d 100644 --- a/defaults/python/cli.py +++ b/defaults/python/cli.py @@ -432,7 +432,7 @@ async def main(): verbose = False try: # ---- Setup early logging to at least print something out - set_logger_settings(None, print_to_std=True) + set_logger_settings(logger, None, print_to_std=True) # ---- General parser setup [start] parser = ArgumentParserWithRedirect( @@ -446,7 +446,8 @@ async def main(): # -------- Setup logging (as early as possible) parser_args, _ = parser.parse_known_args() verbose = parser_args.verbose - set_logger_settings(parser_args.log_file, + set_logger_settings(logger, + parser_args.log_file, print_to_std=True, log_preamble=f"Executing: {sys.argv[:]}, CWD: {Path.cwd()}", verbose=verbose) diff --git a/defaults/python/lib/constants.py b/defaults/python/lib/constants.py index 9525bf93..d99e2d56 100644 --- a/defaults/python/lib/constants.py +++ b/defaults/python/lib/constants.py @@ -1,5 +1,6 @@ BUDDY_API_VERSION = 8 LOG_FILE = "/tmp/moondeck.log" +FRONTEND_LOG_FILE = "/tmp/moondeck-frontend.log" RUNNER_LOG_FILE = "/tmp/moondeck-runner.log" MOONLIGHT_LOG_FILE = "/tmp/moondeck-runner-moonlight.log" RUNNER_RESULT_FILE = "/tmp/moondeck-runner.result" diff --git a/defaults/python/lib/logger.py b/defaults/python/lib/logger.py index 80fe1a8b..c876e3fa 100644 --- a/defaults/python/lib/logger.py +++ b/defaults/python/lib/logger.py @@ -1,6 +1,6 @@ import logging -def set_logger_settings(filename: str | None, rotate: bool = True, log_preamble: str | None = None, print_to_std: bool = False, verbose: bool = False): +def set_logger_settings(instance: logging.Logger, filename: str | None, rotate: bool = True, log_preamble: str | None = None, print_to_std: bool = False, verbose: bool = False): # Lazy import to improve CLI performance import sys from pathlib import Path @@ -17,13 +17,12 @@ def set_logger_settings(filename: str | None, rotate: bool = True, log_preamble: ) if log_preamble is not None: - optional_separator = "\n\n" if Path(filename).stat().st_size > 0 else "" + optional_separator = "\n\n" if Path(filename).stat().st_size > 0 and log_preamble != "" else "" handler.setFormatter(logging.Formatter("%(message)s")) handler.emit(logging.makeLogRecord({"msg": f"{optional_separator}{log_preamble}"})) - handler.setFormatter(logging.Formatter( - "[%(asctime)s] %(levelname)s: %(message)s")) + handler.setFormatter(logging.Formatter("[%(asctime)s] %(levelname)s: %(message)s")) handlers.append(handler) if print_to_std: @@ -42,16 +41,20 @@ def set_logger_settings(filename: str | None, rotate: bool = True, log_preamble: handlers.append(stdout_handler) handlers.append(stderr_handler) - logging.basicConfig(handlers=handlers, - level=logging.INFO, - force=True) + for h in instance.handlers[:]: + instance.removeHandler(h) + h.close() - if verbose: - enable_debug_level() + for h in handlers: + instance.addHandler(h) + instance.setLevel(logging.DEBUG if verbose else logging.INFO) -def enable_debug_level(): - logging.getLogger().setLevel(logging.DEBUG) +def get_logger(name: str | None = None): + logger = logging.getLogger(name) + logger.propagate = False + return logger -logger = logging.getLogger() + +logger = get_logger() diff --git a/defaults/python/runner.py b/defaults/python/runner.py index 4c92ca83..e5aff315 100644 --- a/defaults/python/runner.py +++ b/defaults/python/runner.py @@ -13,11 +13,12 @@ def add_plugin_to_path(): # <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< # autopep8: on +import logging from lib.buddyrequests import BuddyException from lib.runner.moondeckapprunner import MoonDeckAppRunner from lib.runner.moonlightonlyrunner import MoonlightOnlyRunner -from lib.logger import logger, set_logger_settings, enable_debug_level +from lib.logger import logger, set_logger_settings from lib.utils import is_moondeck_runner_ready from lib.runner.settingsparser import parse_settings, RunnerType @@ -25,7 +26,7 @@ def add_plugin_to_path(): import lib.runnerresult as runnerresult -set_logger_settings(constants.RUNNER_LOG_FILE, rotate=False) +set_logger_settings(logger, constants.RUNNER_LOG_FILE, rotate=False) async def main(): try: @@ -39,7 +40,7 @@ async def main(): settings = await parse_settings() if settings["debug_logs"]: - enable_debug_level() + logger.setLevel(logging.DEBUG) if settings["runner_type"] == RunnerType.MoonDeck: await MoonDeckAppRunner.run(settings) diff --git a/main.py b/main.py index f74c43c5..43677bd4 100644 --- a/main.py +++ b/main.py @@ -15,6 +15,7 @@ def add_plugin_to_path(): import asyncio +import logging import pathlib import lib.gamestreaminfo as gamestreaminfo import lib.constants as constants @@ -22,15 +23,18 @@ def add_plugin_to_path(): from typing import Optional from lib.plugin.settings import UserSettings, UserSettingsManager -from lib.logger import logger, set_logger_settings +from lib.logger import logger, set_logger_settings, get_logger from lib.buddyrequests import SteamUiMode, SteamUiModeResponse, CurrentUserResponse, BuddyException from lib.buddyclient import BuddyClient, AbortHostStateChangeResult from lib.utils import wake_on_lan, change_moondeck_runner_ready_state, TimedPooler from lib.runnerresult import Result, set_result, get_result from lib.moonlightproxy import MoonlightProxy +set_logger_settings(logger, constants.LOG_FILE, rotate=True, verbose=True) + +frontend_logger = get_logger("frontend") +set_logger_settings(frontend_logger, constants.FRONTEND_LOG_FILE, rotate=True, log_preamble="", verbose=True) -set_logger_settings(constants.LOG_FILE, rotate=True) settings_manager = UserSettingsManager(constants.get_config_file_path()) @@ -47,6 +51,12 @@ async def _main(self): async def _unload(self): self.__cleanup_states() + async def frontend_log_entry(self, level: str, message: str): + try: + frontend_logger.log(logging._checkLevel(level), message) + except Exception: + logger.exception("Failed to save frontend log") + @utils.async_scope_log(logger.info) async def set_runner_ready(self): change_moondeck_runner_ready_state(True) @@ -57,11 +67,20 @@ async def get_runner_result(self): # Cleanup is needed to differentiate between moondeck launches set_result(None) return result + + def set_log_levels(self, data: UserSettings): + level = logging.DEBUG if data["runnerDebugLogs"] else logging.INFO + for instance in [logger, frontend_logger]: + if instance.level != level: + instance.setLevel(level) + instance.info(f"Set logger log level to {logging.getLevelName(level)}") @utils.async_scope_log(logger.info) async def get_user_settings(self): try: - return await settings_manager.read_or_update() + data = await settings_manager.read_or_update() + self.set_log_levels(data) + return data except Exception: logger.exception("Failed to get user settings") return None @@ -70,6 +89,7 @@ async def get_user_settings(self): async def set_user_settings(self, data: UserSettings): try: await settings_manager.write(data) + self.set_log_levels(data) except Exception: logger.exception("Failed to set user settings") diff --git a/package.json b/package.json index 1d1bb578..8fb93f16 100644 --- a/package.json +++ b/package.json @@ -57,6 +57,7 @@ "async-mutex": "^0.5.0", "ip-regex": "^5.0.0", "lodash": "^4.17.21", + "loupe": "^3.2.1", "mobx": "^6.15.0", "promise-throttle-all": "^1.1.1", "react-icons": "^5.5.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index ca6ff042..ef096ceb 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -20,6 +20,9 @@ importers: lodash: specifier: ^4.17.21 version: 4.17.21 + loupe: + specifier: ^3.2.1 + version: 3.2.1 mobx: specifier: ^6.15.0 version: 6.15.0 @@ -841,11 +844,12 @@ packages: glob@10.4.5: resolution: {integrity: sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==} + deprecated: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me hasBin: true glob@7.2.3: resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==} - deprecated: Glob versions prior to v9 are no longer supported + deprecated: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me globals@14.0.0: resolution: {integrity: sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==} @@ -997,6 +1001,9 @@ packages: lodash@4.17.21: resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==} + loupe@3.2.1: + resolution: {integrity: sha512-CdzqowRJCeLU72bHvWqwRBBlLcMEtIvGrlvef74kMnV2AolS9Y8xUv1I0U/MNAWMhBlKIoyuEgoJ0t/bbwHbLQ==} + lru-cache@10.4.3: resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==} @@ -2308,6 +2315,8 @@ snapshots: lodash@4.17.21: {} + loupe@3.2.1: {} + lru-cache@10.4.3: {} magic-string@0.30.10: diff --git a/src/lib/executeasync.ts b/src/lib/executeasync.ts index 8391fc75..4cb1b326 100644 --- a/src/lib/executeasync.ts +++ b/src/lib/executeasync.ts @@ -1,17 +1,22 @@ import { logger } from "./logger"; -type AsyncCallback = () => Promise; +type VoidPromise = Promise; +type AsyncCallback = () => VoidPromise; -export function executeAsyncWrapper(callback: AsyncCallback) { +export function executeAsyncWrapper(input: AsyncCallback | VoidPromise) { return () => { Promise.resolve() .then(async () => { - await callback(); + if (typeof input === "function") { + await input(); + } else { + await input; + } }) .catch((e) => logger.critical(e)); }; } -export function executeAsync(callback: AsyncCallback) { - executeAsyncWrapper(callback)(); +export function executeAsync(input: AsyncCallback | VoidPromise) { + executeAsyncWrapper(input)(); } diff --git a/src/lib/logger.ts b/src/lib/logger.ts index 499b58b9..60867672 100644 --- a/src/lib/logger.ts +++ b/src/lib/logger.ts @@ -1,4 +1,31 @@ -import { toaster } from "@decky/api"; +import { call, toaster } from "@decky/api"; +import { inspect } from "loupe"; + +function serialize(...args: unknown[]) { + return args.map((arg) => typeof arg === "string" ? arg : inspect(arg)).join(" "); +} + +function criticalConsoleLog(...args: unknown[]) { + console.error( + "%c MoonDeck %c Critical:", + "background: #16a085; color: black;", + "background: transparent;", + ...args + ); +} + +function executeAsyncSafe(input: Promise) { + Promise.resolve(input).catch((e) => criticalConsoleLog(e)); +} + +type PythonLogLevel = "DEBUG" | "INFO" | "WARNING" | "ERROR" | "CRITICAL"; +async function frontendLogEntry(level: PythonLogLevel, ...args: unknown[]): Promise { + try { + await call<[PythonLogLevel, string], unknown>("frontend_log_entry", level, serialize(...args)); + } catch (message) { + criticalConsoleLog("Error while trying to save frontend log: ", message); + } +} class Logger { toast(entry: string, options: { duration?: number; output?: "debug" | "log" | "warn" | "error" | null } = {}): void { @@ -32,6 +59,7 @@ class Logger { "background: transparent;", ...args ); + executeAsyncSafe(frontendLogEntry("DEBUG", ...args)); } log(...args: unknown[]): void { @@ -42,6 +70,7 @@ class Logger { "background: transparent;", ...args ); + executeAsyncSafe(frontendLogEntry("INFO", ...args)); } warn(...args: unknown[]): void { @@ -52,6 +81,7 @@ class Logger { "background: transparent;", ...args ); + executeAsyncSafe(frontendLogEntry("WARNING", ...args)); } error(...args: unknown[]): void { @@ -62,15 +92,12 @@ class Logger { "background: transparent;", ...args ); + executeAsyncSafe(frontendLogEntry("ERROR", ...args)); } critical(...args: unknown[]): void { - console.error( - "%c MoonDeck %c Critical:", - "background: #16a085; color: black;", - "background: transparent;", - ...args - ); + criticalConsoleLog(...args); + executeAsyncSafe(frontendLogEntry("CRITICAL", ...args)); } }