ข้อดีอย่างหนึ่งของการทำงานกับรางคือเมื่อมีบางอย่างผิดพลาดในการพัฒนา คุณจะได้รับหน้ารายละเอียดข้อผิดพลาดที่ดีมาก คุณได้รับ backtrace ที่ดีโดยเน้นส่วนที่เกี่ยวข้องกับแอพของคุณ คุณสามารถดูพารามิเตอร์ที่โพสต์ รวมถึงตรวจสอบตัวแปรสภาพแวดล้อมและเซสชันได้
วันนี้เราจะมาดูกันว่าหน้าข้อผิดพลาดแฟนซีเหล่านี้ทำงานอย่างไร
การแคร็กเปิด Actionpack
ไฟล์ที่เราจะกล่าวถึงในวันนี้เป็นส่วนใหญ่คือ actionpack/lib/action_dispatch/middleware/debug_exceptions.rb การแสดงหน้าข้อผิดพลาดของโหมดการพัฒนานั้นทำได้ยากที่สุด หากคุณสงสัยว่าหน้าจอข้อผิดพลาดของโหมดใช้งานจริงมาจากไหน โปรดดูที่ public_exceptions.rb
แร็คมิดเดิลแวร์
หากคุณไม่คุ้นเคยกับมิดเดิลแวร์ของแร็ค แนวคิดนี้ก็เรียบง่าย ช่วยให้คุณสามารถสกัดกั้นคำขอ HTTP ก่อนที่พวกเขาจะไปถึงแอปของคุณ และสกัดกั้นเอาต์พุตของแอปก่อนที่จะส่งกลับไปยังผู้ใช้
นี่คือมิดเดิลแวร์ง่ายๆ ที่ไม่ทำอะไรที่น่าสนใจ
class MyMiddleware
def initialize(app)
@app = app
end
def call(env)
@app.call(env)
end
end
การช่วยเหลือข้อยกเว้นทั้งหมดผ่านแร็คมิดเดิลแวร์
ข้อยกเว้นใด ๆ ที่เกิดขึ้นในแอปของคุณเกิดจากการเรียก @app.call() การช่วยเหลือข้อยกเว้นทั้งหมดในแอพแร็คนั้นง่ายพอๆ กับการเพิ่มส่วนคำสั่งช่วยเหลือให้กับมิดเดิลแวร์
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
สิ่งที่ส่งคืนจากวิธีการโทรจะถือว่าเหมือนกับเป็นหน้าเว็บปกติ ดังนั้นเนื้อหาที่ส่งคืนโดย render_exception จะแทนที่การตอบกลับเดิม
การแสดงข้อยกเว้น
ฉันได้ตัดตอนเมธอด render_exception จาก ActionDispatch::DebugExceptions อย่างที่คุณเห็น มันเพียงดึงข้อมูลที่เกี่ยวข้องจากข้อยกเว้นและป้อนลงในเทมเพลต 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
การใช้งานอื่นๆ
คุณสามารถใช้เคล็ดลับมิดเดิลแวร์ชั้นวางนี้เพื่อทำสิ่งที่น่าสนใจมากมายโดยมีข้อยกเว้น ที่ Honeybadger เราใช้เพื่อสกัดกั้นข้อผิดพลาดและบันทึกลงใน API ของเรา นี่คือรหัสที่เราใช้ทำ:
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