Skip to content

Instantly share code, notes, and snippets.

@amkisko
Created December 2, 2025 09:38
Show Gist options
  • Select an option

  • Save amkisko/09a50b10e4ea82b0ac835307a940fe60 to your computer and use it in GitHub Desktop.

Select an option

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
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