Created
December 2, 2025 09:38
-
-
Save amkisko/09a50b10e4ea82b0ac835307a940fe60 to your computer and use it in GitHub Desktop.
rspec debug full trace logger including SQL queries and TracePoint events with calls and exceptions from stack
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| RSpec.configure do |config| | |
| config.before do |example| | |
| @spec_file_path = example.metadata[:example_group][:file_path] | |
| @spec_line_number = example.metadata[:example_group][:line_number] | |
| def spec_dirname | |
| File.dirname(@spec_file_path) | |
| end | |
| def spec_basename | |
| File.basename(@spec_file_path) | |
| end | |
| if ENV["DEBUG"] | |
| Rails.logger.level = 0 | |
| puts "Running #{@spec_file_path}:#{@spec_line_number}" | |
| end | |
| end | |
| end | |
| with_sql = ENV["DEBUG_SQL"] | |
| with_stack = ENV["DEBUG_STACK"] | |
| debug_requested = with_sql || with_stack | |
| if debug_requested | |
| require "amazing_print" | |
| def pp(*, **) = ENV["PRETTY"] ? ap(*, **) : puts(*, **) | |
| RSpec.configure do |config| | |
| config.around do |example| | |
| if with_sql | |
| subscriber = ActiveSupport::Notifications.subscribe("sql.active_record") do |event| | |
| payload = event.payload[:sql] | |
| # next if payload.match?(/^(SELECT|SET|SHOW|BEGIN|COMMIT|ROLLBACK|RELEASE|SAVEPOINT)/) | |
| # next if payload.include?("audits") | |
| next if payload.include?("pg_attribute") | |
| type_casted_binds = if event.payload[:type_casted_binds].is_a?(Array) | |
| event.payload[:type_casted_binds] | |
| elsif event.payload[:type_casted_binds].is_a?(Proc) | |
| event.payload[:type_casted_binds].call | |
| else | |
| [] | |
| end | |
| if type_casted_binds.is_a?(Array) | |
| type_casted_binds.each_with_index.reverse_each do |bind, index| | |
| payload = payload.gsub("$#{index + 1}", "'#{bind}'") | |
| end | |
| end | |
| pp(event: "sql.active_record", sql: payload) | |
| end | |
| end | |
| if with_stack | |
| trace = TracePoint.new do |tp| | |
| next if !tp.path&.start_with?(Rails.root.to_s) | |
| next if tp.path&.include?(Rails.root.join("spec/support").to_s) | |
| if tp.event == :call | |
| method_name = tp.method_id | |
| defined_class = tp.defined_class | |
| args = [] | |
| kwargs = {} | |
| begin | |
| # Try to get method object - handle both instance and class methods | |
| method_obj = begin | |
| if defined_class.singleton_class? | |
| defined_class.method(method_name) | |
| else | |
| tp.self.method(method_name) | |
| end | |
| rescue | |
| # Fallback: try instance method | |
| begin | |
| defined_class.instance_method(method_name) | |
| rescue | |
| nil | |
| end | |
| end | |
| if method_obj&.respond_to?(:parameters) | |
| params = method_obj.parameters | |
| binding = tp.binding | |
| params.each do |param_type, param_name| | |
| next unless binding.local_variable_defined?(param_name) | |
| value = binding.local_variable_get(param_name) | |
| case param_type | |
| when :req, :opt | |
| args << value | |
| when :key, :keyreq | |
| kwargs[param_name] = value | |
| when :keyrest | |
| kwargs.merge!(value || {}) | |
| when :rest | |
| args.concat(value || []) | |
| end | |
| end | |
| end | |
| rescue | |
| # If we can't extract arguments, just continue without them | |
| end | |
| # Format method display: use . for class methods, # for instance methods | |
| method_display = if defined_class.singleton_class? | |
| "#{defined_class}.#{method_name}" | |
| else | |
| "#{defined_class}##{method_name}" | |
| end | |
| output = { | |
| event: tp.event, | |
| method: method_display, | |
| path: "#{tp.path}:#{tp.lineno}" | |
| } | |
| output[:args] = args if args.any? | |
| output[:kwargs] = kwargs if kwargs.any? | |
| pp(output) | |
| elsif tp.event == :raise | |
| pp({ | |
| event: tp.event, | |
| raised_exception: tp.raised_exception, | |
| path: "#{tp.path}:#{tp.lineno}", | |
| method_id: tp.method_id | |
| }) | |
| end | |
| end | |
| trace.enable | |
| end | |
| example.run | |
| trace.disable if with_stack | |
| ActiveSupport::Notifications.unsubscribe(subscriber) if with_sql | |
| end | |
| end | |
| end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment