Merge pull request #837 from hydralauncher/feat/logs-python-process-errors

feat: logs python process
This commit is contained in:
Zamitto 2024-08-19 21:35:14 -03:00 committed by GitHub
commit 7e7c4dae1d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 66 additions and 11 deletions

View File

@ -19,6 +19,7 @@ import {
LibtorrentPayload,
ProcessPayload,
} from "./types";
import { pythonInstanceLogger as logger } from "../logger";
export class PythonInstance {
private static pythonProcess: cp.ChildProcess | null = null;
@ -32,11 +33,13 @@ export class PythonInstance {
});
public static spawn(args?: StartDownloadPayload) {
logger.log("spawning python process with args:", args);
this.pythonProcess = startRPCClient(args);
}
public static kill() {
if (this.pythonProcess) {
logger.log("killing python process");
this.pythonProcess.kill();
this.pythonProcess = null;
this.downloadingGameId = -1;
@ -45,6 +48,7 @@ export class PythonInstance {
public static killTorrent() {
if (this.pythonProcess) {
logger.log("killing torrent in python process");
this.rpc.post("/action", { action: "kill-torrent" });
this.downloadingGameId = -1;
}
@ -138,12 +142,14 @@ export class PythonInstance {
save_path: game.downloadPath!,
});
} else {
await this.rpc.post("/action", {
action: "start",
game_id: game.id,
magnet: game.uri,
save_path: game.downloadPath,
} as StartDownloadPayload);
await this.rpc
.post("/action", {
action: "start",
game_id: game.id,
magnet: game.uri,
save_path: game.downloadPath,
} as StartDownloadPayload)
.catch(this.handleRpcError);
}
this.downloadingGameId = game.id;
@ -159,4 +165,14 @@ export class PythonInstance {
this.downloadingGameId = -1;
}
private static async handleRpcError(_error: unknown) {
await this.rpc.get("/healthcheck").catch(() => {
logger.error(
"RPC healthcheck failed. Killing process and starting again"
);
this.kill();
this.spawn();
});
}
}

View File

@ -4,6 +4,8 @@ import crypto from "node:crypto";
import fs from "node:fs";
import { app, dialog } from "electron";
import type { StartDownloadPayload } from "./types";
import { Readable } from "node:stream";
import { pythonInstanceLogger as logger } from "../logger";
const binaryNameByPlatform: Partial<Record<NodeJS.Platform, string>> = {
darwin: "hydra-download-manager",
@ -15,6 +17,13 @@ export const BITTORRENT_PORT = "5881";
export const RPC_PORT = "8084";
export const RPC_PASSWORD = crypto.randomBytes(32).toString("hex");
const logStderr = (readable: Readable | null) => {
if (!readable) return;
readable.setEncoding("utf-8");
readable.on("data", logger.log);
};
export const startTorrentClient = (args?: StartDownloadPayload) => {
const commonArgs = [
BITTORRENT_PORT,
@ -40,10 +49,14 @@ export const startTorrentClient = (args?: StartDownloadPayload) => {
app.quit();
}
return cp.spawn(binaryPath, commonArgs, {
stdio: "inherit",
const childProcess = cp.spawn(binaryPath, commonArgs, {
windowsHide: true,
stdio: ["inherit", "inherit"],
});
logStderr(childProcess.stderr);
return childProcess;
} else {
const scriptPath = path.join(
__dirname,
@ -53,8 +66,12 @@ export const startTorrentClient = (args?: StartDownloadPayload) => {
"main.py"
);
return cp.spawn("python3", [scriptPath, ...commonArgs], {
stdio: "inherit",
const childProcess = cp.spawn("python3", [scriptPath, ...commonArgs], {
stdio: ["inherit", "inherit"],
});
logStderr(childProcess.stderr);
return childProcess;
}
};

View File

@ -6,6 +6,10 @@ log.transports.file.resolvePathFn = (
_: log.PathVariables,
message?: log.LogMessage | undefined
) => {
if (message?.scope === "python-instance") {
return path.join(logsPath, "pythoninstance.txt");
}
if (message?.level === "error") {
return path.join(logsPath, "error.txt");
}
@ -23,4 +27,5 @@ log.errorHandler.startCatching({
log.initialize();
export const pythonInstanceLogger = log.scope("python-instance");
export const logger = log.scope("main");

View File

@ -158,7 +158,7 @@ export class WindowManager {
const recentlyPlayedGames: Array<MenuItemConstructorOptions | MenuItem> =
games.map(({ title, executablePath }) => ({
label: title,
label: title.length > 15 ? `${title.slice(0, 15)}` : title,
type: "normal",
click: async () => {
if (!executablePath) return;

View File

@ -20,6 +20,23 @@ if start_download_payload:
class Handler(BaseHTTPRequestHandler):
rpc_password_header = 'x-hydra-rpc-password'
skip_log_routes = [
"process-list",
"status"
]
def log_error(self, format, *args):
sys.stderr.write("%s - - [%s] %s\n" %
(self.address_string(),
self.log_date_time_string(),
format%args))
def log_message(self, format, *args):
for route in self.skip_log_routes:
if route in args[0]: return
super().log_message(format, *args)
def do_GET(self):
if self.path == "/status":
if self.headers.get(self.rpc_password_header) != rpc_password: