如何在Node中用python_shell重定向Bokeh Server日志至文件?
Let's break down why your initial approach didn't work, then walk through two solid solutions—one using shell redirection with python_shell, and a more robust stream-based method that eliminates the zombie process issue you faced with exec.
Why Your First Attempt Failed
The >> and 2>&1 are shell redirection operators, not arguments for the Python interpreter or Bokeh Server. When you passed them in args or pythonOptions, python_shell treated them as literal command-line inputs for bokeh serve, which is why they had no effect. By default, python_shell runs the Python process directly, not through a shell, so shell-specific syntax isn't parsed.
Solution 1: Enable Shell Mode in python_shell
If you prefer to use shell redirection, you can tell python_shell to execute the command through a shell. This lets the shell handle the log redirection operators properly.
Here's the adjusted code:
const PythonShell = require('python-shell').PythonShell; // Build the full shell command (wrap paths in quotes if they contain spaces) const fullCommand = `${python_path} -m bokeh serve >> ${debug_path} 2>&1`; const options = { shell: true, // Enable shell execution to parse redirection operators cwd: `${path}` // Set the working directory where Bokeh should run }; PythonShell.run(fullCommand, options, (err, results) => { if (err) { logger.error(`${err}`); return; } if (results) { logger.info(results); } });
Note: If your paths (like python_path or debug_path) include spaces, wrap them in double quotes to avoid shell parsing errors, e.g., "${python_path}".
Solution 2: Stream Output Directly (Recommended)
This method avoids shell dependencies entirely. Instead, we'll listen to the stdout/stderr streams from the Bokeh Server process and write logs to the file in real-time. Most importantly, we can properly clean up the child process if Node is terminated, fixing the zombie process problem from your exec approach.
Here's the implementation:
const fs = require('fs'); const PythonShell = require('python-shell').PythonShell; // Create a write stream for the log file (append mode to preserve existing logs) const logStream = fs.createWriteStream(debug_path, { flags: 'a' }); // Set the working directory for Bokeh Server process.chdir(`${path}`); const options = { pythonPath: `${python_path}`, pythonOptions: ['-m', 'bokeh', 'serve'] }; // Initialize the PythonShell instance const bokehProcess = new PythonShell('', options); // Capture stdout and write to log file in real-time bokehProcess.stdout.on('data', (data) => { const output = data.toString().trim(); logStream.write(`${output}\n`); // Add newline for readable log formatting logger.info(output); // Optional: also log to Node's internal logger }); // Capture stderr and write to log file (mark errors explicitly) bokehProcess.stderr.on('data', (data) => { const errorOutput = data.toString().trim(); logStream.write(`[ERROR] ${errorOutput}\n`); logger.error(errorOutput); }); // Clean up when Bokeh Server exits bokehProcess.end((err) => { if (err) { logger.error(`Bokeh Server error: ${err}`); } logStream.end(); // Close the log stream to avoid resource leaks logger.info('Bokeh Server process exited cleanly'); }); // Prevent zombie processes by killing Bokeh when Node receives termination signals process.on('SIGINT', () => { bokehProcess.childProcess.kill(); logStream.end(); logger.info('Node process interrupted; Bokeh Server terminated'); process.exit(0); }); process.on('SIGTERM', () => { bokehProcess.childProcess.kill(); logStream.end(); logger.info('Node process terminated; Bokeh Server terminated'); process.exit(0); });
Why This Is Better:
- No shell dependency: Avoids path escaping issues and shell-specific behavior.
- Real-time control: You can filter, format, or modify logs before writing them to the file.
- Zombie process prevention: We explicitly kill the Bokeh Server when Node receives termination signals (like
SIGINTfor Ctrl+C orSIGTERMfor graceful shutdown), ensuring no orphaned processes are left behind.
内容的提问来源于stack exchange,提问作者ChesuCR

