🐾

Nest-Commander における Error Handling を設定する

2024/12/16に公開

Ubie の NestJS で書かれたバックエンドアプリケーションのバッチ処理として Nest-Commander を利用しています。

バッチ処理の失敗時に期待する動きとして exit-status が 1 になる、というものがありますが残念ながら Nest-Commander を使った処理内で Error が throw されたとしても上手く設定をしないと exit(0) となってしまいます。

バッチ処理の仕様によっては exit-status が常に 0 では問題になってしまうはずなので適切に設定する必要があります。

挙動を確認する

簡単のために Nest-Commander を使った簡易なコマンドを実装してその中で Error を投げてみます。

export class SampleCommand extends CommandRunner {
  async run(_: string[]): Promise<void> {
    throw Error("Not implemented");
  }
}

しかしログには何も出力されずに終了してしまいます。

Successfully compiled: 733 files with swc (611.99ms)
INFO [NestFactory] Starting Nest application...

exit status も 0 になってしまっています。

$ echo $?
0

これはエラーを投げる変わりに process.exit(1) とすれば良いですが、インフラの影響などの不意な Error に対応しようと思うと処理全体を try-catch する、などが考えられます。

一方で cli アプリケーションの実装として exit-status を変更したいニーズはあるはずで、Nest-Commander の仕組みとして定義されているのではないかということで、挙動を確認するために Nest-Commander のコードを読んでいきます。

コードを読む

まずはエントリポイントとなっている CommandFactory.run(AppModule) から読み進めて行くと、すぐに CommandRunnerServicerun が見つかります。

async run(args?: string[]): Promise<void> {
  await this.commander
    .parseAsync(args || process.argv)
    .catch(this.options.serviceErrorHandler);
}

この parseAsync 関数では名前から湧くイメージとは裏腹にコマンドの実行も担っており、これを catch しているコールバックで this.options.serviceErrorHandler が渡されており、これはまさに CommandFactory.runCommandFactory.createWithoutRunning とかの第2引数にわたす CommandFactoryRunOptions の中身でした。

ということで以下のように設定しておけば Error が発生したときにちゃんと exit(1) してくれるようになります。

await CommandFactory.run(AppModule, {
  serviceErrorHandler: (error) => {
    console.log('My error handler', error)
    process.exit(1);
  }
})
Ubie テックブログ

Discussion