edge
더 많은 정보: rubyonrails.org에서 확인하세요: 더 많은 Ruby on Rails

Rails 애플리케이션 디버깅

이 가이드는 Ruby on Rails 애플리케이션을 디버깅하는 기술을 소개합니다.

이 가이드를 읽은 후에는 다음을 알게 됩니다:

1 디버깅을 위한 뷰 헬퍼

하나의 일반적인 작업은 변수의 내용을 검사하는 것입니다. Rails는 이를 수행하기 위해 세 가지 다른 방법을 제공합니다:

  • debug
  • to_yaml
  • inspect

1.1 debug

debug 헬퍼는 YAML 형식을 사용하여 객체를 렌더링하는 <pre> 태그를 반환합니다. 이를 통해 어떤 객체에서도 사람이 읽을 수 있는 데이터를 생성할 수 있습니다. 예를 들어, 다음과 같은 코드가 뷰에 있다면:

<%= debug @article %>
<p>
  <b>Title:</b>
  <%= @article.title %>
</p>

다음과 같은 결과를 볼 수 있습니다:

--- !ruby/object Article
attributes:
  updated_at: 2008-09-05 22:55:47
  body: It's a very helpful guide for debugging your Rails app.
  title: Rails debugging guide
  published: t
  id: "1"
  created_at: 2008-09-05 22:55:47
attributes_cache: {}


Title: Rails debugging guide

1.2 to_yaml

대신, 어떤 객체에 대해 to_yaml을 호출하면 YAML로 변환됩니다. 이 변환된 객체를 simple_format 헬퍼 메서드에 전달하여 출력을 형식화할 수 있습니다. 이것이 debug가 마법을 부리는 방법입니다.

<%= simple_format @article.to_yaml %>
<p>
  <b>Title:</b>
  <%= @article.title %>
</p>

위의 코드는 다음과 같이 렌더링됩니다:

--- !ruby/object Article
attributes:
updated_at: 2008-09-05 22:55:47
body: It's a very helpful guide for debugging your Rails app.
title: Rails debugging guide
published: t
id: "1"
created_at: 2008-09-05 22:55:47
attributes_cache: {}

Title: Rails debugging guide

1.3 inspect

객체 값을 표시하는 데 유용한 다른 메서드는 inspect입니다. 특히 배열이나 해시와 함께 작업할 때 유용합니다. 이는 객체 값을 문자열로 출력합니다. 예를 들어:

<%= [1, 2, 3, 4, 5].inspect %>
<p>
  <b>Title:</b>
  <%= @article.title %>
</p>

다음과 같이 렌더링됩니다:

[1, 2, 3, 4, 5]

Title: Rails debugging guide

2 로거

런타임 중에 정보를 로그 파일에 저장하는 것도 유용할 수 있습니다. Rails는 각 런타임 환경에 대해 별도의 로그 파일을 유지합니다.

2.1 로거란?

Rails는 로그 정보를 작성하기 위해 ActiveSupport::Logger 클래스를 사용합니다. Log4r와 같은 다른 로거도 대체될 수 있습니다.

config/application.rb 또는 다른 환경 파일에서 대체 로거를 지정할 수 있습니다. 예를 들어:

config.logger = Logger.new(STDOUT)
config.logger = Log4r::Logger.new("Application Log")

또는 Initializer 섹션에 다음 중 아무거나 추가하세요.

Rails.logger = Logger.new(STDOUT)
Rails.logger = Log4r::Logger.new("Application Log")

팁: 기본적으로 각 로그는 Rails.root/log/ 아래에 생성되며, 로그 파일은 애플리케이션이 실행되는 환경에 따라 이름이 지정됩니다.

2.2 로그 레벨

로그에 기록될 때, 메시지의 로그 레벨이 구성된 로그 레벨보다 같거나 높은 경우 해당 로그에 출력됩니다. 현재 로그 레벨을 알고 싶다면 Rails.logger.level 메서드를 호출할 수 있습니다.

