বেশ বড় অ্যাপ্লিকেশনে এক্সসেপশন নিয়ে কী হচ্ছে বুঝতে কখনও কখনও খুব কঠিন হয়। কল্পনা করুন, আপনি একটি বিদ্যমান প্রজেক্টের কোডে কাজ করছেন। আপনি একটি এক্সসেপশন raise করলেন, তারপর কিছু অদ্ভুত ঘটল। হয়তো এক্সসেপশনটি নিলে যাওয়া হয়েছে। হয়তো একটি এনভায়রনমেন্ট ভেরিয়েবল পরিবর্তন হয়েছে। হয়তো আপনার এক্সসেপশনটি অন্য একটি এক্সসেপশনে র্যাপ হয়ে গেছে।
আমি আপনাকে একটি সহজ পদ্ধতি দেখাবো TracePoint ব্যবহার করে আপনার অ্যাপ্লিকেশনের এক্সসেপশন সম্পর্কে আরও তথ্য পাওয়ার জন্য—এমন এক্সসেপশনগুলোর জন্যও যেগুলো নিলে যাওয়া হয়েছে।
একটি সুবিধাজনক উদাহরণ
রেইলসে কন্ট্রোলার এবং ভিউ-এর মধ্যে সীমা একটি এমন জায়গা যেখানে এক্সসেপশন লজিকের বিপরীত আচরণ করতে দেখে। এটা আপনি সহজেই নিজে দেখতে পারেন। একটি ভিউতে এক্সসেপশন raise করুন এবং কন্ট্রোলার থেকে সেটি রescue করার চেষ্টা করুন। আপনি দেখবেন যে আপনি কন্ট্রোলার থেকে টেমপ্লেট এরর রescue করতে পারছেন না!
# pages_controller.rb
def index
render
rescue # এটি কখনোই চলবে না
logger.debug "someone raised the roof"
end
# index.haml
- raise "the roof"
WTF!?! মনে করতাম আমি এটি রescue করেছি!
স্পষ্ট হয়ে গেলো যে এখানে কিছু ট্রিকি কাজ হচ্ছে। আসুন দেখি কি হচ্ছে তা বুঝতে পারি কিনা।
TracePoint সহ সব এক্সসেপশন লগিং
TracePoint একটি শক্তিশালী ইন্ট্রোস্পেকশন টুল যা রুবি ২.০ থেকে আছে। এটি রানটাইম ইভেন্টের জন্য কলব্যাক সংজ্ঞায়িত করতে দেয়। উদাহরণস্বরূপ, আপনি যখন একটি ক্লাস সংজ্ঞায়িত হয়, একটি মেথড কল হয়, বা একটি এক্সসেপশন raise হয় তখন নোটিফিকেশন পেতে পারেন। আরও ইভেন্টের জন্য TracePoint ডকুমেন্টেশন দেখুন।
আসুন একটি TracePoint যোগ করি যা هرবার এক্সসেপশন raise হলে কল হবে এবং তার সারসংক্ষেপ লগে লিখবে।
class PagesController < ApplicationController
def index
TracePoint.new(:raise) do |tp|
# tp.raised_exeption মূল এক্সসেপশন অবজেক্টটি ধারণ করে!
logger.debug "#{tp.raised_exception.object_id}: #{tp.raised_exception.class} #{tp.raised_exception.message} ".yellow + tp.raised_exception.backtrace[0].sub(Rails.root.to_s, "").blue
end.enable do
render
end
end
end
আপনি যদি yellow এবং blue মেথড সম্পর্কে কৌতুহলী হন, তবে আমি colorize gem ব্যবহার করছি। এটি আউটপুটে ANSI কালার কোড যোগ করে।
এখন যখন আমি পেজ রিফ্রেশ করি, আমার লগগুলো নিচের স্ক্রিনশটের মতো দেখায়। একটি আকর্ষণীয় বিষয় লক্ষ্য করতে পারেন—দুটি আলাদা এক্সসেপশন আছে, এবং প্রতিটি দুবার raise হয়েছে। প্রতিটি লাইনের শুরুতে সেই বড় সংখ্যাটি এক্সসেপশনের object id। এটাই কনফার্ম করে যে চারটি এক্সসেপশন অবজেক্ট নয়, বরং দুটি অবজেক্ট।
এই লগটি রেন্ডারিং প্রসেসে raise এর প্রতিটি ব্যবহার দেখায়
কোন মেথড কোন raise কারণ হয়েছে?
"raise" ইভেন্টের একটি লিস্ট পাওয়া খুব উপকারী। কিন্তু আরও ভালো হতো যদি আমাদের কিছু ধারণা হতো কোন মেথডগুলো কোন raise করছে। একবার আবার TracePoint সাহায্য করতে এসেছে।
TracePoint আমাদের একটি হ্যান্ডলার যোগ করতে দেয় যা كلما একটি মেথড রিটার্ন হয় তখন কল হবে। এটি "raise" ইভেন্টের মতো সহজে ব্যবহার করা যায়। নিচের উদাহরণে আমরা প্রতিটি মেথড রিটার্ন লগ করছি:
TracePoint.trace(:return) do |tp|
logger.debug [tp.method_id, tp.lineno, tp.path.sub(Rails.root.to_s, "")].join(" : ").green
end
একটি সমস্যা আছে। আপনি যদি এই কোডটি আপনার রেইলস অ্যাপে যোগ করেন, তবে আপনি দেখবেন আপনার অ্যাপ রিকোয়েস্ট রেসপন্ড করা বন্ধ করে দিয়েছে। সবচেয়ে সহজ রেইলস রিকোয়েস্টেও এত বেশি মেথড কল হয় যে সার্ভার সেগুলো লগে লিখতে পারার আগে টাইমআউট হয়ে যায়।
যেহেতু আমাদের আসল আগ্রহ শুধু সেই মেথড কলগুলিতে যেগুলো এক্সসেপশন করাচ্ছে, তাই আমাদের কোড মডিফাই করি যাতে প্রতিটি এক্সসেপশনের পর প্রথম দুটি "return" ইভেন্ট আউটপুট হয়।
class PagesController < ApplicationController
def index
counter = 0
return_trace = TracePoint.trace(:return) do |tp|
logger.debug "\t" + [tp.method_id, tp.lineno, tp.path.sub(Rails.root.to_s, "")].join(" : ").green if (counter += 1) > 3
return_trace.disable
counter = 0
end
return_trace.disable # ডিফল্টভাবে tracepoint ডিজেবল
TracePoint.new(:raise) do |tp|
logger.debug "#{tp.raised_exception.object_id}: #{tp.raised_exception.class} #{tp.raised_exception.message} ".yellow + tp.raised_exception.backtrace[0].sub(Rails.root.to_s, "").blue
# "raise" "return" tracepoint ইনেবল করে
return_trace.enable
end.enable do
render
end
end
end
যখন আমি ব্রাউজার রিফ্রেশ করি, লগে নিচের লাইনগুলো যোগ হয়েছে:
প্রতিটি "raise" ইভেন্ট তার কারণবূপ মেথডের উপরে দেখানো হয়েছে
কারণ আমরা "return" TracePoint শুধু এক্সসেপশন raise হলে ইনেবল করি, তাই প্রথম "return" ইভেন্টটি হবে সেই মেথড থেকে যা এক্সসেপশন raise করেছে।
আমরা এই তথ্য ব্যবহার করে আমাদের রহস্য সমাধান করতে পারি। আমাদের মূল RuntimeError হলো ActionView::Template::Error-এ কনভার্ট হয়েছে handle_render_error মেথড দ্বারা template.rb-র লাইন ৩১০-এ।
এই টেকনিকের ভালো দিক হলো এর রেইলসের সাথে কোনো সম্পর্ক নেই। আপনি এটি যেকোনো সময় ব্যবহার করতে পারেন যখন আপনাকে বুঝতে হবে আন্ডার দ হুড কোন এক্সসেপশনগুলো raise এবং catch হচ্ছে।