Skip to content

DirectFileStore fails on Ruby 4.x: undefined method 'parse' for CGI #329

Description

@nbluis

Hi 👋
I’m seeing an incompatibility when using prometheus-client with Ruby 4.x, specifically when using Prometheus::Client::DataStores::DirectFileStore.

The same code works correctly on Ruby 3.4.2, but fails on Ruby 4.0.1 with a NoMethodError related to CGI.parse.

Other data stores were not tested yet, but the error clearly originates from DirectFileStore.

Environment

Ruby:
✅ 3.4.2 (works)
❌ 4.0.1 (fails)

Reproduction steps

require 'prometheus/client'
require 'prometheus/client/data_stores/direct_file_store'

Prometheus::Client.config.data_store =
  Prometheus::Client::DataStores::DirectFileStore.new(
    dir: '/tmp/prometheus_direct_file_store'
  )

prometheus = Prometheus::Client.registry

http_requests = Prometheus::Client::Counter.new(
  :http_requests,
  docstring: 'A counter of HTTP requests made',
  labels: [:status_code]
)

prometheus.register(http_requests)

http_requests.increment(labels: { status_code: 200 })

prometheus.metrics.each do |metric|
  puts "Metric: #{metric.name} #{metric.values}"
end

Expected behavior (Ruby 3.4.2)

Metric: http_requests {{} => 2.0, {status_code: "200"} => 15.0}

Actual behavior (Ruby 4.0.1)

/home/ebohrer/.asdf/installs/ruby/4.0.1/lib/ruby/gems/4.0.0/gems/prometheus-client-4.2.5/lib/prometheus/client/data_stores/direct_file_store.rb:139:in 'block (2 levels) in Prometheus::Client::DataStores::DirectFileStore::MetricStore#all_values': undefined method 'parse' for class CGI (NoMethodError)

                  label_set = CGI::parse(labelset_qs).map do |k, vs|
                                 ^^^^^^^
        from /home/ebohrer/.asdf/installs/ruby/4.0.1/lib/ruby/gems/4.0.0/gems/prometheus-client-4.2.5/lib/prometheus/client/data_stores/direct_file_store.rb:135:in 'Array#each'
        from /home/ebohrer/.asdf/installs/ruby/4.0.1/lib/ruby/gems/4.0.0/gems/prometheus-client-4.2.5/lib/prometheus/client/data_stores/direct_file_store.rb:135:in 'block in Prometheus::Client::DataStores::DirectFileStore::MetricStore#all_values'
        from /home/ebohrer/.asdf/installs/ruby/4.0.1/lib/ruby/gems/4.0.0/gems/prometheus-client-4.2.5/lib/prometheus/client/data_stores/direct_file_store.rb:132:in 'Array#each'
        from /home/ebohrer/.asdf/installs/ruby/4.0.1/lib/ruby/gems/4.0.0/gems/prometheus-client-4.2.5/lib/prometheus/client/data_stores/direct_file_store.rb:132:in 'Prometheus::Client::DataStores::DirectFileStore::MetricStore#all_values'
        from /home/ebohrer/.asdf/installs/ruby/4.0.1/lib/ruby/gems/4.0.0/gems/prometheus-client-4.2.5/lib/prometheus/client/metric.rb:80:in 'Prometheus::Client::Metric#values'
        from main.rb:17:in 'block in <main>'
        from main.rb:16:in 'Array#each'
        from main.rb:16:in '<main>'

Fast Analysis

This issue appears to be specific to DirectFileStore, more precisely in
DirectFileStore::MetricStore#all_values.

Ruby 4 removed or changed the availability of CGI.parse.
In Ruby 3.x this method exists, but in Ruby 4.x it raises NoMethodError.

Activity

  1. Sinjo commented on Feb 5, 2026

    @Sinjo
    Member

    Hello! Thanks for the report. I've got a couple of PRs open that will fix this:

    Once they're merged I'll cut a new release and ping you here!

  2. self-assigned this
    on Feb 5, 2026
  3. ciscou commented on Jul 4, 2026

    @ciscou

    same here. I've fixed it on my rails app with this monkey patch (in config/initializers/monkey_patch_prometheus_client.rb)

    Prometheus::Client::DataStores::DirectFileStore.const_get("MetricStore").class_eval do
      def all_values
        stores_data = Hash.new { |hash, key| hash[key] = [] }
    
        # There's no need to call `synchronize` here. We're opening a second handle to
        # the file, and `flock`ing it, which prevents inconsistent reads
        stores_for_metric.each do |file_path|
          store = Prometheus::Client::DataStores::DirectFileStore::FileMappedDict.new(file_path, true)
          store.all_values.each do |(labelset_qs, v, ts)|
            # Labels come as a query string, that we parse with URI.decode_www_form as this:
            # "foo=bar&x=y" => { "foo" => "bar", "x" => "y" }
            # Turn the keys back into symbols
            label_set = URI.decode_www_form(labelset_qs).map do |k, v|
              [k.to_sym, v]
            end.to_h
    
            stores_data[label_set] << [v, ts]
          end
        ensure
          store.close if store
        end
    
        # Aggregate all the different values for each label_set
        aggregate_hash = Hash.new { |hash, key| hash[key] = 0.0 }
        stores_data.each_with_object(aggregate_hash) do |(label_set, values), acc|
          acc[label_set] = aggregate_values(values)
        end
      end
    end

    Might attempt to open a PR later today

  4. Sinjo commented on Jul 4, 2026

    @Sinjo
    Member

    Hey, sorry for the lack of progress on this. I have the code teed up already, but haven't had time to write and run a test I wanted to before shipping it. I don't get employer-sponsored time to work on this gem any more and life is busy.

  5. ciscou commented on Jul 4, 2026

    @ciscou

    @Sinjo I totally understand! No pressure at all, we're very grateful for all your work. For now I'm happy with my monkey patch, I'll remove it when this is fixed upstream 😄

  6. nbluis commented on Jul 5, 2026

    @nbluis
    Author

    @Sinjo dont worry and thanks for your incredible work.

    I was able to work around this by explicitly adding the cgi gem to my Gemfile:

    gem 'cgi', '~> 0.5.1'
    

    Ruby 4 no longer ships CGI.parse in the default bundled cgi — it now lives in the standalone gem. Adding it back restores the method and DirectFileStore works as expected again.
    This should be a good option for anyone hitting this issue until a definitive fix.

  7. Sinjo commented on Sep 1, 2026

    @Sinjo
    Member

    This is fixed in 5.0.0, which I pushed to RubyGems a few days ago.

    If you're curious about the testing I wanted to do, it's here: #330 (comment)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions