SIGINT, beforeExit and exit event handlers not called for ctrl-c keypress when application output is piped on - node.js

Note: I've already seen SIGINT handler in NodeJS app not called for ctrl-C (Mac) but it seems it's not my case.
I have a Node.js application - a Discord bot that uses Pino for logging. Main file, app.js is like this:
...
const logger = pino();
const client = new Discord.Client();
process
.on('SIGINT', () => {
console.log('SIGINT');
client.destroy();
logger.info('Exiting');
})
.on('beforeExit', () => { console.log('beforeExit'); }
.on('exit', () => { console.log('exit'); }
client.on('ready', () => {
logger.info(`Logged in`);
});
client.login(process.env.TOKEN);
when I start it just with node ./dist/app.js and press ctrl-c, it works - I get "SIGINT" from console.log(), "Exiting" from logger and "beforeExit", "exit" from console again. But I want to use a run script to redirect logging output to various files and to console:
#!/bin/bash
node ./dist/app.js | \
tee -a \
./logs/complete.log \
>(jq -cM --unbuffered 'select(.level == 40)' >> ./logs/user.log) \
>(jq -cM --unbuffered 'select(.level >= 50)' >> ./logs/error.log) | \
./node_modules/.bin/pino-pretty --levelFirst --ignore hostname,pid,ctx --translateTime SYS:standard
But now when I press ctrl-c it just exits silently, "SIGINT" handler is not activated at all. Same for "beforeExit" and "exit". It seems that ctrl-c is intercepted somewhere down the pipe.
So, how to make the mentioned handlers work? Also, could anyone offer an explanation why this happens and - if it's really something with the pipe - how breaking a pipe works in regard to initiating process?

Related

Is it possible to monitor terminal for specific output?

I am using NodeJS to run webots by command line and have redirected the stdout too the node terminal. My problem is that I want to trigger an event based on a console log. I tried redirecting the stdout of the command to another file, but this didn't seem to work.
This is the console output
INFO: sumo_example_two: Starting controller: python.exe -u sumo_example_two.py
INFO: sumo_supervisor: Starting controller: python.exe -u sumo_supervisor.py
robot2
INFO: sumo_example_one: Terminating.
INFO: sumo_example_two: Terminating.
INFO: sumo_supervisor: Terminating.
stdout:
I want to extract 'robot2'.
I have just tested and the following snippet works fine for me:
const { spawn } = require('child_process');
const ls = spawn('webots', ['--stdout']);
ls.stdout.on('data', (data) => {
console.log(`stdout: ${data}`);
// Process `data` as you prefer, something like
//
// if (data.includes('robot2')) {
// something()
// }
});

PM2 breaks child process bash script

I have a place in my code where I need to spawn a bash script, write to its standard input, and read from its standard output.
I do this using node's child_process.spawn.
Unfortunately, when I run this code under pm2, the bash script hangs forever when calling mkdir.
Is there any way to avoid this issue?
test.js
'use strict';
const child_process = require('child_process');
setInterval(() => {
const process = child_process.spawn('./test.sh');
process.on('exit', () => {
console.log('process exit');
});
process.stdout.on('data', (data) => {
console.log('Output: ' + data.toString('utf8'));
});
}, 1000);
test.sh
#!/usr/bin/env bash
TEMP=$(mktemp -d);
echo "Created directory $TEMP"
rm -rf ${TEMP}
echo "Deleted directory $TEMP"
Expected output
Starting
Spawning test.sh
Output: Created directory /tmp/tmp.I6Buifdmlu
Output: Deleted directory /tmp/tmp.I6Buifdmlu
process exit
Actual output
[STREAMING] Now streaming realtime logs for [test] process
2|test | Spawning test.sh
2|test | Spawning test.sh
Environment
OS: Ubuntu 14.04
Node: 4.7.3
PM2: 2.4.0
NB: I have tested this on Mac OSX and there is no problem

Command not called, anything wrong with this spawn syntax?

When i run this pidof command by hand, it works. Then put into my server.js.
// send signal to start the install script
var spw = cp.spawn('/sbin/pidof', ['-x', 'wait4signal.py', '|', 'xargs', 'kill', '-USR1']);
spw.stderr.on('data', function(data) {
res.write('----- Install Error !!! -----\n');
res.write(data.toString());
console.log(data.toString());
});
spw.stdout.on('data', function(data) {
res.write('----- Install Data -----\n');
res.write(data.toString());
console.log(data.toString());
});
spw.on('close', function(data) {
res.end('----- Install Finished, please to to status page !!! -----\n');
console.log('88');
});
In the web i only see "----- Install Finished, please to to status page !!!". My install script seems never get this USR1 signal. Anything wrong please ?
The problem is that you have two separate commands. You are piping the output of your /sbin/pidof command to the input stream of your xargs command. If you are using spawn (rather than exec, which a string exactly as you would write on the command line), you need to spawn one process per command.
Spawn your processes like this:
const pidof = spawn('/sbin/pidof', ['-x', 'wait4signal.py']);
const xargs = spawn('xargs', ['kill', '-USR1']);
Now pipe the output of the first process to the input of the second, like so:
pidof.stdout.pipe(xargs.stdin);
Now you can listen to events on your xargs process, like so:
xargs.stdout.on('data', data => {
console.log(data.toString());
});

Callback when starting Android emulator with shell command via node

I am opening an android emulator with node via a shell script:
var process = require('child_process');
process.exec('~/Library/Android/sdk/tools/emulator -avd Nexus_5_API_21_x86', processed);
function processed(data){
console.log('processed called', data, data.toString());
}
I need to be able to detect when the emulator has finished loading so I can initiate a screen unlock and then launch the browser to a specified url (~/Library/Android/sdk/platform-tools/adb shell input keyevent 82 and ~/Library/Android/sdk/platform-tools/adb shell am start -a android.intent.action.VIEW -d http://www.stackoverflow.com)
However, when I launch the emulator I don't appear to get anything back and the process stays engaged with the emulator. When shutting the process down (ctrl+c) the emulator is closed along with it. (This is the same behaviour as running the shell command directly in the terminal)
Is it possible to know when the emulator has opened and loaded?
How can I execute additional commands when the process continues to
run?
I solved it like a boss.
I was able to set a timer to check once a second if the bootanimation had stopped. If it has, we know the emulator is open and booted.
var process = require('child_process');
process.exec('~/Library/Android/sdk/tools/emulator -avd Nexus_5_API_21_x86');
function isEmulatorBooted(){
process.exec('~/Library/Android/sdk/platform-tools/adb shell getprop init.svc.bootanim', function(error, stdout, stderr){
if (stdout.toString().indexOf("stopped")>-1){
clearInterval(bootChecker);
emulatorIsBooted();
} else {
console.log('we are still loading');
}
});
}
function emulatorIsBooted(){
//unlock the device
process.exec('~/Library/Android/sdk/platform-tools/adb shell input keyevent 82');
//gotourl
process.exec('~/Library/Android/sdk/platform-tools/adb shell am start -a android.intent.action.VIEW -d http://192.168.10.126:9876/');
}
bootChecker = setInterval(function(){
isEmulatorBooted();
},1000);
In case anyone else is looking for this, made an alternate version of Fraser's script - which also starts the actual emulator in a background process.
#!/usr/bin/env node
const process = require('child_process');
/* Get last emulator in list of emulators */
process.exec("emulator -list-avds|sed '$!d'", (_, stdout) => {
console.log('[android emulator] Booting')
/* Start emulator */
process.exec(`nohup emulator -avd ${stdout.replace('\n', '')} >/dev/null 2>&1 &`)
/* Wait for emulator to boot */
const waitUntilBootedThen = completion => {
process.exec('adb shell getprop init.svc.bootanim', (_, stdout) => {
if (stdout.replace('\n', '') !== 'stopped') {
setTimeout(() => waitUntilBootedThen(completion), 250)
} else {
completion()
}
})
}
/* When emulator is booted up */
waitUntilBootedThen(() => {
console.log('[android emulator] Done')
})
})

Use child_process.execSync but keep output in console

I'd like to use the execSync method which was added in NodeJS 0.12 but still have the output in the console window from which i ran the Node script.
E.g. if I run a NodeJS script which has the following line I'd like to see the full output of the rsync command "live" inside the console:
require('child_process').execSync('rsync -avAXz --info=progress2 "/src" "/dest"');
I understand that execSync returns the ouput of the command and that I could print that to the console after execution but this way I don't have "live" output...
You can pass the parent´s stdio to the child process if that´s what you want:
require('child_process').execSync(
'rsync -avAXz --info=progress2 "/src" "/dest"',
{stdio: 'inherit'}
);
You can simply use .toString().
var result = require('child_process').execSync('rsync -avAXz --info=progress2 "/src" "/dest"').toString();
console.log(result);
Edit: Looking back on this, I've realised that it doesn't actually answer the specific question because it doesn't show the output to you 'live' — only once the command has finished running.
However, I'm leaving this answer here because I know quite a few people come across this question just looking for how to print the result of the command after execution.
Unless you redirect stdout and stderr as the accepted answer suggests, this is not possible with execSync or spawnSync. Without redirecting stdout and stderr those commands only return stdout and stderr when the command is completed.
To do this without redirecting stdout and stderr, you are going to need to use spawn to do this but it's pretty straight forward:
var spawn = require('child_process').spawn;
//kick off process of listing files
var child = spawn('ls', ['-l', '/']);
//spit stdout to screen
child.stdout.on('data', function (data) { process.stdout.write(data.toString()); });
//spit stderr to screen
child.stderr.on('data', function (data) { process.stdout.write(data.toString()); });
child.on('close', function (code) {
console.log("Finished with code " + code);
});
I used an ls command that recursively lists files so that you can test it quickly. Spawn takes as first argument the executable name you are trying to run and as it's second argument it takes an array of strings representing each parameter you want to pass to that executable.
However, if you are set on using execSync and can't redirect stdout or stderr for some reason, you can open up another terminal like xterm and pass it a command like so:
var execSync = require('child_process').execSync;
execSync("xterm -title RecursiveFileListing -e ls -latkR /");
This will allow you to see what your command is doing in the new terminal but still have the synchronous call.
Simply:
try {
const cmd = 'git rev-parse --is-inside-work-tree';
execSync(cmd).toString();
} catch (error) {
console.log(`Status Code: ${error.status} with '${error.message}'`;
}
Ref: https://stackoverflow.com/a/43077917/104085
// nodejs
var execSync = require('child_process').execSync;
// typescript
const { execSync } = require("child_process");
try {
const cmd = 'git rev-parse --is-inside-work-tree';
execSync(cmd).toString();
} catch (error) {
error.status; // 0 : successful exit, but here in exception it has to be greater than 0
error.message; // Holds the message you typically want.
error.stderr; // Holds the stderr output. Use `.toString()`.
error.stdout; // Holds the stdout output. Use `.toString()`.
}
When command runs successful:
Add {"encoding": "utf8"} in options.
execSync(`pwd`, {
encoding: "utf8"
})

Resources