created: 2020-02-14T02:26:41.000Z

nodejsで標準入出力/エラーを共有した子プロセスを作る

cloudrunを使う際に必要になった

cloudrunはhttpエンドポイントから処理がキックされるので、コンテナでbashコマンドを実行したいときはnodejsのhttpサーバの子プロセスがスクリプトを実行するという形になる(つかいにくい)

子プロセスの出力をログとしてstackdriverに流したいので、nodejsで標準入出力/エラーを共有した子プロセスを作る必要があった

コード

こんなコードを書いて動作確認

  • $ node index.js out などで親プロセスを起動
  • サブコマンドごとに子プロセスが以下の動作
    • 標準出力を吐いて終了
    • エラー出力を吐いて終了
    • 異常終了
const { spawn } = require('child_process');

const pSpawn = (command, args, options) => new Promise((resolve, reject) => {
  const childProcess = spawn(command, args, options);
  childProcess.on('exit', code => code ? reject(code) : resolve(code));
});

(async () => {
  const subcommand = process.argv[2];
  const promise = pSpawn('./entrypoint.sh', [subcommand], {
    env: process.env,
    cwd: __dirname,
    stdio: 'inherit',
  });
  let exitCode;
  try {
    exitCode = await promise
  } catch (error) {
    exitCode = error;
  }
  console.log(`exitCode: ${exitCode}`);
})();

entrypoint.sh

#!/bin/bash
set -eu
scase "$1" in
  out)
    shift
    echo "one!"
    ;;
  err)
    shift
    perl -E 'warn "two"'
    ;;
  die)
    shift
    exit 77
    ;;
esac

動作

期待通りに動作した

  • stdoutはstdoutに
  • stderrはstderrに
  • exitcodeが0でないときはrejectされた
$ node index.js out
one!
exitCode: 0
# エラー出力を確認する
$ node index.js err 1>/dev/null
two at -e line 1.
$ node index.js die
exitCode: 77

以下は実装時のメモ

spawnのstdio引数

pSpawn('./entrypoint.sh', [subcommand], {
    env: process.env,
    cwd: __dirname,
    stdio: 'inherit',
  });

このstdio引数で標準出力/エラー出力を制御している

stdio: 'inherit' で今回やりたい動作にできる

'inherit': Pass through the corresponding stdio stream to/from the parent process. In the first three positions, this is equivalent to process.stdin, process.stdout, and process.stderr, respectively. In any other position, equivalent to 'ignore'.

pSpawn

const pSpawn = (command, args, options) => new Promise((resolve, reject) => {
  const childProcess = spawn(command, args, options);
  childProcess.on('exit', code => code ? reject(code) : resolve(code));
});

毎回 on.('exit') などを書きたくないのでヘルパー

  • spawnと引数は一緒
  • ただし異常終了した場合はexitcodeをrejectする

pidが欲しい場合などはちゃんとしたのをつかうのがよい