사용 가능한 로그 레벨은 :debug, :info, :warn, :error, :fatal, :unknown이며, 각각 0부터 5까지의 로그 레벨 번호에 해당합니다. 기본 로그 레벨을 변경하려면 Rails.logger.level을 사용하세요. ```ruby config.log_level = :warn # 어떤 환경 초기화 파일에서든지 또는 Rails.logger.level = 0 # 언제든지

이는 개발 또는 스테이징에서 로그를 기록하고, 불필요한 정보로 생산 로그를 넘치게 하지 않고자 할 때 유용합니다.

팁: 기본 Rails 로그 레벨은 :debug입니다. 그러나 기본 생성된 config/environments/production.rb에서 production 환경에 대해 :info로 설정되어 있습니다.

2.3 메시지 보내기

현재 로그에 쓰려면 컨트롤러, 모델 또는 메일러 내에서 logger.(debug|info|warn|error|fatal|unknown) 메소드를 사용하세요:

logger.debug "Person attributes hash: #{@person.attributes.inspect}"
logger.info "요청 처리 중..."
logger.fatal "응용 프로그램 종료, 복구할 수 없는 오류 발생!!!"

여기에 추가 로깅이 적용된 메소드의 예제가 있습니다:

class ArticlesController < ApplicationController
  # ...

  def create
    @article = Article.new(article_params)
    logger.debug "새로운 글: #{@article.attributes.inspect}"
    logger.debug "글은 유효해야 함: #{@article.valid?}"

    if @article.save
      logger.debug "글이 저장되었으며 사용자가 리디렉션됩니다..."
      redirect_to @article, notice: '글이 성공적으로 생성되었습니다.'
    else
      render :new, status: :unprocessable_entity
    end
  end

  # ...

  private
    def article_params
      params.require(:article).permit(:title, :body, :published)
    end
end

이 컨트롤러 액션이 실행될 때 생성되는 로그의 예제입니다:

Started POST "/articles" for 127.0.0.1 at 2018-10-18 20:09:23 -0400
Processing by ArticlesController#create as HTML
  Parameters: {"utf8"=>"✓", "authenticity_token"=>"XLveDrKzF1SwaiNRPTaMtkrsTzedtebPPkmxEFIU0ordLjICSnXsSNfrdMa4ccyBjuGwnnEiQhEoMN6H1Gtz3A==", "article"=>{"title"=>"Debugging Rails", "body"=>"I'm learning how to print in logs.", "published"=>"0"}, "commit"=>"Create Article"}
새로운 글: {"id"=>nil, "title"=>"Debugging Rails", "body"=>"I'm learning how to print in logs.", "published"=>false, "created_at"=>nil, "updated_at"=>nil}
글은 유효해야 함: true
   (0.0ms)  begin transaction
  ↳ app/controllers/articles_controller.rb:31
  Article Create (0.5ms)  INSERT INTO "articles" ("title", "body", "published", "created_at", "updated_at") VALUES (?, ?, ?, ?, ?)  [["title", "Debugging Rails"], ["body", "I'm learning how to print in logs."], ["published", 0], ["created_at", "2018-10-19 00:09:23.216549"], ["updated_at", "2018-10-19 00:09:23.216549"]]
  ↳ app/controllers/articles_controller.rb:31
   (2.3ms)  commit transaction
  ↳ app/controllers/articles_controller.rb:31
글이 저장되었으며 사용자가 리디렉션됩니다...
Redirected to http://localhost:3000/articles/1
Completed 302 Found in 4ms (ActiveRecord: 0.8ms)

이와 같이 추가 로깅을 추가하면 로그에서 예상치 못한 또는 이상한 동작을 쉽게 찾을 수 있습니다. 추가 로깅을 추가할 때 로그 레벨을 합리적으로 사용하여 생산 로그를 쓸모없는 정보로 채우지 않도록 주의하세요.

2.4 자세한 쿼리 로그

로그에서 데이터베이스 쿼리 출력을 볼 때, 단일 메소드 호출 시 여러 개의 데이터베이스 쿼리가 왜 트리거되는지 즉시 알기 어려울 수 있습니다:

irb(main):001:0> Article.pamplemousse
  Article Load (0.4ms)  SELECT "articles".* FROM "articles"
  Comment Load (0.2ms)  SELECT "comments".* FROM "comments" WHERE "comments"."article_id" = ?  [["article_id", 1]]
  Comment Load (0.1ms)  SELECT "comments".* FROM "comments" WHERE "comments"."article_id" = ?  [["article_id", 2]]
  Comment Load (0.1ms)  SELECT "comments".* FROM "comments" WHERE "comments"."article_id" = ?  [["article_id", 3]]
=> #<Comment id: 2, author: "1", body: "Well, actually...", article_id: 1, created_at: "2018-10-19 00:56:10", updated_at: "2018-10-19 00:56:10">

bin/rails console 세션에서 ActiveRecord.verbose_query_logs = true를 실행하여 자세한 쿼리 로그를 활성화하고 메소드를 다시 실행하면, 이러한 개별적인 데이터베이스 호출을 생성하는 단일 코드 라인이 명확해집니다:

irb(main):003:0> Article.pamplemousse
  Article Load (0.2ms)  SELECT "articles".* FROM "articles"
  ↳ app/models/article.rb:5
  Comment Load (0.1ms)  SELECT "comments".* FROM "comments" WHERE "comments"."article_id" = ?  [["article_id", 1]]
  ↳ app/models/article.rb:6
  Comment Load (0.1ms)  SELECT "comments".* FROM "comments" WHERE "comments"."article_id" = ?  [["article_id", 2]]
  ↳ app/models/article.rb:6
  Comment Load (0.1ms)  SELECT "comments".* FROM "comments" WHERE "comments"."article_id" = ?  [["article_id", 3]]
  ↳ app/models/article.rb:6
=> #<Comment id: 2, author: "1", body: "Well, actually...", article_id: 1, created_at: "2018-10-19 00:56:10", updated_at: "2018-10-19 00:56:10">

각 데이터베이스 문 아래에는 데이터베이스 호출로 이어지는 메서드의 소스 파일 이름(및 라인 번호)을 가리키는 화살표가 표시됩니다. 이를 통해 N+1 쿼리로 인해 발생하는 성능 문제를 식별하고 해결할 수 있습니다. N+1 쿼리는 여러 추가 쿼리를 생성하는 단일 데이터베이스 쿼리입니다.

Rails 5.2 이후 개발 환경 로그에서는 자세한 쿼리 로그가 기본적으로 활성화됩니다.

경고: 운영 환경에서 이 설정을 사용하는 것을 권장하지 않습니다. 이 설정은 루비의 Kernel#caller 메서드에 의존하며, 메서드 호출의 스택 추적을 생성하기 위해 많은 메모리를 할당하는 경향이 있습니다. 대신 쿼리 로그 태그를 사용하세요(아래 참조).

2.5 자세한 Enqueue 로그

위의 "자세한 쿼리 로그"와 유사하게, 백그라운드 작업을 예약하는 메서드의 소스 위치를 출력할 수 있습니다.

개발 환경에서 기본적으로 활성화되어 있습니다. 다른 환경에서 활성화하려면 application.rb 또는 환경 초기화 파일에 다음을 추가하세요:

config.active_job.verbose_enqueue_logs = true

자세한 쿼리 로그와 마찬가지로, 운영 환경에서는 권장하지 않습니다.

3 SQL 쿼리 주석

SQL 문은 컨트롤러 또는 작업의 이름과 같은 런타임 정보를 포함하는 태그로 주석 처리할 수 있습니다. 이를 통해 느린 쿼리를 기록할 때(예: MySQL, PostgreSQL), 현재 실행 중인 쿼리를 확인할 때 또는 엔드 투 엔드 추적 도구에 유용합니다.

활성화하려면 application.rb 또는 환경 초기화 파일에 다음을 추가하세요:

config.active_record.query_log_tags_enabled = true

기본적으로 응용 프로그램의 이름, 컨트롤러의 이름과 액션 또는 작업의 이름이 로그에 기록됩니다. 기본 형식은 SQLCommenter입니다. 예를 들면 다음과 같습니다:

Article Load (0.2ms)  SELECT "articles".* FROM "articles" /*application='Blog',controller='articles',action='index'*/

Article Update (0.3ms)  UPDATE "articles" SET "title" = ?, "updated_at" = ? WHERE "posts"."id" = ? /*application='Blog',job='ImproveTitleJob'*/  [["title", "Improved Rails debugging guide"], ["updated_at", "2022-10-16 20:25:40.091371"], ["id", 1]]

ActiveRecord::QueryLogs의 동작은 SQL 쿼리와 연결하는 데 도움이 되는 모든 것을 포함하도록 수정할 수 있습니다. 예를 들어 응용 프로그램 로그의 요청 및 작업 ID, 계정 및 테넌트 식별자 등입니다.

3.1 태그된 로깅

다중 사용자, 다중 계정 애플리케이션을 실행할 때 로그를 사용자 정의 규칙을 사용하여 필터링하는 것이 유용합니다. Active Support의 TaggedLogging을 사용하면 하위 도메인, 요청 ID 및 디버깅에 도움이 되는 기타 사항을 로그 라인에 표시하여 이러한 애플리케이션의 디버깅을 지원할 수 있습니다.

logger = ActiveSupport::TaggedLogging.new(Logger.new(STDOUT))
logger.tagged("BCX") { logger.info "Stuff" }                            # "[BCX] Stuff" 로그
logger.tagged("BCX", "Jason") { logger.info "Stuff" }                   # "[BCX] [Jason] Stuff" 로그
logger.tagged("BCX") { logger.tagged("Jason") { logger.info "Stuff" } } # "[BCX] [Jason] Stuff" 로그

3.2 로그의 성능에 미치는 영향

로그 기록은 항상 Rails 애플리케이션의 성능에 약간의 영향을 미칩니다. 특히 디스크에 로그를 기록할 때 그 영향이 커집니다. 또한 몇 가지 주의사항이 있습니다:

:debug 레벨을 사용하는 것은 :fatal보다 성능에 더 큰 영향을 미칩니다. 왜냐하면 더 많은 문자열이 평가되고 로그 출력(예: 디스크)에 기록되기 때문입니다.

코드에서 Logger에 대한 호출이 너무 많은 경우 또 다른 잠재적인 문제가 될 수 있습니다:

logger.debug "Person attributes hash: #{@person.attributes.inspect}"

위의 예에서는 허용된 출력 레벨에 디버그가 포함되어 있지 않아도 성능에 영향을 줄 수 있습니다. 그 이유는 루비가 이러한 문자열을 평가해야 하기 때문입니다. 이 과정에는 상당히 무거운 String 객체의 인스턴스화와 변수의 보간이 포함됩니다. 따라서, 로거 메서드에 블록을 전달하는 것이 권장됩니다. 이는 출력 레벨이 허용된 레벨과 동일하거나 포함되어 있는 경우에만 평가되기 때문에 (즉, 지연 로딩), 성능 절약을 위해 블록을 전달하는 것이 좋습니다. 같은 코드를 다시 작성하면 다음과 같습니다:

logger.debug { "Person attributes hash: #{@person.attributes.inspect}" }

블록의 내용 및 따라서 문자열 보간은 디버그가 활성화된 경우에만 평가됩니다. 이러한 성능 절약은 로깅이 많은 양으로 이루어진 경우에만 실제로 눈에 띄지만, 이는 좋은 관행입니다.

이 섹션은 Jon Cairns의 스택 오버플로우 답변에 의해 작성되었으며 cc by-sa 4.0로 라이선스가 부여되었습니다.

4 debug 젬을 사용한 디버깅

코드가 예상치 못한 방식으로 동작할 때, 문제를 진단하기 위해 로그나 콘솔에 출력해 볼 수 있습니다. 그러나 때로는 이러한 오류 추적이 문제의 근본 원인을 찾는 데 효과적이지 않을 때도 있습니다. 실제로 실행 중인 소스 코드로 진입해야 하는 경우 디버거가 가장 좋은 동반자입니다.

디버거는 또한 Rails 소스 코드에 대해 배우고 싶지만 어디서부터 시작해야 할지 모를 때도 도움이 될 수 있습니다. 애플리케이션에 대한 요청을 디버그하고 이 가이드를 사용하여 작성한 코드에서 기본적인 Rails 코드로 이동하는 방법을 배울 수 있습니다.

Rails 7은 CRuby로 생성된 새로운 애플리케이션의 Gemfiledebug 젬을 포함하고 있습니다. 기본적으로 developmenttest 환경에서 사용할 수 있습니다. 사용법에 대해서는 문서를 확인하십시오.

4.1 디버깅 세션 진입

기본적으로 디버깅 세션은 debug 라이브러리가 필요한 경우, 즉 앱이 부팅될 때 시작됩니다. 하지만 걱정하지 마세요, 세션은 애플리케이션에 영향을 미치지 않습니다.

디버깅 세션에 진입하려면 binding.break 및 그 별칭인 binding.bdebugger를 사용할 수 있습니다. 다음 예제에서는 debugger를 사용합니다:

class PostsController < ApplicationController
  before_action :set_post, only: %i[ show edit update destroy ]

  # GET /posts or /posts.json
  def index
    @posts = Post.all
    debugger
  end
  # ...
end

앱이 디버깅 문을 평가하면 디버깅 세션에 진입합니다:

Processing by PostsController#index as HTML
[2, 11] in ~/projects/rails-guide-example/app/controllers/posts_controller.rb
     2|   before_action :set_post, only: %i[ show edit update destroy ]
     3|
     4|   # GET /posts or /posts.json
     5|   def index
     6|     @posts = Post.all
=>   7|     debugger
     8|   end
     9|
    10|   # GET /posts/1 or /posts/1.json
    11|   def show
=>#0    PostsController#index at ~/projects/rails-guide-example/app/controllers/posts_controller.rb:7
  #1    ActionController::BasicImplicitRender#send_action(method="index", args=[]) at ~/.rbenv/versions/3.0.1/lib/ruby/gems/3.0.0/gems/actionpack-7.1.0.alpha/lib/action_controller/metal/basic_implicit_render.rb:6
  # and 72 frames (use `bt' command for all frames)
