쉽게 재현할 수 없는 버그가 있었나요? 사람들이 귀하의 앱을 한동안 사용한 경우에만 발생하는 것 같습니다. 그리고 오류 메시지와 역추적은 놀라울 정도로 도움이 되지 않습니다.
이럴 때 예외가 발생하기 직전에 앱 상태의 스냅샷을 찍을 수 있다면 정말 편리할 것입니다. 예를 들어 모든 지역 변수와 그 값의 목록을 가질 수 있다면. 음, 할 수 있습니다. 그리 어렵지도 않습니다!
이 게시물에서는 예외 발생 시 현지인을 캡처하는 방법을 보여 드리겠습니다. 하지만 먼저 경고해야 합니다. 이러한 기술 중 어느 것도 프로덕션에 사용해서는 안 됩니다. 스테이징, 사전 제작, 개발 등에서 사용할 수 있습니다. 프로덕션은 아닙니다. 우리가 사용할 보석은 앱 속도를 저하시키는 꽤 무거운 내성 마법에 의존합니다. 최악의 경우... 누가 알겠습니까?
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()
작동 모습은 다음과 같습니다.
연습:인스턴스 변수 기록
지역 변수와 함께 인스턴스 변수를 기록하는 연습으로 남겨두겠습니다. 힌트: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
이제 예외가 발생할 때마다 역추적, 매개변수 등과 함께 모든 로컬에 대한 보고서를 받게 됩니다.