Computer >> Hướng Dẫn Máy Tính >  >> Lập Trình >> Ruby

Bên trong Rails: Cách Exception Page tinh tế hoạt động

Điều hay của Rails: khi lỗi ở development, nhận error detail page rất đẹp. Backtrace đẹp, phần liên quan app highlight. Xem params posted, inspect env và session variable.

Hôm nay xem cách fancy error page này hoạt động.

Bên trong Rails: Cách Exception Page tinh tế hoạt động

Mở Actionpack

File chủ yếu quan tâm: `actionpack/lib/action_dispatch/middleware/debug_exceptions.rb`. Nó làm heavy lifting cho development mode error page. Tò mò production error screen, xem `public_exceptions.rb`.

Rack Middleware

Nếu không quen Rack middleware, concept đơn giản: cho intercept HTTP request trước khi đến app, và intercept output app trước khi trả user.

Middleware đơn giản không làm gì thú vị:


class MyMiddleware
  def initialize(app)
    @app = app
  end
  def call(env)
    @app.call(env)
  end
end

Rescue tất cả exception qua Rack Middleware

Exception trong app xảy ra do gọi `@app.call()`. Vì vậy rescue all exception trong Rack app đơn giản: thêm rescue clause vào middleware.


def call(env)
  @app.call(env)
rescue StandardError => exception
  # method này generate exception page
  render_exception(env, exception)
end

Bất kỳ gì return từ call method được treat như normal web page. Nội dung từ `render_exception` replace response gốc.

Render exception

Trích method `render_exception` từ `ActionDispatch::DebugExceptions`. Nhìn thấy nó pull data từ exception feed vào ERB template.


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

Ứng dụng khác

Dùng Rack middleware trick này làm nhiều điều thú vị với exception. Tại Honeybadger, dùng intercept error record lên API. Code dùng:


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