Repository: discourse/prometheus_exporter (Ruby)

Issue:
Incorrect keyword arguments handling in MethodProfiler
Patched method that has keyword arguments can raise error
```ruby
require 'bundler/inline'

gemfile do
  gem 'prometheus_exporter', '2.1.0'
end

require 'prometheus_exporter/instrumentation/method_profiler'

class Test
  def method(sql, cache: false, **settings)
    puts "sql -> #{sql}"
    puts "cache -> #{cache}"
    puts "settings -> #{settings}"
  end
end

PrometheusExporter::Instrumentation::MethodProfiler.patch(Test, [:method], :some_name, instrument: :prepend)

t = Test.new

t.method('select * from table')
# sql -> select * from table
# cache -> false
# settings -> {}

t.method('select * from table', cache: true, other_setting: false)
# => wrong number of arguments (given 2, expected 1) (ArgumentError)
```

@SamSaffron I can create pull request that:
- increases minimal Ruby version to 3.0, 2.7 is already EOL for a year
- changes arguments in wrapped method to `...`, it was introduced in 2.7 => https://ryanbigg.com/2021/07/ruby-27s-new-triple-dot-syntax
`def #{method_name}(...)`

Are you ok with these changes?


Discussion:

Patch:
diff --git a/.rubocop b/.rubocop
new file mode 100644
index 00000000..fc40e8e2
--- /dev/null
+++ b/.rubocop
@@ -0,0 +1,1 @@
+--ignore-unrecognized-cops
diff --git a/.rubocop.yml b/.rubocop.yml
index 3f1b32d7..17c7f883 100644
--- a/.rubocop.yml
+++ b/.rubocop.yml
@@ -4,4 +4,11 @@ inherit_gem:
 AllCops:
   Exclude:
     - 'gemfiles/**/*'
-    - 'vendor/**/*'
\ No newline at end of file
+    - 'vendor/**/*'
+
+Discourse/Plugins/NoMonkeyPatching:
+  Enabled: false
+
+Discourse/Plugins/NamespaceMethods:
+  Exclude:
+    - bin/prometheus_exporter
diff --git a/README.md b/README.md
index 73b42cf6..d20e53d3 100644
--- a/README.md
+++ b/README.md
@@ -40,7 +40,7 @@ To learn more see [Instrumenting Rails with Prometheus](https://samsaffron.com/a
 
 ## Requirements
 
-Minimum Ruby of version 2.6.0 is required, Ruby 2.5.0 is EOL as of March 31st 2021.
+Minimum Ruby of version 3.0.0 is required, Ruby 2.7 is EOL as of March 31st 2023.
 
 ## Migrating from v0.x
 
@@ -884,7 +884,7 @@ prometheus_exporter -p 8080 \
                     --prefix 'foo_'
 ```
 
-You can use `-b` option to bind the `prometheus_exporter` web server to any IPv4 interface with `-b 0.0.0.0`, 
+You can use `-b` option to bind the `prometheus_exporter` web server to any IPv4 interface with `-b 0.0.0.0`,
 any IPv6 interface with `-b ::`, or `-b ANY` to any IPv4/IPv6 interfaces available on your host system.
 
 #### Enabling Basic Authentication
diff --git a/gemfiles/ar_70.gemfile b/gemfiles/ar_70.gemfile
index 095e6608..bc1dfc9c 100644
--- a/gemfiles/ar_70.gemfile
+++ b/gemfiles/ar_70.gemfile
@@ -2,4 +2,6 @@
 
 source "https://rubygems.org"
 
+gem "activerecord", "~> 7.0.0"
+
 gemspec path: "../"
diff --git a/lib/prometheus_exporter/instrumentation/method_profiler.rb b/lib/prometheus_exporter/instrumentation/method_profiler.rb
index f1658df4..045a77f9 100644
--- a/lib/prometheus_exporter/instrumentation/method_profiler.rb
+++ b/lib/prometheus_exporter/instrumentation/method_profiler.rb
@@ -44,7 +44,7 @@ def self.define_methods_on_module(klass, methods, name)
     patch_source_line = __LINE__ + 3
     patches = methods.map do |method_name|
       <<~RUBY
-        def #{method_name}(*args, &blk)
+        def #{method_name}(...)
           unless prof = Thread.current[:_method_profiler]
             return super
           end
@@ -75,13 +75,13 @@ def self.patch_using_alias_method(klass, methods, name)
       <<~RUBY
       unless defined?(#{method_name}__mp_unpatched)
         alias_method :#{method_name}__mp_unpatched, :#{method_name}
-        def #{method_name}(*args, &blk)
+        def #{method_name}(...)
           unless prof = Thread.current[:_method_profiler]
-            return #{method_name}__mp_unpatched(*args, &blk)
+            return #{method_name}__mp_unpatched(...)
           end
           begin
             start = Process.clock_gettime(Process::CLOCK_MONOTONIC)
-            #{method_name}__mp_unpatched(*args, &blk)
+            #{method_name}__mp_unpatched(...)
           ensure
             data = (prof[:#{name}] ||= {duration: 0.0, calls: 0})
             data[:duration] += Process.clock_gettime(Process::CLOCK_MONOTONIC) - start
diff --git a/prometheus_exporter.gemspec b/prometheus_exporter.gemspec
index de1fa497..dffec661 100644
--- a/prometheus_exporter.gemspec
+++ b/prometheus_exporter.gemspec
@@ -34,7 +34,7 @@ Gem::Specification.new do |spec|
   spec.add_development_dependency "oj", "~> 3.0"
   spec.add_development_dependency "rack-test", "~> 0.8.3"
   spec.add_development_dependency "minitest-stub-const", "~> 0.6"
-  spec.add_development_dependency "rubocop-discourse", ">2"
+  spec.add_development_dependency "rubocop-discourse", ">= 3"
   spec.add_development_dependency "appraisal", "~> 2.3"
   spec.add_development_dependency "activerecord", "~> 6.0.0"
   spec.add_development_dependency "redis", "> 5"
@@ -42,5 +42,5 @@ Gem::Specification.new do |spec|
   if !RUBY_ENGINE == 'jruby'
     spec.add_development_dependency "raindrops", "~> 0.19"
   end
-  spec.required_ruby_version = '>= 2.6.0'
+  spec.required_ruby_version = '>= 3.0.0'
 end


Test patch:
diff --git a/test/instrumentation/method_profiler_test.rb b/test/instrumentation/method_profiler_test.rb
index 64026b24..d57c5e62 100644
--- a/test/instrumentation/method_profiler_test.rb
+++ b/test/instrumentation/method_profiler_test.rb
@@ -22,7 +22,7 @@ def some_method
   def test_alias_method_source_location
     file, line = SomeClassPatchedUsingAliasMethod.instance_method(:some_method).source_location
     source = File.read(file).lines[line - 1].strip
-    assert_equal 'def #{method_name}(*args, &blk)', source
+    assert_equal 'def #{method_name}(...)', source
   end
 
   def test_alias_method_preserves_behavior
@@ -32,7 +32,7 @@ def test_alias_method_preserves_behavior
   def test_prepend_source_location
     file, line = SomeClassPatchedUsingPrepend.instance_method(:some_method).source_location
     source = File.read(file).lines[line - 1].strip
-    assert_equal 'def #{method_name}(*args, &blk)', source
+    assert_equal 'def #{method_name}(...)', source
   end
 
   def test_prepend_preserves_behavior