(rdbg)

디버깅 세션을 언제든지 종료하고 continue (또는 c) 명령을 사용하여 애플리케이션 실행을 계속할 수 있습니다. 또는 디버깅 세션과 애플리케이션 모두를 종료하려면 quit (또는 q) 명령을 사용하십시오.

4.2 컨텍스트

디버깅 세션에 진입한 후에는 Rails 콘솔이나 IRB에 있는 것처럼 Ruby 코드를 입력할 수 있습니다.

(rdbg) @posts    # ruby
[]
(rdbg) self
#<PostsController:0x0000000000aeb0>
(rdbg)

변수 이름이 디버거 명령과 충돌하는 경우 유용한 p 또는 pp 명령을 사용하여 Ruby 표현식을 평가할 수도 있습니다. rb (rdbg) p headers # 명령어 => {"X-Frame-Options"=>"SAMEORIGIN", "X-XSS-Protection"=>"1; mode=block", "X-Content-Type-Options"=>"nosniff", "X-Download-Options"=>"noopen", "X-Permitted-Cross-Domain-Policies"=>"none", "Referrer-Policy"=>"strict-origin-when-cross-origin"} (rdbg) pp headers # 명령어 {"X-Frame-Options"=>"SAMEORIGIN", "X-XSS-Protection"=>"1; mode=block", "X-Content-Type-Options"=>"nosniff", "X-Download-Options"=>"noopen", "X-Permitted-Cross-Domain-Policies"=>"none", "Referrer-Policy"=>"strict-origin-when-cross-origin"} (rdbg)

