Ruby
 Computer >> コンピューター >  >> プログラミング >> Ruby

Rubyで例外が発生したときにローカル変数とインスタンス変数をログに記録する

簡単に再現できないバグがあったことはありますか?それは、人々があなたのアプリをしばらく使用したときにのみ起こるようです。そして、エラーメッセージとバックトレースは驚くほど役に立ちません。

例外が発生する直前のアプリの状態のスナップショットを撮ることができれば、このような場合に非常に便利です。たとえば、すべてのローカル変数とその値のリストを作成できる場合。ええと、できます-そしてそれはそれほど難しいことではありません!

この投稿では、例外時にローカルをキャプチャする方法を紹介します。しかし、最初に、私はあなたに警告する必要があります。 これらの手法はいずれも本番環境では使用しないでください。 ステージング、プレプロデュース、開発などで使用できます。本番環境では使用できません。私たちが使用する宝石は、かなり重い内省魔法に依存しており、せいぜいアプリの速度を低下させます。最悪の場合...誰が知っていますか?

binding_of_callerの紹介

binding_of_caller gemを使用すると、現在のスタックの任意のレベルのバインディングにアクセスできます。すっごく.....それは正確にはどういう意味ですか?

「スタック」は、現在「進行中」のメソッドのリストにすぎません。 callerを使用できます 現在のスタックを調べるメソッド。簡単な例を次に示します。

def a
  puts caller.inspect # ["caller.rb:20:in `<main>'"]
  b()
end

def b
  puts caller.inspect # ["caller.rb:4:in `a'", "caller.rb:20:in `<main>'"]
  c()
end

def c
  puts caller.inspect # ["caller.rb:11:in `b'", "caller.rb:4:in `a'", "caller.rb:20:in `<main>'"]
end

a()

バインディングは、現在の実行コンテキストのスナップショットです。以下の例では、メソッドのバインディングをキャプチャし、それを使用してメソッドのローカル変数にアクセスします。

def get_binding
  a = "marco"
  b = "polo"
  return binding
end

my_binding = get_binding

puts my_binding.local_variable_get(:a) # "marco"
puts my_binding.local_variable_get(:b) # "polo"

binding_of_caller gemを使用すると、現在の実行スタックの任意のレベルのバインディングにアクセスできます。たとえば、これを使用してcを許可できます。 aへのメソッドアクセス メソッドのローカル変数。

require "rubygems"
require "binding_of_caller"

def a
  fruit = "orange"
  b()
end

def b
  fruit = "apple"
  c()
end

def c
  fruit = "pear"

  # Get the binding "two levels up" and ask it for its local variable "fruit"
  puts binding.of_caller(2).local_variable_get(:fruit) 
end

a() # prints "orange"

この時点で、おそらく2つの相反する感情を感じています。これは本当にクールなので、興奮。そして嫌悪感。これは、DHHと言うよりも早く依存関係の醜い混乱に退化する可能性があるためです。

例外時にローカルのログを記録する

binding_of_callerをマスターしたので、例外時にすべてのローカル変数をログに記録するのは簡単です。以下の例では、raiseメソッドをオーバーライドしています。私の新しいraiseメソッドは、それを呼び出したメソッドのバインディングをフェッチします。次に、すべてのローカルを繰り返して印刷します。

require "rubygems"
require "binding_of_caller"

module LogLocalsOnRaise
  def raise(*args)
    b = binding.of_caller(1)
    b.eval("local_variables").each do |k|
      puts "Local variable #{ k }: #{ b.local_variable_get(k) }"
    end
    super
  end
end

class Object
  include LogLocalsOnRaise
end

def buggy
  s = "hello world"
  raise RuntimeError
end

buggy()

実際の動作は次のとおりです。

Rubyで例外が発生したときにローカル変数とインスタンス変数をログに記録する

演習:インスタンス変数をログに記録する

ローカルと一緒にインスタンス変数をログに記録するための演習として残しておきます。ヒントは次のとおりです。my_binding.eval("instance_variables")を使用できます およびmy_binding.instance_variable_get my_binding.eval("local_variables")を使用するのとまったく同じ方法で およびmy_binding.instance_variable_get

簡単な方法

これはかなりクールなトリックです。ただし、特にアプリがステージング中であり、複数のユーザーが使用している場合は、ログファイルを確認するのがバグを修正するための最も便利な方法ではありません。また、維持しなければならないのはコードだけです。

Honeybadgerを使用してアプリのエラーを監視している場合は、ローカルを自動的にキャプチャできます。あなたがしなければならないのは、binding_of_callergemをGemfileに追加することだけです:

# Gemfile

group :development, :staging do
  # Including this gem enables local variable capture via Honeybadger
  gem "binding_of_caller"
  ...
end

これで、例外が発生するたびに、バックトレース、パラメータなどとともにすべてのローカルのレポートが表示されます。

Rubyで例外が発生したときにローカル変数とインスタンス変数をログに記録する


  1. Rubyで環境変数を使用する方法

    環境変数はキーと値のペアであり、次のようになります。 KEY=VALUE これらの変数を使用して、コンピューター内のすべてのプログラム間で構成オプションを共有します。 そのため、それらがどのように機能するか、およびENVを使用してRubyプログラムからそれらにアクセスする方法を学ぶことが重要です。 特別な変数。 環境変数の例 : デフォルトのエディターの構成 宝石の場所をRubyに伝える(GEM_PATH / GEM_HOME ) APIキーを、ソース管理(git)にコミットせずにアプリケーションに渡す オペレーティングシステムがバイナリファイル(Windowsでは.exe)を検

  2. Rubyのケースステートメントの多くの用途

    if / elsifを使用する必要があるときはいつでも 代わりにRubyのcaseステートメントを使用することを検討できるステートメント。この投稿では、いくつかの異なるユースケースと、それが実際に内部でどのように機能するかを学びます。 注:他のプログラミング言語では、これはスイッチとして知られています。 ステートメント。 Rubyのcaseステートメントのコンポーネント: キーワード 説明 ケース ケースステートメントの定義を開始します。使用する変数を取得します。 いつ 一致する可能性のあるすべての条件は1つのwhenステートメントです。 その他 一致