6
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

RubyでRuntimeError(例外)のバックトレースを最初のraiseまでさかのぼって出力する

Last updated at Posted at 2017-07-26

Ruby2.1で導入されたException#causeを参照すれば、例外をrescueして、そこから新たな例外を投げた時も、元の例外のraiseまでたどれます。

MAX_BACKTRACE_DEPTH = 5

def level1
  begin
    level2
  rescue
    raise RuntimeError.new("level1で発生したエラーです")
  end
end

def level2
  begin
    level3
  rescue
    raise RuntimeError.new("level2で発生したエラーです")
  end
end

def level3
  raise RuntimeError.new("level3で発生したエラーです")
end
#
# 再帰呼び出しでcauseをたどり、すべてのバックトレースを得る
#
def full_backtrace(e, depth:)
  items = []
  items << e.class.name + " " + e.message
  items += e.backtrace
  if (e.cause == nil) || (depth >= MAX_BACKTRACE_DEPTH)
    return items
  else
    return items + full_backtrace(e.cause, depth: depth + 1)
  end
end

begin
  level1
rescue => e
  puts full_backtrace(e, depth: 1).join("\n")
end

上記のコードは、このような流れになります。

  • level3例外raise
  • level2で、level3の例外をrescueして、新たな例外をraise
  • level1で、level2の例外をrescueして、新たな例外をraise

出力結果

RuntimeError level1で発生したエラーです
error2.rb:7:in `rescue in level1'
error2.rb:4:in `level1'
error2.rb:35:in `<main>'
RuntimeError level2で発生したエラーです
error2.rb:15:in `rescue in level2'
error2.rb:12:in `level2'
error2.rb:5:in `level1'
error2.rb:35:in `<main>'
RuntimeError level3で発生したエラーです
error2.rb:20:in `level3'
error2.rb:13:in `level2'
error2.rb:5:in `level1'
error2.rb:35:in `<main>'

level1 -> level2 -> level3と、最初にlevel3の例外をraiseした場所まで追えます。エラーハンドラでこのようにログ出力しておくと、エラーの原因を特定しやすくなりますね。

ちなみに、MAX_BACKTRACE_DEPTHを2に設定すると、level2でraiseした例外までしか出力しません。実際の開発でも例外を追う階層に制限をかけたほうがいいかもしれません。

RuntimeError level1で発生したエラーです
error2.rb:7:in `rescue in level1'
error2.rb:4:in `level1'
error2.rb:35:in `<main>'
RuntimeError level2で発生したエラーです
error2.rb:15:in `rescue in level2'
error2.rb:12:in `level2'
error2.rb:5:in `level1'
error2.rb:35:in `<main>'

さらに余談ですが、

def level3
  raise "level3で発生したエラーです"
end

のようにしても、RuntimeErrorとしてraiseされます。つまり出力結果は同じ。

しかし、

def level3
  raise Exception.new("level3で発生したエラーです")
end

としてしまうと、途中のlevel2,level1のrescueでは、例外をキャッチしてくれず、最初のbegin〜rescueのrescue => eで例外がキャッチされます。rescueだけだと、Exceptionはキャッチされないことに注意。

バックトレースの取りこぼしをなくすためにも、基本的にアプリケーションで使う例外は、RuntimeErrorもしくは、そのサブクラスにしましょう。rescueだけを書くというケースもあまりなさそうですけど。

6
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
6
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?