직접 평가하는 것 외에도 디버거는 다양한 명령어를 통해 다양한 정보를 수집하는 데 도움이 됩니다. 예를 들어 다음과 같은 명령어가 있습니다.

  • info (또는 i) - 현재 프레임에 대한 정보를 제공합니다.
  • backtrace (또는 bt) - 추가 정보와 함께 백트레이스를 표시합니다.
  • outline (또는 o, ls) - 현재 스코프에서 사용 가능한 메서드, 상수, 로컬 변수 및 인스턴스 변수를 나열합니다.

4.2.1 info 명령어

info는 현재 프레임에서 볼 수 있는 로컬 및 인스턴스 변수의 값을 개요 형식으로 제공합니다.

(rdbg) info    # 명령어
%self = #<PostsController:0x0000000000af78>
@_action_has_layout = true
@_action_name = "index"
@_config = {}
@_lookup_context = #<ActionView::LookupContext:0x00007fd91a037e38 @details_key=nil, @digest_cache=...
@_request = #<ActionDispatch::Request GET "http://localhost:3000/posts" for 127.0.0.1>
@_response = #<ActionDispatch::Response:0x00007fd91a03ea08 @mon_data=#<Monitor:0x00007fd91a03e8c8>...
@_response_body = nil
@_routes = nil
@marked_for_same_origin_verification = true
@posts = []
@rendered_format = nil

4.2.2 backtrace 명령어

옵션 없이 사용하면 backtrace는 스택의 모든 프레임을 나열합니다.

=>#0    PostsController#index at ~/projects/rails-guide-example/app/controllers/posts_controller.rb:7
  #1    ActionController::BasicImplicitRender#send_action(method="index", args=[]) at ~/.rbenv/versions/3.0.1/lib/ruby/gems/3.0.0/gems/actionpack-7.1.0.alpha/lib/action_controller/metal/basic_implicit_render.rb:6
  #2    AbstractController::Base#process_action(method_name="index", args=[]) at ~/.rbenv/versions/3.0.1/lib/ruby/gems/3.0.0/gems/actionpack-7.1.0.alpha/lib/abstract_controller/base.rb:214
  #3    ActionController::Rendering#process_action(#arg_rest=nil) at ~/.rbenv/versions/3.0.1/lib/ruby/gems/3.0.0/gems/actionpack-7.1.0.alpha/lib/action_controller/metal/rendering.rb:53
  #4    block in process_action at ~/.rbenv/versions/3.0.1/lib/ruby/gems/3.0.0/gems/actionpack-7.1.0.alpha/lib/abstract_controller/callbacks.rb:221
  #5    block in run_callbacks at ~/.rbenv/versions/3.0.1/lib/ruby/gems/3.0.0/gems/activesupport-7.1.0.alpha/lib/active_support/callbacks.rb:118
  #6    ActionText::Rendering::ClassMethods#with_renderer(renderer=#<PostsController:0x0000000000af78>) at ~/.rbenv/versions/3.0.1/lib/ruby/gems/3.0.0/gems/actiontext-7.1.0.alpha/lib/action_text/rendering.rb:20
  #7    block {|controller=#<PostsController:0x0000000000af78>, action=#<Proc:0x00007fd91985f1c0 /Users/st0012/...|} in <class:Engine> (4 levels) at ~/.rbenv/versions/3.0.1/lib/ruby/gems/3.0.0/gems/actiontext-7.1.0.alpha/lib/action_text/engine.rb:69
  #8    [C] BasicObject#instance_exec at ~/.rbenv/versions/3.0.1/lib/ruby/gems/3.0.0/gems/activesupport-7.1.0.alpha/lib/active_support/callbacks.rb:127
  ..... 그리고  많음

각 프레임에는 다음이 포함됩니다.

  • 프레임 식별자
  • 호출 위치
  • 추가 정보 (예: 블록 또는 메서드 인수)

이를 통해 앱에서 무슨 일이 일어나고 있는지에 대한 좋은 감을 얻을 수 있습니다. 그러나 아마도 다음 사항을 알아채게 될 것입니다.

  • 프레임이 너무 많습니다 (일반적으로 Rails 앱에서는 50개 이상).
  • 대부분의 프레임은 Rails 또는 사용하는 다른 라이브러리에서 가져온 것입니다.

