add more robust logging

This commit is contained in:
kmmiio99o 2026-01-09 21:28:58 +01:00
parent 7cb2d1aa9c
commit 0cca83ff07
No known key found for this signature in database
GPG key ID: 163FE39F7772D2EC
2 changed files with 237 additions and 37 deletions

View file

@ -10,6 +10,108 @@ import { serviceFactory } from "./services/ServiceFactory";
import Settings from "./ui/pages/Settings";
import patchSidebar from "./sidebar";
(function installUnhandledRejectionHandler() {
try {
const g = typeof globalThis !== "undefined" ? (globalThis as any) : {};
// Avoid installing multiple times
if (g && !g.__scrobble_unhandled_rejection_installed) {
const handler = (ev: any) => {
try {
// ev may be an event or a reason object depending on the runtime
const reason = ev && (ev.reason ?? ev) ? (ev.reason ?? ev) : ev;
console.error("[Scrobble Plugin] Unhandled rejection:", reason);
} catch (inner) {
// Defensive: if formatting the event fails, still log something
console.error("[Scrobble Plugin] Unhandled rejection (unknown format)", ev);
}
};
// Prefer globalThis.addEventListener
if (g && typeof g.addEventListener === "function") {
try {
g.addEventListener("unhandledrejection", handler);
g.__scrobble_unhandled_rejection_installed = true;
return;
} catch (e) {
// continue to other fallbacks
console.error("[Scrobble Plugin] Failed to register via globalThis.addEventListener:", e);
}
}
// If window exists and supports addEventListener, use it
if (typeof (globalThis as any).window !== "undefined") {
const w = (globalThis as any).window;
if (w && typeof w.addEventListener === "function") {
try {
w.addEventListener("unhandledrejection", handler);
g.__scrobble_unhandled_rejection_installed = true;
return;
} catch (e) {
console.error("[Scrobble Plugin] Failed to register via window.addEventListener:", e);
}
} else if (w) {
// fallback to assigning onunhandledrejection if available and not a function call
try {
if (typeof w.onunhandledrejection === "undefined") {
w.onunhandledrejection = (ev: any) => handler(ev);
g.__scrobble_unhandled_rejection_installed = true;
return;
}
} catch (e) {
console.error("[Scrobble Plugin] Failed to assign window.onunhandledrejection:", e);
}
}
}
// As a last resort, try process.on (Node-style). Guard existence and function type.
try {
const proc = (globalThis as any).process;
if (proc && typeof proc.on === "function") {
proc.on("unhandledRejection", (reason: any) => handler({ reason }));
g.__scrobble_unhandled_rejection_installed = true;
return;
}
} catch (e) {
// ignore
}
// If none of the above worked, set a marker so we don't keep trying on repeated loads
g.__scrobble_unhandled_rejection_installed = true;
}
} catch (e) {
// Most defensive: don't let any error here block plugin loading
try {
console.error("[Scrobble Plugin] Error while installing unhandled rejection handler:", e);
} catch {
/* swallow */
}
}
})();
// Heartbeat helpers
let __scrobbleHeartbeat: any;
function startHeartbeat() {
if (__scrobbleHeartbeat) return;
try {
__scrobbleHeartbeat = setInterval(() => {
console.log("[Scrobble Plugin] Heartbeat (alive)");
}, 60_000);
} catch (e) {
console.error("[Scrobble Plugin] Failed to start heartbeat:", e);
}
}
function stopHeartbeat() {
if (!__scrobbleHeartbeat) return;
try {
clearInterval(__scrobbleHeartbeat);
} catch (e) {
console.error("[Scrobble Plugin] Failed to clear heartbeat:", e);
} finally {
__scrobbleHeartbeat = undefined;
}
}
export const pluginState = {
pluginStopped: false,
lastActivity: undefined,
@ -28,18 +130,29 @@ let sidebarUnpatch: (() => void) | undefined;
// Set up default plugin settings
const defaultSettings: LFMSettings = Constants.DEFAULT_SETTINGS;
for (const key of Object.keys(defaultSettings)) {
// Defensive: plugin.storage may not accept arbitrary keys in some environments,
// but assignment should generally be fine in Vendetta.
try {
plugin.storage[key] =
plugin.storage[key] ?? defaultSettings[key as keyof typeof defaultSettings];
} catch (e) {
console.error("[Scrobble Plugin] Failed to initialize default setting", key, e);
}
}
try {
// safe default for addToSidebar
plugin.storage.addToSidebar ??= false;
} catch (e) {
// ignore if storage access fails
}
export const currentSettings = new Proxy(plugin.storage, {
get(target, prop) {
return target[prop];
return (target as any)[prop];
},
set(target, prop, value) {
target[prop] = value;
(target as any)[prop] = value;
return true;
},
});
@ -50,8 +163,7 @@ const MAX_CONNECTION_ATTEMPTS = 3;
const RECONNECT_DELAY = 5000;
const log = (...args: any[]) => console.log("[Scrobble Plugin]", ...args);
const logError = (...args: any[]) =>
console.error("[Scrobble Plugin] Error:", ...args);
const logError = (...args: any[]) => console.error("[Scrobble Plugin] Error:", ...args);
async function tryInitialize() {
try {
@ -63,9 +175,7 @@ async function tryInitialize() {
connectionAttempts++;
if (connectionAttempts < MAX_CONNECTION_ATTEMPTS) {
log(
`Retrying connection (${connectionAttempts}/${MAX_CONNECTION_ATTEMPTS})...`,
);
log(`Retrying connection (${connectionAttempts}/${MAX_CONNECTION_ATTEMPTS})...`);
setTimeout(tryInitialize, RECONNECT_DELAY);
} else {
logError(`Failed to connect after ${MAX_CONNECTION_ATTEMPTS} attempts`);
@ -80,7 +190,12 @@ async function validateAndInitialize() {
return;
}
const serviceName = serviceFactory.getCurrentService().getServiceName();
let serviceName = "Unknown";
try {
serviceName = serviceFactory.getCurrentService().getServiceName();
} catch (e) {
logError("Failed to determine current service name:", e);
}
// Make sure we have credentials for whatever service is selected
const service = currentSettings.service;
@ -91,9 +206,7 @@ async function validateAndInitialize() {
hasCredentials = !!(currentSettings.username && currentSettings.apiKey);
break;
case "librefm":
hasCredentials = !!(
currentSettings.librefmUsername && currentSettings.librefmApiKey
);
hasCredentials = !!(currentSettings.librefmUsername && currentSettings.librefmApiKey);
break;
case "listenbrainz":
hasCredentials = !!currentSettings.listenbrainzUsername;
@ -101,9 +214,7 @@ async function validateAndInitialize() {
}
if (!hasCredentials) {
logError(
`Missing credentials for ${serviceName}. Please configure in plugin settings.`,
);
logError(`Missing credentials for ${serviceName}. Please configure in plugin settings.`);
return;
}
@ -115,14 +226,22 @@ async function validateAndInitialize() {
} else {
log("Waiting for Discord connection...");
const waitForUser = () => {
try {
if (UserStore.getCurrentUser()) {
log("Discord connection established");
tryInitialize();
FluxDispatcher.unsubscribe("CONNECTION_OPEN", waitForUser);
}
} catch (e) {
logError("Error while waiting for Discord connection:", e);
}
};
try {
FluxDispatcher.subscribe("CONNECTION_OPEN", waitForUser);
} catch (e) {
logError("Failed to subscribe to CONNECTION_OPEN:", e);
}
}
}
@ -131,15 +250,27 @@ export default {
log("Plugin loading...");
pluginState.pluginStopped = false;
// Start heartbeat so we can confirm plugin process is alive
try {
startHeartbeat();
log("Heartbeat started");
} catch (e) {
logError("Failed to start heartbeat:", e);
}
// Check if service is configured
if (!currentSettings.service) {
log("No service configured. Please select a service in plugin settings.");
} else {
// Show what we're starting with
try {
const serviceName = serviceFactory.getCurrentService().getServiceName();
log(
`Configuration: Service=${serviceName}, Update Interval=${currentSettings.timeInterval}s, Verbose=${currentSettings.verboseLogging}`,
);
} catch (e) {
logError("Failed to read service configuration:", e);
}
}
// Add to sidebar if user wants it
@ -147,7 +278,11 @@ export default {
sidebarUnpatch = patchSidebar();
cleanupFunctions.push(() => {
if (sidebarUnpatch) {
try {
sidebarUnpatch();
} catch (e) {
// ignore individual cleanup errors
}
sidebarUnpatch = undefined;
}
});
@ -155,13 +290,26 @@ export default {
log("Sidebar setup failed:", error);
}
try {
validateAndInitialize();
log("onLoad complete - validateAndInitialize triggered");
} catch (e) {
logError("validateAndInitialize threw an error:", e);
}
},
onUnload() {
log("Plugin unloading...");
pluginState.pluginStopped = true;
// Stop heartbeat first to avoid stray logs after unload
try {
stopHeartbeat();
log("Heartbeat stopped");
} catch (e) {
logError("Failed to stop heartbeat:", e);
}
// Run all cleanup functions
cleanupFunctions.forEach((cleanup) => {
try {
@ -172,7 +320,11 @@ export default {
});
cleanupFunctions.length = 0;
try {
stop();
} catch (e) {
logError("Error while stopping plugin manager:", e);
}
log("Plugin unloaded");
},
@ -184,9 +336,13 @@ export default {
const newSidebar = newSettings.addToSidebar;
// Apply the new settings
try {
Object.assign(currentSettings, newSettings);
} catch (e) {
logError("Failed to apply new settings:", e);
}
log("Settings updated:", Object.keys(newSettings));
log("Settings updated:", Object.keys(newSettings || {}));
// Check if sidebar setting changed
if (oldSidebar !== newSidebar) {
@ -199,7 +355,11 @@ export default {
}
} else {
if (sidebarUnpatch) {
try {
sidebarUnpatch();
} catch (e) {
logError("Failed to unpatch sidebar:", e);
}
sidebarUnpatch = undefined;
log("Sidebar disabled");
}
@ -209,7 +369,11 @@ export default {
// Switch services if needed
if (oldService !== newService && newService) {
log(`Service changed from ${oldService || "none"} to ${newService}`);
try {
await switchService(newService);
} catch (e) {
logError("Failed to switch service:", e);
}
} else if (!pluginState.pluginStopped && currentSettings.service) {
// Just restart with the updated settings (only if service is selected)
log("Restarting with updated settings...");
@ -217,7 +381,11 @@ export default {
} else if (!currentSettings.service) {
// Service was unselected, stop the plugin
log("Service unselected, stopping plugin...");
try {
stop();
} catch (e) {
logError("Error while stopping due to service unselected:", e);
}
}
},

View file

@ -53,8 +53,14 @@ class PluginManager {
// Check for new tracks and update Discord status when something changes
private async updateActivity() {
if (pluginState.pluginStopped) {
log("Plugin is stopped; skipping activity updates and clearing timers");
logVerbose("Plugin is stopped, skipping update");
try {
this.stopUpdates();
logVerbose("Update timers cleared due to stopped plugin");
} catch (e) {
logError("Error while stopping updates for stopped plugin:", e);
}
return;
}
@ -75,10 +81,16 @@ class PluginManager {
});
if (ignoredActivity) {
log(`Ignored app (${ignoredActivity.name}) is currently active; clearing activity and skipping updates`);
logVerbose(
`Ignored app (${ignoredActivity.name}) is currently playing, clearing activity`,
);
// Remove these setDebugInfo calls that cause TypeScript errors
try {
// Provide a little debug context so we can see why updates are skipped
setDebugInfo("ignoredActivity", ignoredActivity.name);
} catch (e) {
logVerbose("Failed to set debug info for ignored activity:", e);
}
clearActivity();
return;
}
@ -91,13 +103,20 @@ class PluginManager {
setDebugInfo("lastTrack", lastTrack);
if (!lastTrack.nowPlaying) {
log("No currently playing track reported by service; clearing activity and skipping RPC update");
logVerbose("Track is not currently playing");
try {
setDebugInfo("lastTrack_nowPlaying", false);
} catch (e) {
logVerbose("Failed to set debug info for nowPlaying:", e);
}
clearActivity();
return;
}
// use simple URL comparison like Last.fm plugin
if (pluginState.lastTrackUrl === lastTrack.url) {
log("Track unchanged; skipping Discord RPC update");
logVerbose("Track hasn't changed");
recordSuccessfulUpdate();
this.consecutiveFailures = 0;
@ -255,7 +274,20 @@ class PluginManager {
);
} catch (error) {
logError("Update failed:", error);
try {
recordServiceError(currentSettings.service, (error as Error).message);
} catch (e) {
logError("Failed to record service error:", e);
}
try {
setDebugInfo("lastUpdateError", {
message: (error as Error).message,
service: currentSettings.service,
lastTrackUrl: pluginState.lastTrackUrl,
});
} catch (e) {
logVerbose("Failed to set debug info for last update error:", e);
}
this.handleError(error as Error);
}
}