Computer >> Máy Tính >  >> Lập trình >> Ruby

Cách hoạt động của trang ngoại lệ ưa thích của Rails

Một trong những điều thú vị khi làm việc với rails là khi có vấn đề gì xảy ra trong quá trình phát triển, bạn sẽ nhận được một trang chi tiết lỗi rất hay. Bạn nhận được một backtrace đẹp, với các phần liên quan đến ứng dụng của bạn được đánh dấu. Bạn có thể xem các thông số đã được đăng, cũng như kiểm tra các biến môi trường và phiên.

Hôm nay chúng ta sẽ xem xét cách hoạt động của các trang lỗi lạ mắt này.

Cách hoạt động của trang ngoại lệ ưa thích của Rails

Bẻ khóa Actionpack đang mở

Tệp mà chúng ta sẽ quan tâm nhất hôm nay là actionpack / lib / action_dispatch / middleware / debug_exceptions.rb. Nó thực hiện hầu hết các công việc nặng nhọc khi hiển thị các trang lỗi chế độ phát triển đó. Nếu bạn tò mò về nguồn gốc của màn hình lỗi chế độ sản xuất, hãy xem public_exceptions.rb.

Phần mềm Trung gian Rack

Nếu bạn không quen thuộc với phần mềm trung gian rack, khái niệm này rất đơn giản. Nó cho phép bạn chặn các yêu cầu HTTP trước khi chúng truy cập vào ứng dụng của bạn và chặn đầu ra của ứng dụng trước khi nó quay trở lại người dùng.

Đây là một phần mềm trung gian đơn giản không làm bất cứ điều gì thú vị.

class MyMiddleware
  def initialize(app)
    @app = app
  end

  def call(env)
    @app.call(env)
  end
end

Giải cứu tất cả các ngoại lệ thông qua phần mềm trung gian của rack

Bất kỳ ngoại lệ nào xảy ra trong ứng dụng của bạn đều xảy ra do gọi @ app.call (). Vì vậy, việc giải cứu tất cả các trường hợp ngoại lệ trong ứng dụng rack cũng đơn giản như việc thêm điều khoản cứu hộ vào phần mềm trung gian.

def call(env)
  @app.call(env)
rescue StandardError => exception
  # this is a method we have to provide to generate the exception page
  render_exception(env, exception)
end

Mọi thứ trả về từ phương thức gọi sẽ được xử lý như thể nó là một trang web bình thường. Vì vậy, nội dung được trả về bởi render_exception sẽ thay thế phản hồi ban đầu.

Hiển thị ngoại lệ

Tôi đã trích dẫn phương thức render_exception từ ActionDispatch ::DebugExceptions. Như bạn có thể thấy, nó chỉ cần lấy dữ liệu có liên quan từ ngoại lệ và đưa dữ liệu đó vào một mẫu ERB.

def render_exception(env, exception)
  wrapper = ExceptionWrapper.new(env, exception)
  log_error(env, wrapper)

  if env['action_dispatch.show_detailed_exceptions']
    request = Request.new(env)
    template = ActionView::Base.new([RESCUES_TEMPLATE_PATH],
      request: request,
      exception: wrapper.exception,
      application_trace: wrapper.application_trace,
      framework_trace: wrapper.framework_trace,
      full_trace: wrapper.full_trace,
      routes_inspector: routes_inspector(exception),
      source_extract: wrapper.source_extract,
      line_number: wrapper.line_number,
      file: wrapper.file
    )
    file = "rescues/#{wrapper.rescue_template}"

    if request.xhr?
      body = template.render(template: file, layout: false, formats: [:text])
      format = "text/plain"
    else
      body = template.render(template: file, layout: 'rescues/layout')
      format = "text/html"
    end
    render(wrapper.status_code, body, format)
  else
    raise exception
  end
end

def render(status, body, format)
  [status, {'Content-Type' => "#{format}; charset=#{Response.default_charset}", 'Content-Length' => body.bytesize.to_s}, [body]]
end

Các mục đích sử dụng khác

Bạn có thể sử dụng thủ thuật phần mềm trung gian rack này để làm rất nhiều điều thú vị với các trường hợp ngoại lệ. Tại Honeybadger, chúng tôi sử dụng nó để chặn lỗi và ghi lại chúng vào API của chúng tôi. Đây là mã chúng tôi sử dụng để làm điều đó:

def call(env)
  config.with_request(::Rack::Request.new(env)) do
    begin
      env['honeybadger.config'] = config
      response = @app.call(env)
    rescue Exception => raised
      env['honeybadger.error_id'] = notify_honeybadger(raised, env)
      raise
    end

    framework_exception = framework_exception(env)
    if framework_exception
      env['honeybadger.error_id'] = notify_honeybadger(framework_exception, env)
    end

    response
  end
ensure
  Honeybadger.context.clear!
end