backtrace 명령에는 프레임을 필터링하는 데 도움이 되는 2가지 옵션이 있습니다.

  • backtrace [num] - num개의 프레임만 표시합니다. 예: backtrace 10.
  • backtrace /pattern/ - 식별자 또는 위치가 패턴과 일치하는 프레임만 표시합니다. 예: backtrace /MyModel/.

이러한 옵션을 함께 사용하는 것도 가능합니다. backtrace [num] /pattern/입니다.

4.2.3 outline 명령어

outlinepryirbls 명령어와 유사합니다. 현재 스코프에서 액세스할 수 있는 내용을 보여줍니다. 다음을 포함합니다.

  • 로컬 변수
  • 인스턴스 변수
  • 클래스 변수
  • 메서드 및 소스
ActiveSupport::Configurable#methods: config
AbstractController::Base#methods:
  action_methods  action_name  action_name=  available_action?  controller_path  inspect
  response_body
ActionController::Metal#methods:
  content_type       content_type=  controller_name  dispatch          headers
  location           location=      media_type       middleware_stack  middleware_stack=
  middleware_stack?  performed?     request          request=          reset_session
  response           response=      response_body=   response_code     session
  set_request!       set_response!  status           status=           to_a
ActionView::ViewPaths#methods:
  _prefixes  any_templates?  append_view_path   details_for_lookup  formats     formats=  locale
  locale=    lookup_context  prepend_view_path  template_exists?    view_paths
AbstractController::Rendering#methods: view_assigns

# .....

PostsController#methods: create  destroy  edit  index  new  show  update
instance variables:
  @_action_has_layout  @_action_name    @_config  @_lookup_context                      @_request
  @_response           @_response_body  @_routes  @marked_for_same_origin_verification  @posts
  @rendered_format
class variables: @@raise_on_missing_translations  @@raise_on_open_redirects

4.3 중단점

디버거에서 중단점을 삽입하고 트리거하는 여러 가지 방법이 있습니다. 코드에 직접 디버깅 문(예: debugger)을 추가하는 것 외에도 다음 명령어로 중단점을 삽입할 수도 있습니다.

  • break (또는 b)
    • break - 모든 중단점을 나열합니다.
    • break <num> - 현재 파일의 num 줄에 중단점을 설정합니다.
    • break <file:num> - filenum 줄에 중단점을 설정합니다.
    • break <Class#method> 또는 break <Class.method> - Class#method 또는 Class.method에 중단점을 설정합니다.
    • break <expr>.<method> - <expr> 결과의 <method> 메서드에 중단점을 설정합니다.
  • catch <Exception> - Exception이 발생할 때 중단점을 설정합니다.
  • watch <@ivar> - 현재 객체의 @ivar의 결과가 변경될 때 중단점을 설정합니다 (느립니다). 그리고 그것들을 제거하려면 다음을 사용할 수 있습니다:

  • delete (또는 del)

    • delete - 모든 중단점 삭제
    • delete <num> - id가 num인 중단점 삭제

4.3.1 break 명령어

지정된 줄 번호에 중단점 설정 - 예: b 28

[20, 29] in ~/projects/rails-guide-example/app/controllers/posts_controller.rb
    20|   end
    21|
    22|   # POST /posts or /posts.json
    23|   def create
    24|     @post = Post.new(post_params)
=>  25|     debugger
    26|
    27|     respond_to do |format|
    28|       if @post.save
    29|         format.html { redirect_to @post, notice: "Post was successfully created." }
=>#0    PostsController#create at ~/projects/rails-guide-example/app/controllers/posts_controller.rb:25
  #1    ActionController::BasicImplicitRender#send_action(method="create", args=[]) at ~/.rbenv/versions/3.0.1/lib/ruby/gems/3.0.0/gems/actionpack-7.0.0.alpha2/lib/action_controller/metal/basic_implicit_render.rb:6
  # 그리고 72개의 프레임 (`bt` 명령어를 사용하여 모든 프레임 표시)
(rdbg) b 28    # break 명령어
#0  BP - Line  /Users/st0012/projects/rails-guide-example/app/controllers/posts_controller.rb:28 (line)
(rdbg) c    # continue 명령어
[23, 32] in ~/projects/rails-guide-example/app/controllers/posts_controller.rb
    23|   def create
    24|     @post = Post.new(post_params)
    25|     debugger
    26|
    27|     respond_to do |format|
=>  28|       if @post.save
    29|         format.html { redirect_to @post, notice: "Post was successfully created." }
    30|         format.json { render :show, status: :created, location: @post }
    31|       else
    32|         format.html { render :new, status: :unprocessable_entity }
=>#0    block {|format=#<ActionController::MimeResponds::Collec...|} in create at ~/projects/rails-guide-example/app/controllers/posts_controller.rb:28
  #1    ActionController::MimeResponds#respond_to(mimes=[]) at ~/.rbenv/versions/3.0.1/lib/ruby/gems/3.0.0/gems/actionpack-7.0.0.alpha2/lib/action_controller/metal/mime_responds.rb:205
  # 그리고 74개의 프레임 (`bt` 명령어를 사용하여 모든 프레임 표시)

#0  BP - Line  /Users/st0012/projects/rails-guide-example/app/controllers/posts_controller.rb:28 (line)에서 중지

주어진 메서드 호출에 중단점 설정 - 예: b @post.save.

[20, 29] in ~/projects/rails-guide-example/app/controllers/posts_controller.rb
    20|   end
    21|
    22|   # POST /posts or /posts.json
    23|   def create
    24|     @post = Post.new(post_params)
=>  25|     debugger
    26|
    27|     respond_to do |format|
    28|       if @post.save
    29|         format.html { redirect_to @post, notice: "Post was successfully created." }
