結論
Rバージョン4.0以上で、次のコードを実行しておくとエラー時に自動でスタックトレースが出力されるようになります。
globalCallingHandlers(error = function(e) { capture.output(traceback(6), file = stderr()) })
動作
次のような内容のファイル example.R
があるとします。
# example.R
f <- function() { g() }
g <- function() { h() }
h <- function() { stop("Error") }
これを次のようにして呼び出すと
source("example.R")
f()
標準エラー出力に以下のようなメッセージが出力されます。
4: stop("Error") at example.R#3
3: h() at example.R#2
2: g() at example.R#1
1: f()
Error in h() : Error
動機
冒頭のコードを実行せず、デフォルトの状態で
source("example.R")
f()
を実行すると、次のようなエラーメッセージが表示されます。
Error in h() : Error
このように、Rではエラーが発生してもその直接の呼び出ししかエラーメッセージに表示されません。
インタラクティブモードでRを実行している場合は、エラーが発生した後でtraceback()
関数を実行すれば直前のエラーについてのコールスタック情報を表示できます。
traceback()
4: stop("Error") at example.R#3
3: h() at example.R#2
2: g() at example.R#1
1: f()
このように、どの関数がどのような順序で呼び出されたか、その関数はどのファイルの何行目で定義されているかがわかります。 これはスタックトレースとも呼ばれ、エラー時の原因調査に役立つため多くの言語ではエラー発生時に自動的に出力されるようになっています。
定型作業をRで自動化して定期的にスケジュール実行したい場合などには、Rをインタラクティブモードではなくバッチモードで実行することになります。Rscript
コマンドを使って上と同じコードをバッチモードで実行してみます。
$ Rscript -e 'source("example.R"); f()'
このときの出力は次のようになります。
Error in h() : Error
Calls: f -> g -> h
Execution halted
コールスタックが f -> g -> h
と表示されているものの、traceback()
のようにファイル名や行番号などは表示されません。
バッチモードでは後からtraceback()
関数でエラー時のスタックトレースを調べることはできません。そのため、エラーが起きたときにソースコード上のどこに問題があるのかわかりにくいのが難点でした。エラーが発生した時点で自動的にtraceback()
の内容が出力されるようあらかじめ設定してあれば、もし処理が失敗しても後から原因箇所を特定しやすくなります。
実装のポイント
冒頭のコードを再掲します。
globalCallingHandlers(error = function(e) { capture.output(traceback(6), file = stderr()) })
globalCallingHandlers()
はR 4.0で追加された関数で、グローバルな条件ハンドラを設定できます。ここではerror
クラスの条件が発生したときに実行される関数を設定しています。条件ハンドリングについては前の記事に書きましたので詳しくはそちらをご参照ください。
引数を与えずにtraceback()
を呼び出すと直前のエラーについてのコールスタック情報が出力されますが、整数値の引数を与えて呼び出すと現在のコールスタックについての情報が出力されます。このとき、コールスタックのうち最初のいくつかはtraceback()
自身の呼び出しなどデバッグに不要な情報なので、与えた整数値の数だけ表示をスキップすることができます。
capture.output()
を使うと、Rの出力をキャプチャして通常の出力先とは異なるファイルに書き込んだり文字列ベクトルに格納したりできます。ここではtraceback()
の出力先を標準エラー出力に変えています。
参考
本記事の内容は
"A simple way to show stack trace on error in R - Kun Ren's Blog Posts"
を下敷きにしています。そちらではR 3.6以前でも使えるoptions(error=...)
による設定例が示されています。