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"

이 시점에서 아마도 두 가지 상충되는 감정을 느끼고 있을 것입니다. 흥분, 이것이 정말 멋지기 때문입니다. 그리고 혐오감, 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를 사용하여 앱에 오류가 있는지 모니터링하는 경우 로컬을 자동으로 캡처할 수 있습니다. Gemfile에 binding_of_caller gem을 추가하기만 하면 됩니다.

# Gemfile

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

이제 예외가 발생할 때마다 역추적, 매개변수 등과 함께 모든 로컬에 대한 보고서를 받게 됩니다.

Ruby에서 예외가 발생할 때 로컬 및 인스턴스 변수 로깅