=>#0    PostsController#create at ~/projects/rails-guide-example/app/controllers/posts_controller.rb:25
  #1    ActionController::BasicImplicitRender#send_action(method="create", args=[]) at ~/.rbenv/versions/3.0.1/lib/ruby/gems/3.0.0/gems/actionpack-7.0.0.alpha2/lib/action_controller/metal/basic_implicit_render.rb:6
  # 그리고 72개의 프레임 (`bt` 명령어를 사용하여 모든 프레임 표시)
(rdbg) b @post.save    # break 명령어
#0  BP - Method  @post.save at /Users/st0012/.rbenv/versions/3.0.1/lib/ruby/gems/3.0.0/gems/activerecord-7.0.0.alpha2/lib/active_record/suppressor.rb:43

(rdbg) c    # continue 명령어
[39, 48] in ~/.rbenv/versions/3.0.1/lib/ruby/gems/3.0.0/gems/activerecord-7.0.0.alpha2/lib/active_record/suppressor.rb
    39|         SuppressorRegistry.suppressed[name] = previous_state
    40|       end
    41|     end
    42|
    43|     def save(**) # :nodoc:
=>  44|       SuppressorRegistry.suppressed[self.class.name] ? true : super
    45|     end
    46|
    47|     def save!(**) # :nodoc:
    48|       SuppressorRegistry.suppressed[self.class.name] ? true : super
=>#0    ActiveRecord::Suppressor#save(#arg_rest=nil) at ~/.rbenv/versions/3.0.1/lib/ruby/gems/3.0.0/gems/activerecord-7.0.0.alpha2/lib/active_record/suppressor.rb:44
  #1    block {|format=#<ActionController::MimeResponds::Collec...|} in create at ~/projects/rails-guide-example/app/controllers/posts_controller.rb:28
  # 그리고 75개의 프레임 (`bt` 명령어를 사용하여 모든 프레임 표시)

#0  BP - Method  @post.save at /Users/st0012/.rbenv/versions/3.0.1/lib/ruby/gems/3.0.0/gems/activerecord-7.0.0.alpha2/lib/active_record/suppressor.rb:43에서 중지

4.3.2 catch 명령어

예외가 발생할 때 중지 - 예: catch ActiveRecord::RecordInvalid.

[20, 29] in ~/projects/rails-guide-example/app/controllers/posts_controller.rb
    20|   end
    21|
    22|   # POST /posts or /posts.json
    23|   def create
    24|     @post = Post.new(post_params)
=>  25|     debugger
    26|
    27|     respond_to do |format|
    28|       if @post.save!
    29|         format.html { redirect_to @post, notice: "Post was successfully created." }
=>#0    PostsController#create at ~/projects/rails-guide-example/app/controllers/posts_controller.rb:25
  #1    ActionController::BasicImplicitRender#send_action(method="create", args=[]) at ~/.rbenv/versions/3.0.1/lib/ruby/gems/3.0.0/gems/actionpack-7.0.0.alpha2/lib/action_controller/metal/basic_implicit_render.rb:6
  # 그리고 72개의 프레임 (`bt` 명령어를 사용하여 모든 프레임 표시)
(rdbg) catch ActiveRecord::RecordInvalid    # 명령어
#1  BP - Catch  "ActiveRecord::RecordInvalid"
(rdbg) c    # continue 명령어
[75, 84] in ~/.rbenv/versions/3.0.1/lib/ruby/gems/3.0.0/gems/activerecord-7.0.0.alpha2/lib/active_record/validations.rb
    75|     def default_validation_context
    76|       new_record? ? :create : :update
    77|     end
    78|
    79|     def raise_validation_error
=>  80|       raise(RecordInvalid.new(self))
    81|     end
    82|
    83|     def perform_validations(options = {})
    84|       options[:validate] == false || valid?(options[:context])
=>#0    ActiveRecord::Validations#raise_validation_error at ~/.rbenv/versions/3.0.1/lib/ruby/gems/3.0.0/gems/activerecord-7.0.0.alpha2/lib/active_record/validations.rb:80
  #1    ActiveRecord::Validations#save!(options={}) at ~/.rbenv/versions/3.0.1/lib/ruby/gems/3.0.0/gems/activerecord-7.0.0.alpha2/lib/active_record/validations.rb:53
  # 그리고 88개의 프레임 (`bt` 명령어를 사용하여 모든 프레임 표시)

#1  BP - Catch  "ActiveRecord::RecordInvalid"에서 중지

4.3.3 watch 명령어

인스턴스 변수가 변경될 때 멈춥니다. 예: watch @_response_body.

[20, 29] in ~/projects/rails-guide-example/app/controllers/posts_controller.rb
    20|   end
    21|
    22|   # POST /posts or /posts.json
    23|   def create
    24|     @post = Post.new(post_params)
=>  25|     debugger
    26|
    27|     respond_to do |format|
    28|       if @post.save!
    29|         format.html { redirect_to @post, notice: "Post was successfully created." }
=>#0    PostsController#create at ~/projects/rails-guide-example/app/controllers/posts_controller.rb:25
  #1    ActionController::BasicImplicitRender#send_action(method="create", args=[]) at ~/.rbenv/versions/3.0.1/lib/ruby/gems/3.0.0/gems/actionpack-7.0.0.alpha2/lib/action_controller/metal/basic_implicit_render.rb:6
  # 그리고 72개의 프레임 (모든 프레임을 보려면 `bt` 명령어 사용)
(rdbg) watch @_response_body    # 명령어
#0  BP - Watch  #<PostsController:0x00007fce69ca5320> @_response_body =
(rdbg) c    # 계속 명령어
[173, 182] in ~/.rbenv/versions/3.0.1/lib/ruby/gems/3.0.0/gems/actionpack-7.0.0.alpha2/lib/action_controller/metal.rb
   173|       body = [body] unless body.nil? || body.respond_to?(:each)
   174|       response.reset_body!
   175|       return unless body
   176|       response.body = body
   177|       super
=> 178|     end
   179|
   180|     # Tests if render or redirect has already happened.
   181|     def performed?
   182|       response_body || response.committed?
