Better Express error
Better-express-error
Expressでの開発時、エラーが発生すると、通常はエラーページにそのまま出力されるか、本番環境なら404や500のページに直接リダイレクトされることが多い。
これは取り立てて言うほどのことでもないかもしれないが、正直な話、こんな画面を見て嬉しい気分になるだろうか?

Ruby on Railsの開発に慣れ親しんでいるエンジニアなら、デバッグのためにbetter_errorsや、Rails本体に組み込まれているエラートレースページを使ったことがあるはずだ。
だがExpressにおいては、better_errorsのような機能を持つパッケージをまだ見かけたことがなく、いつもこの見づらくて不格好なエラーメッセージを見て天を仰ぐしかなかった。
そこで、僕自身でこれを処理するシンプルなミドルウェアを作ってみた。実質的にはbetter_errorsのExpress版実装というわけだ。
エラーメッセージの分析
TypeError: range out of bound. Please check http://kjj6198.github.io for more information.
at app.get (/Users/kalan/code/express-error/server/app.js:17:9)
at Layer.handle [as handle_request] (/Users/kalan/code/express-error/node_modules/express/lib/router/layer.js:95:5)
at next (/Users/kalan/code/express-error/node_modules/express/lib/router/route.js:137:13)
at Route.dispatch (/Users/kalan/code/express-error/node_modules/express/lib/router/route.js:112:3)
at Layer.handle [as handle_request] (/Users/kalan/code/express-error/node_modules/express/lib/router/layer.js:95:5)
at /Users/kalan/code/express-error/node_modules/express/lib/router/index.js:281:22
at Function.process_params (/Users/kalan/code/express-error/node_modules/express/lib/router/index.js:335:12)
at next (/Users/kalan/code/express-error/node_modules/express/lib/router/index.js:275:10)
at jsonParser (/Users/kalan/code/express-error/node_modules/body-parser/lib/types/json.js:109:7)
at Layer.handle [as handle_request] (/Users/kalan/code/express-error/node_modules/express/lib/router/layer.js:95:5)
詳しく観察してみると、エラーメッセージのフォーマットはかなり規則正しいことが分かる。まず1行目はエラー名とメッセージであり、通常これが最も重要な情報だ。2行目以降はコールスタックとなっている。at … は特定の関数の呼び出しであり、括弧内にはファイル名や行数、列(row)の情報が含まれている。
エラーメッセージを簡単に分析できれば、プレーンテキストをより有益な情報へと変換できる。そのため、split('\n') で分割し、文字列に対して簡単な正規表現マッチングを行うことで、ファイル名と行番号の情報に分けることができる。
エラーの表示
1行目のエラーメッセージは、コードがまさにそこでエラーを吐いているため、通常最も重要な情報だ。そのため、1行目のエラーメッセージは一番目立つ場所に配置し、ハイライト表示にする。

そして2行目以降のエラーメッセージについては、色やサイズを使ってファイル名、行番号、呼び出された関数名を表示する。

先ほどの黒々としたテキストの塊に比べれば、このようにシンプルに整理するだけでも、何が起きたのかが一目で把握できるようになる。
ファイル内容の表示
だが、エラーメッセージを表示するだけでなく、該当するファイルの内容やそのコンテキスト(前後の文脈)も同時に確認できるようにしたい。そこで右半分には、エラーメッセージから得られたファイル名と行番号を利用して、該当するファイルの内容を表示するようにした。
Node.jsであれば、fs.readFileSync を使うだけで十分だ。
function(filename, line, row) {
const content = fs.readFileSync(filename);
content.toString()
.split('\n')
.slice(line - 5, line + 5)
.map(content => `<span>${content}</span>`)
.join('\n');
}
ここでは非常にシンプルな方法を採り、前後の5行をそのまま出力している。よりスマートなやり方としては、ASTなどの手法を用いて該当する関数の内容だけを出力することも考えられる。しかし、今のところは前後のコードを5行ずつ出力するだけで事足りる。
少し調整と修正を加えると、だいたいこんな感じになる:

簡単なハイライトを入れることで、開発者はどこでエラーが発生したかをすぐに把握できる。
REPL
エラーを表示するだけでなく、このページ上で簡単なコードを入力して問題点を確認した上で、実際のコードを修正できるようにしたい。
Node.jsには vm モジュールがあり、V8のVirtual Machineコンテキストを利用して指定したコードを実行できる。このモジュールを使えば、REPLのような機能を実装できる!
const debugContext = vm.createContext({
request: req,
response: res,
util: require("util"),
Buffer: require("buffer").Buffer,
stream: require("stream"),
console: {
log: util.format,
},
clear: "",
})
公開したい変数をcontextに渡し、POST経由でフロントエンドからのコードを読み込むことで、手軽にデバッグ効果を得られる。

(上記はまだ少し調整が必要だ)
すべて統合!

すべてを組み合わせると、ページはおおよそこのようになる。
元のプレーンテキストと比べると、ページのスタイル調整やREPL機能の実装に少し手間はかかったものの、デバッグの流れが格段にスムーズになった。
結論
詳細な実装はこのリポジトリにある。近いうちに時間ができたら、誰もが使いやすいようにミドルウェアの形式に切り出す予定だ。また、レイアウト全体やコードハイライトの部分を順次最適化し、フローや画面をよりスムーズにしていきたいと思っている。まあ、いつになるかは分からないけれど(笑)。
もし何かアドバイスや提案があれば、気軽にIssueを立ててほしい。
関連記事
- 測定が目標になるとき:窓税からPull Request数まで かつて僕は小さなツールを自作し、四半期で自分がどれだけPRに貢献したか、レビューコメントをどれだけ残したか、チケットをどれだけ消化したかを集計して、上司にアウトプットを証明しようとしたことがある。上司は淡々と、評価はアウトプットだけで見るものではないと言った。数年後、僕はようやく理解した――測定が目標になるとき、それはもはや良い測定ではなくなるのだ。英国の窓税、ハノイのネズミ駆除の報奨金から、現代のPR数による開発者評価に至るまで、そのメカニズムはまったく同じだ。
- Cloudflare Images を画像ストレージ・変換ソリューションとして使う ウェブページに画像を1枚置くのはフロントエンドにとって最も簡単なことだが、リサイズや各種フォーマットの生成、さらにはトラフィックの負荷に耐えることまで完璧にやろうとすると、実際には一つの包括的なソリューションが必要になる。僕はその後、すべて Cloudflare Images に任せるようになり、オリジナル画像1枚だけを渡すようにしている。
- もう AWS Access Key を使うのはやめよう Access Key は AWS において見落とされがちなセキュリティリスクだ。OIDC と IAM Role を組み合わせることで、GitHub Actions にシークレットを一切保持させることなく、安全に AWS リソースを操作できるようにする。
- データベース主キー:AUTO_INCREMENT、UUID、そしてUUIDv7 バックエンド開発で度々直面する主キーの決定。auto incrementを使うべきか、それともUUIDか?衝突への懸念は?UUIDv7とcreated_at + インデックスの性能差はどれほどか?実際に2,000万件のデータで検証したベンチマークと設計上の意思決定を解説する。