From 4dcba38e3f4df5d82fa6aee20bec93a1ad2d9202 Mon Sep 17 00:00:00 2001 From: ynhhoJ <22500212+ynhhoJ@users.noreply.github.com> Date: Sun, 28 Dec 2025 15:09:09 +0200 Subject: [PATCH 1/2] feat(backend/decky_loader): Print logs with plugin version if available --- backend/decky_loader/browser.py | 7 +++-- backend/decky_loader/loader.py | 12 ++++---- backend/decky_loader/plugin/plugin.py | 19 ++++++++---- .../decky_loader/plugin/sandboxed_plugin.py | 29 ++++++++++++------- 4 files changed, 41 insertions(+), 26 deletions(-) diff --git a/backend/decky_loader/browser.py b/backend/decky_loader/browser.py index fe8ae71a4..09788a401 100644 --- a/backend/decky_loader/browser.py +++ b/backend/decky_loader/browser.py @@ -152,11 +152,12 @@ async def uninstall_plugin(self, name: str): # logger.debug("current plugins: %s", snapshot_string) if name in self.plugins: - logger.debug("Plugin %s was found", name) + plugin_display = self.plugins[name].get_display_name() + logger.debug(f"Plugin {plugin_display} was found") await self.plugins[name].stop(uninstall=True) - logger.debug("Plugin %s was stopped", name) + logger.debug(f"Plugin {plugin_display} was stopped") del self.plugins[name] - logger.debug("Plugin %s was removed from the dictionary", name) + logger.debug(f"Plugin {plugin_display} was removed from the dictionary") self.cleanup_plugin_settings(name) logger.debug("removing files %s" % str(name)) rmtree(plugin_dir) diff --git a/backend/decky_loader/loader.py b/backend/decky_loader/loader.py index 4574cd1d2..297fc202e 100644 --- a/backend/decky_loader/loader.py +++ b/backend/decky_loader/loader.py @@ -171,16 +171,16 @@ async def plugin_emitted_event(event: str, args: Any): return if plugin.name in self.plugins: if not "debug" in plugin.flags and refresh: - self.logger.info(f"Plugin {plugin.name} is already loaded and has requested to not be re-loaded") + self.logger.info(f"Plugin {plugin.get_display_name()} is already loaded and has requested to not be re-loaded") return else: await self.plugins[plugin.name].stop() self.plugins.pop(plugin.name, None) if plugin.passive: - self.logger.info(f"Plugin {plugin.name} is passive") + self.logger.info(f"Plugin {plugin.get_display_name()} is passive") self.plugins[plugin.name] = plugin.start() - self.logger.info(f"Loaded {plugin.name}") + self.logger.info(f"Loaded {plugin.get_display_name()}") if not batch: self.loop.create_task(self.dispatch_plugin(plugin.name, plugin.version, plugin.load_type)) except Exception as e: @@ -208,7 +208,7 @@ async def handle_plugin_method_call_legacy(self, plugin_name: str, method_name: plugin = self.plugins[plugin_name] try: if method_name.startswith("_"): - raise RuntimeError(f"Plugin {plugin.name} tried to call private method {method_name}") + raise RuntimeError(f"Plugin {plugin.get_display_name()} tried to call private method {method_name}") res["result"] = await plugin.execute_legacy_method(method_name, kwargs) res["success"] = True except Exception as e: @@ -220,10 +220,10 @@ async def handle_plugin_method_call(self, plugin_name: str, method_name: str, *a plugin = self.plugins[plugin_name] try: if method_name.startswith("_"): - raise RuntimeError(f"Plugin {plugin.name} tried to call private method {method_name}") + raise RuntimeError(f"Plugin {plugin.get_display_name()} tried to call private method {method_name}") result = await plugin.execute_method(method_name, *args) except Exception as e: - self.logger.error(f"Method {method_name} of plugin {plugin.name} failed with the following exception:\n{format_exc()}") + self.logger.error(f"Method {method_name} of plugin {plugin.get_display_name()} failed with the following exception:\n{format_exc()}") raise e # throw again to pass the error to the frontend return result diff --git a/backend/decky_loader/plugin/plugin.py b/backend/decky_loader/plugin/plugin.py index a7edaa459..7bad33f4d 100644 --- a/backend/decky_loader/plugin/plugin.py +++ b/backend/decky_loader/plugin/plugin.py @@ -81,6 +81,13 @@ def __init__(self, file: str, plugin_directory: str, plugin_path: str, emit_call def __str__(self) -> str: return self.name + def get_display_name(self) -> str: + """Returns plugin name with version if available, formatted for logging.""" + if self.version: + return f"{self.name} (v{self.version})" + + return self.name + async def _response_listener(self): while self._socket.active: try: @@ -92,7 +99,7 @@ async def _response_listener(self): elif res["type"] == SocketMessageType.RESPONSE.value: self._method_call_requests.pop(res["id"]).set_result(res) except CancelledError: - self.log.info(f"Stopping response listener for {self.name}") + self.log.info(f"Stopping response listener for {self.get_display_name()}") await self._socket.close_socket_connection() raise except: @@ -101,7 +108,7 @@ async def _response_listener(self): async def execute_legacy_method(self, method_name: str, kwargs: Dict[Any, Any]): if not self.legacy_method_warning: self.legacy_method_warning = True - self.log.warning(f"Plugin {self.name} is using legacy method calls. This will be removed in a future release.") + self.log.warning(f"Plugin {self.get_display_name()} is using legacy method calls. This will be removed in a future release.") if self.passive: raise RuntimeError("This plugin is passive (aka does not implement main.py)") @@ -136,7 +143,7 @@ async def stop(self, uninstall: bool = False): start_time = time() if self.passive: return - self.log.info(f"Shutting down {self.name}") + self.log.info(f"Shutting down {self.get_display_name()}") pending: set[Task[None]] | None = None; @@ -156,16 +163,16 @@ async def stop(self, uninstall: bool = False): for pending_task in pending: pending_task.cancel() - self.log.info(f"Plugin {self.name} has been stopped in {time() - start_time:.1f}s") + self.log.info(f"Plugin {self.get_display_name()} has been stopped in {time() - start_time:.1f}s") except Exception as e: - self.log.error(f"Error during shutdown for plugin {self.name}: {str(e)}\n{format_exc()}") + self.log.error(f"Error during shutdown for plugin {self.get_display_name()}: {str(e)}\n{format_exc()}") async def kill_if_still_running(self): start_time = time() while self.proc and self.proc.is_alive(): elapsed_time = time() - start_time if elapsed_time >= 5: - self.log.warning(f"Plugin {self.name} still alive 5 seconds after stop request! Sending SIGKILL!") + self.log.warning(f"Plugin {self.get_display_name()} still alive 5 seconds after stop request! Sending SIGKILL!") self.terminate(True) await sleep(0.1) diff --git a/backend/decky_loader/plugin/sandboxed_plugin.py b/backend/decky_loader/plugin/sandboxed_plugin.py index 20da747e4..675eb4643 100644 --- a/backend/decky_loader/plugin/sandboxed_plugin.py +++ b/backend/decky_loader/plugin/sandboxed_plugin.py @@ -45,6 +45,13 @@ def __init__(self, self.log = getLogger("sandboxed_plugin") + def get_display_name(self) -> str: + """Returns plugin name with version if available, formatted for logging.""" + if self.version: + return f"{self.name} (v{self.version})" + + return self.name + def initialize(self, socket: LocalSocket): self._socket = socket @@ -125,51 +132,51 @@ async def emit(event: str, *args: Any) -> None: get_event_loop().create_task(self.Plugin._main(self.Plugin)) get_event_loop().create_task(socket.setup_server(self.on_new_message)) except: - self.log.error("Failed to start " + self.name + "!\n" + format_exc()) + self.log.error(f"Failed to start {self.get_display_name()}!\n{format_exc()}") sys.exit(0) try: get_event_loop().run_forever() except SystemExit: pass except: - self.log.error("Loop exited for " + self.name + "!\n" + format_exc()) + self.log.error(f"Loop exited for {self.get_display_name()}!\n{format_exc()}") finally: get_event_loop().close() async def _unload(self): try: - self.log.info("Attempting to unload with plugin " + self.name + "'s \"_unload\" function.\n") + self.log.info(f"Attempting to unload with plugin {self.get_display_name()}'s \"_unload\" function.\n") if hasattr(self.Plugin, "_unload"): if self.api_version > 0: await self.Plugin._unload() else: await self.Plugin._unload(self.Plugin) - self.log.info("Unloaded " + self.name + "\n") + self.log.info(f"Unloaded {self.get_display_name()}\n") else: - self.log.info("Could not find \"_unload\" in " + self.name + "'s main.py" + "\n") + self.log.info(f"Could not find \"_unload\" in {self.get_display_name()}'s main.py\n") except: - self.log.error("Failed to unload " + self.name + "!\n" + format_exc()) + self.log.error(f"Failed to unload {self.get_display_name()}!\n{format_exc()}") pass async def _uninstall(self): try: - self.log.info("Attempting to uninstall with plugin " + self.name + "'s \"_uninstall\" function.\n") + self.log.info(f"Attempting to uninstall with plugin {self.get_display_name()}'s \"_uninstall\" function.\n") if hasattr(self.Plugin, "_uninstall"): if self.api_version > 0: await self.Plugin._uninstall() else: await self.Plugin._uninstall(self.Plugin) - self.log.info("Uninstalled " + self.name + "\n") + self.log.info(f"Uninstalled {self.get_display_name()}\n") else: - self.log.info("Could not find \"_uninstall\" in " + self.name + "'s main.py" + "\n") + self.log.info(f"Could not find \"_uninstall\" in {self.get_display_name()}'s main.py\n") except: - self.log.error("Failed to uninstall " + self.name + "!\n" + format_exc()) + self.log.error(f"Failed to uninstall {self.get_display_name()}!\n{format_exc()}") pass async def shutdown(self): if not self.shutdown_running: self.shutdown_running = True - self.log.info(f"Calling Loader unload function for {self.name}.") + self.log.info(f"Calling Loader unload function for {self.get_display_name()}.") await self._unload() if self.uninstalling: From dc08f43f947544fff99de51bef2e66c79ea2001e Mon Sep 17 00:00:00 2001 From: ynhhoJ <22500212+ynhhoJ@users.noreply.github.com> Date: Sun, 28 Dec 2025 15:09:29 +0200 Subject: [PATCH 2/2] feat(frontend): Print logs with plugin version if available --- .../settings/pages/plugin_list/index.tsx | 3 ++- frontend/src/plugin-loader.tsx | 19 +++++++++++-------- frontend/src/utils/pluginHelpers.ts | 14 ++++++++++++++ 3 files changed, 27 insertions(+), 9 deletions(-) create mode 100644 frontend/src/utils/pluginHelpers.ts diff --git a/frontend/src/components/settings/pages/plugin_list/index.tsx b/frontend/src/components/settings/pages/plugin_list/index.tsx index 43d797099..75365ba5b 100644 --- a/frontend/src/components/settings/pages/plugin_list/index.tsx +++ b/frontend/src/components/settings/pages/plugin_list/index.tsx @@ -23,6 +23,7 @@ import { requestPluginInstall, } from '../../../../store'; import { useSetting } from '../../../../utils/hooks/useSetting'; +import { getPluginDisplayName } from '../../../../utils/pluginHelpers'; import { useDeckyState } from '../../../DeckyState'; import PluginListLabel from './PluginListLabel'; @@ -69,7 +70,7 @@ function PluginInteractables(props: { entry: ReorderableEntry } try { await reloadPluginBackend(name); } catch (err) { - console.error('Error Reloading Plugin Backend', err); + console.error(`Error Reloading Plugin Backend for ${getPluginDisplayName(name, version)}`, err); } }} > diff --git a/frontend/src/plugin-loader.tsx b/frontend/src/plugin-loader.tsx index fd4dc1c0e..f0a358ef6 100644 --- a/frontend/src/plugin-loader.tsx +++ b/frontend/src/plugin-loader.tsx @@ -38,6 +38,7 @@ import { checkForPluginUpdates } from './store'; import TabsHook from './tabs-hook'; import Toaster from './toaster'; import { getVersionInfo } from './updater'; +import { getPluginDisplayName } from './utils/pluginHelpers'; import { getSetting, setSetting } from './utils/settings'; import TranslationHelper, { TranslationClass } from './utils/TranslationHelper'; @@ -343,7 +344,7 @@ class PluginLoader extends Logger { public dismountAll() { for (const plugin of this.plugins) { - this.log(`Dismounting ${plugin.name}`); + this.log(`Dismounting ${getPluginDisplayName(plugin.name, plugin.version)}`); plugin.onDismount?.(); } } @@ -403,14 +404,14 @@ class PluginLoader extends Logger { timeoutMS?: number, ) { if (useQueue && this.reloadLock) { - this.log('Reload currently in progress, adding to queue', name); + this.log(`Reload currently in progress, adding ${getPluginDisplayName(name, version)} to queue`); this.pluginReloadQueue.push({ name, version: version, loadType }); return; } try { if (useQueue) this.reloadLock = true; - this.log(`Trying to load ${name}`); + this.log(`Trying to load ${getPluginDisplayName(name, version)}`); this.unloadPlugin(name, true); const startTime = performance.now(); @@ -420,7 +421,7 @@ class PluginLoader extends Logger { this.deckyState.setDisabledPlugins(this.deckyState.publicState().disabledPlugins.filter((d) => d.name !== name)); this.deckyState.setPlugins(this.plugins); - this.log(`Loaded ${name} in ${endTime - startTime}ms`); + this.log(`Loaded ${getPluginDisplayName(name, version)} in ${endTime - startTime}ms`); } catch (e) { throw e; } finally { @@ -502,16 +503,17 @@ class PluginLoader extends Logger { version: version, loadType, }); - } else throw new Error(`${name} frontend_bundle not OK`); + } else throw new Error(`${getPluginDisplayName(name, version)} frontend_bundle not OK`); break; default: - throw new Error(`${name} has no defined loadType.`); + throw new Error(`${getPluginDisplayName(name, version)} has no defined loadType.`); } } catch (e) { if (e === timeoutException) throw timeoutException; - this.error('Error loading plugin ' + name, e); + this.error(`Error loading plugin ${getPluginDisplayName(name, version)}`, e); + const TheError: FC<{}> = () => ( @@ -731,7 +733,8 @@ class PluginLoader extends Logger { backendAPI.useQuickAccessVisible = useQuickAccessVisible; } - this.debug(`${pluginName} connected to loader API.`); + // Note: version info not available at connection time + this.debug(`Plugin ${pluginName} connected to loader API.`); return backendAPI; }, }; diff --git a/frontend/src/utils/pluginHelpers.ts b/frontend/src/utils/pluginHelpers.ts new file mode 100644 index 000000000..a23f801ed --- /dev/null +++ b/frontend/src/utils/pluginHelpers.ts @@ -0,0 +1,14 @@ +/** + * Returns a formatted display name for a plugin with version if available + * @param name - The plugin name + * @param version - The optional plugin version + * + * @returns Formatted string like "PluginName (v1.2.3)" or just "PluginName" if version is undefined + */ +export function getPluginDisplayName(name: string, version?: string): string { + if (version) { + return `${name} (v${version})`; + } + + return name; +}