=>#0    ActionController::Metal#response_body=(body=["<html><body>You are being <a href=\"ht...) at ~/.rbenv/versions/3.0.1/lib/ruby/gems/3.0.0/gems/actionpack-7.0.0.alpha2/lib/action_controller/metal.rb:178 #=> ["<html><body>You are being <a href=\"ht...
  #1    ActionController::Redirecting#redirect_to(options=#<Post id: 13, title: "qweqwe", content:..., response_options={:allow_other_host=>false}) at ~/.rbenv/versions/3.0.1/lib/ruby/gems/3.0.0/gems/actionpack-7.0.0.alpha2/lib/action_controller/metal/redirecting.rb:74
  # 그리고 82개의 프레임 (모든 프레임을 보려면 `bt` 명령어 사용)

#0  BP - Watch  #<PostsController:0x00007fce69ca5320> @_response_body =  -> ["<html><body>You are being <a href=\"http://localhost:3000/posts/13\">redirected</a>.</body></html>"]
(rdbg)

4.3.4 중단점 옵션

다양한 종류의 중단점 외에도, 더 고급 디버깅 워크플로우를 위해 옵션을 지정할 수도 있습니다. 현재 디버거는 4개의 옵션을 지원합니다:

  • do: <cmd 또는 expr> - 중단점이 트리거될 때 지정된 명령어/표현식을 실행하고 프로그램을 계속합니다:
    • break Foo#bar do: bt - Foo#bar가 호출될 때 스택 프레임을 출력합니다.
  • pre: <cmd 또는 expr> - 중단점이 트리거될 때 멈추기 전에 지정된 명령어/표현식을 실행합니다:
    • break Foo#bar pre: info - Foo#bar가 호출될 때 멈추기 전에 주변 변수를 출력합니다.
  • if: <expr> - 중단점은 <expr>의 결과가 true인 경우에만 멈춥니다:
    • break Post#save if: params[:debug] - params[:debug]가 true인 경우에만 Post#save에서 멈춥니다.
  • path: <path_regexp> - 중단점은 트리거하는 이벤트(예: 메소드 호출)가 지정된 경로에서 발생하는 경우에만 멈춥니다:
    • break Post#save if: app/services/a_service - 메소드 호출이 Ruby 정규식 /app\/services\/a_service/와 일치하는 메소드에서 Post#save에서 멈춥니다.

또한 앞서 언급한 디버그 문에서도 do:, pre:, if:와 같은 첫 3개의 옵션을 사용할 수 있다는 점에 유의해주세요. 예를 들면:

[2, 11] in ~/projects/rails-guide-example/app/controllers/posts_controller.rb
     2|   before_action :set_post, only: %i[ show edit update destroy ]
     3|
     4|   # GET /posts or /posts.json
     5|   def index
     6|     @posts = Post.all
=>   7|     debugger(do: "info")
     8|   end
     9|
    10|   # GET /posts/1 or /posts/1.json
    11|   def show
=>#0    PostsController#index at ~/projects/rails-guide-example/app/controllers/posts_controller.rb:7
  #1    ActionController::BasicImplicitRender#send_action(method="index", args=[]) at ~/.rbenv/versions/3.0.1/lib/ruby/gems/3.0.0/gems/actionpack-7.0.0.alpha2/lib/action_controller/metal/basic_implicit_render.rb:6
  # 그리고 72개의 프레임 (모든 프레임을 보려면 `bt` 명령어 사용)
(rdbg:binding.break) info
%self = #<PostsController:0x00000000017480>
@_action_has_layout = true
@_action_name = "index"
@_config = {}
@_lookup_context = #<ActionView::LookupContext:0x00007fce3ad336b8 @details_key=nil, @digest_cache=...
@_request = #<ActionDispatch::Request GET "http://localhost:3000/posts" for 127.0.0.1>
@_response = #<ActionDispatch::Response:0x00007fce3ad397e8 @mon_data=#<Monitor:0x00007fce3ad396a8>...
@_response_body = nil
@_routes = nil
@marked_for_same_origin_verification = true
@posts = #<ActiveRecord::Relation [#<Post id: 2, title: "qweqwe", content: "qweqwe", created_at: "...
@rendered_format = nil

4.3.5 디버깅 워크플로우 프로그래밍하기

이러한 옵션을 사용하여 다음과 같이 디버깅 워크플로우를 한 줄로 스크립트화할 수 있습니다.

def create
  debugger(do: "catch ActiveRecord::RecordInvalid do: bt 10")
  # ...
end

그런 다음 디버거는 스크립트된 명령을 실행하고 catch 중단점을 삽입합니다.

(rdbg:binding.break) catch ActiveRecord::RecordInvalid do: bt 10
#0  BP - Catch  "ActiveRecord::RecordInvalid"
[75, 84] in ~/.rbenv/versions/3.0.1/lib/ruby/gems/3.0.0/gems/activerecord-7.0.0.alpha2/lib/active_record/validations.rb
    75|     def default_validation_context
    76|       new_record? ? :create : :update
    77|     end
    78|
    79|     def raise_validation_error
=>  80|       raise(RecordInvalid.new(self))
    81|     end
    82|
    83|     def perform_validations(options = {})
    84|       options[:validate] == false || valid?(options[:context])
=>#0    ActiveRecord::Validations#raise_validation_error at ~/.rbenv/versions/3.0.1/lib/ruby/gems/3.0.0/gems/activerecord-7.0.0.alpha2/lib/active_record/validations.rb:80
  #1    ActiveRecord::Validations#save!(options={}) at ~/.rbenv/versions/3.0.1/lib/ruby/gems/3.0.0/gems/activerecord-7.0.0.alpha2/lib/active_record/validations.rb:53
  # 그리고 88개의 프레임 (`bt` 명령을 사용하여 모든 프레임을 확인하세요)

catch 중단점이 트리거되면 스택 프레임이 출력됩니다.

Stop by #0  BP - Catch  "ActiveRecord::RecordInvalid"

