Rails 애플리케이션에서 오류가 발생하면 예외(exception)와 스택 트레이스(stack trace)는 문제가 어디서 발생했는지 파악하는 데 큰 도움이 됩니다. 하지만 무엇이 어디서 일어났는지 아는 것만으로는 부족하며, 그다음 단계는 왜 그런 일이 발생했는지 밝혀내는 것입니다. 이 글에서는 백트레이스를 활용해 Rails 애플리케이션의 버그를 추적하는 방법을 살펴보겠습니다.
NoMethodError (undefined method `request_uri' for #<URI::Generic >):
app/models/product.rb:8:in `download_image!'
app/controllers/products_controller.rb:5:in `create'
위 예시 예외를 보면 undefined method `request_uri' for #<URI::Generic >라는 메시지와 함께 NoMethodError가 발생했습니다. 이 메시지만으로는 문제의 원인을 바로 알기 어렵기 때문에, 스택 트레이스를 자세히 들여다보며 무슨 일이 있었는지 확인해야 합니다.
app/models/product.rb:8:in `download_image!'
app/controllers/products_controller.rb:5:in `create'
스택 트레이스를 보면 예외가 Product 모델의 download_image! 메서드에서 발생했다는 것을 알 수 있습니다. 이제 코드를 직접 열어 스택 트레이스를 따라 내려가면서 무엇이 잘못되었는지 조사해 보겠습니다.
모델 파일을 열어보면 예외가 발생한 8번째 줄에서 Net::HTTP.get(uri)을 호출하고 있습니다. 즉, uri 변수가 우리가 기대하는 객체가 아니라는 뜻입니다.
require 'net/http'
class Product < ApplicationRecord
after_save :download_image!
def download_image!
uri = URI(image_url)
contents = Net::HTTP.get(uri)
File.open("public#{local_image_path}", 'wb') do |file|
file.write contents
end
end
def local_image_path
"/product_#{id}.png"
end
enddownload_image! 메서드는 after_save 콜백이므로, 새 Product가 저장된 직후에 실행된다는 사실도 알 수 있습니다.
uri 변수는 7번째 줄에서 image_url이라는 메서드로부터 만들어집니다. 이 값이 어디서 왔는지 확인하기 위해 다시 스택 트레이스를 살펴보면, ProductsController#create에서 저장 작업이 호출되었음을 알 수 있습니다.
class ProductsController < ApplicationController
def create
@product = Product.new(product_params)
if @product.save
redirect_to @product, notice: 'Product was successfully created.'
else
render :new
end
end
private
def product_params
params.require(:product).permit(:title, :description, :image_url, :price)
end
end원인을 찾았습니다! ProductsController#create는 product_params로 새 상품을 생성하며, 여기에는 우리가 찾던 :image_url 파라미터가 포함되어 있습니다.
즉, image_url 속성이 문제의 URI를 만드는 데 사용된 것입니다. 새 상품을 생성할 때 image_url 필드를 비워 두면 문제를 손쉽게 재현할 수 있습니다.
이 경우 빈 문자열로 URI 객체를 생성하면 URL 형식을 판별할 수 없어 URI::HTTP가 아닌 URI::Generic 객체가 반환됩니다. URI::Generic에는 #request_uri 메서드가 없기 때문에 Net::HTTP.get 호출 시점에 NoMethodError가 발생하는 것입니다.
프로젝트의 요구 사항에 따라 다르겠지만, 필드가 비어 있지 않은지 검증(validation)을 추가하면 이 문제를 해결할 수 있습니다. 물론 이미지 URL이 비어 있는지만 검사해서는 이 구현의 모든 잠재적 문제가 해결되지 않습니다(예를 들어 전달된 값이 URL 형식이 아닌 경우에도 여전히 예외가 발생합니다). 그래도 충분히 좋은 출발점이 될 것입니다.
스택 트레이스로 예외 추적하기
Rails의 로그는 문제를 디버깅하는 데 매우 유용한 도구입니다. 발생한 예외 메시지가 언뜻 이해되지 않는 경우가 많지만, 코드가 문제 지점까지 거쳐 온 단계를 차근차근 역추적해 보면, 문제의 원인이 애플리케이션 깊숙한 곳에 숨어 있더라도 대체로 무엇이 잘못되었는지 찾아낼 수 있습니다.
이 글이 도움이 되었는지, 궁금한 점이 있는지, 그리고 다음에 어떤 주제를 다루길 원하는지 @AppSignal을 통해 알려주세요.