(rdbg:catch) bt 10
=>#0    ActiveRecord::Validations#raise_validation_error at ~/.rbenv/versions/3.0.1/lib/ruby/gems/3.0.0/gems/activerecord-7.0.0.alpha2/lib/active_record/validations.rb:80
  #1    ActiveRecord::Validations#save!(options={}) at ~/.rbenv/versions/3.0.1/lib/ruby/gems/3.0.0/gems/activerecord-7.0.0.alpha2/lib/active_record/validations.rb:53
  #2    block in save! at ~/.rbenv/versions/3.0.1/lib/ruby/gems/3.0.0/gems/activerecord-7.0.0.alpha2/lib/active_record/transactions.rb:302

이 기술을 사용하면 반복적인 수동 입력을 피하고 디버깅 경험을 원활하게 만들 수 있습니다.

더 많은 명령어와 구성 옵션은 문서에서 찾을 수 있습니다.

5 web-console 젬을 사용한 디버깅

Web Console은 debug와 비슷하지만 브라우저에서 실행됩니다. 뷰나 컨트롤러의 컨텍스트에서 어떤 페이지에서든 콘솔을 요청할 수 있습니다. 콘솔은 HTML 콘텐츠 옆에 렌더링됩니다.

5.1 콘솔

컨트롤러 액션이나 뷰 내에서 console 메서드를 호출하여 콘솔을 호출할 수 있습니다.

예를 들어, 컨트롤러에서:

class PostsController < ApplicationController
  def new
    console
    @post = Post.new
  end
end

뷰에서:

<% console %>

<h2>New Post</h2>

이렇게 하면 뷰 내에 콘솔이 렌더링됩니다. console 호출의 위치에 대해 신경 쓸 필요가 없습니다. 호출된 위치에 렌더링되지 않고 HTML 콘텐츠 옆에 렌더링됩니다.

콘솔은 순수한 Ruby 코드를 실행합니다. 사용자 정의 클래스를 정의하고 인스턴스를 생성하며 새로운 모델을 만들고 변수를 검사할 수 있습니다.

참고: 한 번에 하나의 콘솔만 렌더링될 수 있습니다. 그렇지 않으면 web-console은 두 번째 console 호출에서 오류를 발생시킵니다.

5.2 변수 검사

instance_variables를 호출하여 컨텍스트에서 사용 가능한 모든 인스턴스 변수를 나열할 수 있습니다. 로컬 변수를 나열하려면 local_variables를 사용할 수 있습니다.

5.3 설정

  • config.web_console.allowed_ips: 허용된 IPv4 또는 IPv6 주소 및 네트워크 목록 (기본값: 127.0.0.1/8, ::1).
  • config.web_console.whiny_requests: 콘솔 렌더링이 방지될 때 메시지를 로그에 기록할지 여부 (기본값: true).

web-console은 서버에서 원격으로 일반 Ruby 코드를 평가하기 때문에 프로덕션 환경에서 사용하지 마십시오.

6 메모리 누수 디버깅

루비 애플리케이션(레일즈 포함)은 루비 코드나 C 코드 수준에서 메모리 누수가 발생할 수 있습니다.

이 섹션에서는 Valgrind와 같은 도구를 사용하여 이러한 누수를 찾고 수정하는 방법을 알아보겠습니다.

6.1 Valgrind

Valgrind는 C 기반 메모리 누수와 경합 조건을 감지하는 애플리케이션입니다.

Valgrind 도구를 사용하면 많은 메모리 관리 및 스레딩 버그를 자동으로 감지하고 프로그램을 자세히 프로파일링할 수 있습니다. 예를 들어, 인터프리터의 C 확장이 malloc()을 호출하지만 free()를 제대로 호출하지 않으면 이 메모리는 앱이 종료될 때까지 사용할 수 없습니다. Valgrind와 Ruby를 설치하고 사용하는 방법에 대한 자세한 정보는 Evan Weaver의 Valgrind and Ruby를 참조하십시오.

6.2 메모리 누수 찾기

Derailed에서 메모리 누수를 감지하고 수정하는 방법에 대한 훌륭한 기사가 있습니다. 여기에서 읽을 수 있습니다.

7 디버깅을 위한 플러그인

일부 Rails 플러그인을 사용하면 오류를 찾고 응용 프로그램을 디버깅하는 데 도움이 됩니다. 디버깅에 유용한 플러그인 목록은 다음과 같습니다:

  • Query Trace 쿼리의 원본을 추적하여 로그에 추가합니다.
  • Exception Notifier Rails 응용 프로그램에서 오류가 발생할 때 이메일 알림을 보내기 위한 메일러 객체와 기본 템플릿을 제공합니다.
  • Better Errors 기존의 Rails 오류 페이지를 소스 코드 및 변수 검사와 같은 상세한 정보를 포함한 새로운 페이지로 대체합니다.
  • RailsPanel 개발.log를 계속 추적하지 않아도 되는 Rails 개발용 Chrome 확장 프로그램입니다. 브라우저에서 Rails 앱 요청에 대한 모든 정보를 개발자 도구 패널에서 확인할 수 있습니다. 데이터베이스/렌더링/전체 시간, 매개변수 목록, 렌더링된 뷰 등을 제공합니다.
  • Pry IRB 대체 및 런타임 개발자 콘솔입니다.

8 참고 자료

피드백

이 가이드의 품질을 개선하는 데 도움을 주시기를 권장합니다.

오타나 사실적인 오류를 발견하면 기여해주십시오. 시작하려면 문서 기여 섹션을 읽어보세요.

불완전한 내용이나 최신 정보가 아닌 내용을 발견할 수도 있습니다. 주요한 부분에 누락된 문서를 추가해주세요. Edge 가이드에서 이미 문제가 해결되었는지 확인하세요. 스타일과 규칙은 Ruby on Rails 가이드 지침을 확인하세요.

수정할 내용을 발견했지만 직접 수정할 수 없는 경우 이슈를 열어주세요.

마지막으로, Ruby on Rails 문서에 관한 모든 토론은 공식 Ruby on Rails 포럼에서 환영합니다.