prometheus_exporter: a Ruby gem for exporting custom metrics from Rails
A framework for collecting and aggregating prometheus metrics
At a glance
- What is it?
- prometheus_exporter is a Ruby framework for collecting and aggregating custom Prometheus metrics, with a single process mode, a multiprocess mode for clustered servers, and built-in collectors for Sidekiq, Puma, Unicorn, Resque, GoodJob, Shoryuken, Hutch and ActiveRecord connection pools. It is MIT licensed and requires Ruby 3.3 or newer.
- Who is it for?
- Adopt prometheus_exporter if you run a Rails or Ruby service and want to define your own application metrics with a clean API, or if you run a clustered server where each process needs to report to one collector. Skip it if the built-in collectors already cover everything you need, since a standard client library may be a smaller dependency, and note the version constraint that Ruby 3.3 or newer is required.
- Can I use it commercially?
- Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 24 days ago.
- What is it written in?
- Mainly Ruby, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 23, 2026, and from our analysis. They are not legal advice.
Editorial analysis
A framework, not just a middleware
Prometheus Exporter allows you to aggregate custom metrics from multiple processes and export to Prometheus, and the README describes it as a very flexible framework for handling Prometheus metrics that can operate in a single and multiprocess mode. That last phrase is what separates it from the usual Ruby client library. Most client libraries give you a gauge, a counter and a histogram and let you push values into them, and in a clustered server that is exactly the wrong shape, because each worker process has its own memory and the numbers you want are the sum across workers. This library is built around the opposite assumption. A process writes its observations somewhere shared, and a separate exporter process aggregates and serves them. The result is that the same API works whether you run one process or fifty, and the mode is a deployment decision rather than a rewrite. The project is MIT licensed, it is a rubygem, and the project points to a write-up on instrumenting Rails with Prometheus for the longer explanation of the approach. The requirements section states that Ruby 3.3 or newer is required, and the migration note from v0.x records that some metrics are renamed to match the Prometheus official guide for metric names.
Single process mode, where the server and the client live together
The single process mode is the simplest way to consume the exporter, and the API is worth reading closely because it is the part you will use most. You require the server, the client and the instrumentation, create a web server bound to an address and port, start it, and then point the default client at the server's collector.
require 'prometheus_exporter/server'
# client allows instrumentation to send info to server
require 'prometheus_exporter/client'
require 'prometheus_exporter/instrumentation'
# bind is the address, on which the webserver will listen
# port is the port that will provide the /metrics route
server = PrometheusExporter::Server::WebServer.new bind: 'localhost', port: 12345
server.start
# wire up a default local client
PrometheusExporter::Client.default = PrometheusExporter::LocalClient.new(collector: server.collector)
# this ensures basic process instrumentation metrics are added such as RSS and Ruby metrics
PrometheusExporter::Instrumentation::Process.start(type: "my program", labels: {my_custom: "label for all process metrics"})
gauge = PrometheusExporter::Metric::Gauge.new("rss", "used RSS for process")
counter = PrometheusExporter::Metric::Counter.new("web_requests", "number of web requests")
summary = PrometheusExporter::Metric::Summary.new("page_load_time", "time it took to load page")
histogram = PrometheusExporter::Metric::Histogram.new("api_access_time", "time it took to call api")
server.collector.register_metric(gauge)
server.collector.register_metric(counter)
server.collector.register_metric(summary)
server.collector.register_metric(histogram)
gauge.observe(server.get_rss)
gauge.observe(server.get_rss)
counter.observe(1, route: 'test/route')
counter.observe(1, route: 'another/route')
summary.observe(1.1)
summary.observe(1.12)
summary.observe(0.12)
histogram.observe(0.2, api: 'twitter')Four metric types are covered, a gauge, a counter, a summary and a histogram, and each is constructed with a name and a description, then registered with the collector before you observe into it. Labels are passed as keyword arguments to observe rather than at construction, so the label set can vary per observation. The instrumentation process class is the piece that gives you process level metrics such as RSS and Ruby runtime statistics for free, with a type and a set of labels applied to all of them, which is the right way to make sure you can attribute a metric to a process. And the comment at the end notes that the /metrics route on the bound port now returns everything you have registered. The quantiles and buckets are configurable when you need them to be, since the defaults are not always the ones a report needs.
summary = PrometheusExporter::Metric::Summary.new("load_time", "time to load page", quantiles: [0.99, 0.75, 0.5, 0.25])
histogram = PrometheusExporter::Metric::Histogram.new("api_time", "time to call api", buckets: [0.1, 0.5, 1])Multiprocess mode, where the exporter is its own process
The multiprocess mode exists for the case the README names, for example unicorn or puma clusters, where you want to aggregate metrics across multiple processes. The simplest way to achieve it is to use the built-in collector, and the arrangement is that a separate exporter process runs on its own port, defaulting to a local bind on 9394.
$ prometheus_exporterYour application then talks to the default client and registers metrics as before, and the client forwards them to the running exporter.
require 'prometheus_exporter/client'
client = PrometheusExporter::Client.default
gauge = client.register(:gauge, "awesome", "amount of awesome")
gauge.observe(10)
gauge.observe(99, day: "friday")The register call here takes a type as its first argument, unlike the single process example where the type comes from the metric class you construct, which is a small inconsistency in the API to be aware of. The output shows the payoff, since the exporter serves a collector_working gauge telling you whether the master process collector can collect metrics, followed by the aggregated values with their labels.
$ curl localhost:9394/metrics
# HELP collector_working Is the master process collector able to collect metrics
# TYPE collector_working gauge
collector_working 1
# HELP awesome amount of awesome
# TYPE awesome gauge
awesome{day="friday"} 99
awesome 10That collector_working metric is worth watching. If the collector process is unhealthy, your application processes can still accept and buffer observations, so a scrape can succeed while the numbers are stale, and a gauge that reports the health of the collector itself is what tells you the difference between no traffic and broken aggregation. The dockerfile in the repository does the obvious thing for this mode, exposing 9394 and running the exporter as the entrypoint with a bind of ANY.
The Rails middleware, and the patching style conflict
Integration into a Rack application is a Gemfile line and an initializer, and the initializer is worth copying as-is because the guard clause matters. You unshift the middleware onto the Rails application stack, and you skip it in the test environment.
unless Rails.env.test?
require 'prometheus_exporter/middleware'
# This reports stats per request like HTTP status and timings
Rails.application.middleware.unshift PrometheusExporter::Middleware
endYou also need to run the exporter in a monitored background process, which the README gives as a bundle exec prometheus_exporter command, because the middleware needs a collector to talk to. The interesting part of the integration is the method patching style, and it is a real conflict rather than a preference. By default the exporter uses alias_method to instrument the methods used by SQL and Redis, because that is the fastest approach. If you also run another instrumentation library in the same application and that library uses prepend instead, the two can fight, so you can tell the middleware to instrument using prepend by passing an instrument option.
Rails.application.middleware.unshift PrometheusExporter::Middleware, instrument: :prependSo the choice is between being the fastest thing in the process and coexisting with other instrumentation. The README also lists what the middleware collects, described as reporting stats per request like HTTP status and timings, and the table of metrics it collects follows in the document.
The built-in collectors cover the queue and server backends
The table of contents is the best summary of the coverage, because the library ships collectors for the specific pieces of a Ruby deployment that are otherwise invisible. For background work there are Sidekiq metrics, Shoryuken metrics, Resque metrics, GoodJob metrics, Hutch metrics with a message processing tracer, and a Delayed Job plugin. For the web server there are Puma metrics, Unicorn process metrics, and per-process stats in the Rails section. For the database there are ActiveRecord connection pool metrics, which is the one that tends to be the most operationally useful, since a saturated connection pool shows up as request latency and is invisible in the application logs. The README also documents a GraphQL support section, which suggests metrics can be attributed to GraphQL operations, a metrics default prefix and labels setting so that everything your application emits is namespaced consistently, a client default labels setting, a client default host setting, and a histogram mode with custom buckets, which suggests a mode where the exporter computes histograms rather than only aggregating them. There is also a section on custom type collectors and another on multiprocess mode with a custom collector, plus the examples directory in the repository holds a custom_collector.rb file, which is the reference for extending the mechanism. The transport concerns section covers how observations reach the collector, and there are separate sections on JSON generation and parsing and on logging, which tells you the wire format between your processes and the exporter is JSON.
Maintenance, packaging and the version constraint
The maintenance picture needs stating clearly, because it affects your adoption decision. The repository has one tagged release, v0.4.13 from 2019-07-15, and the last push to the repository was on 2026-09-29, so the code is being touched while the version tags are old. The README refers to a v1.x line in the migration section, describing major changes from v0.x including renamed metrics, so the current work is in a major version that has not been tagged as a release in the list. The Ruby version floor of 3.3 or newer is the more actionable constraint, since it excludes a large part of the installed Ruby base, and a gem that requires a recent Ruby is a gem you cannot drop into an older application without upgrading the application. The repository layout shows a conventional gem structure, with the gemspec, a lib directory, a test directory, an exe directory for the executable, a bench directory, an examples directory, a Rakefile, a Guardfile, an Appraisals file for testing against multiple dependency sets, gemfiles/ for those appraisal Gemfiles, and both a .rubocop and a .rubocop.yml, which suggests the project moved to RuboCop's newer configuration layout. The Dockerfile builds on a slim Ruby image with the argument defaulting to Ruby 3.4, installs curl, and exposes the exporter port, so container use is a first class path. A PR template, a code of conduct, a CHANGELOG and the MIT licence file are all present.
Editorial conclusion
Adopt prometheus_exporter if you run a Rails or Ruby service and want to define your own application metrics with a clean API, or if you run a clustered server where each process needs to report to one collector. Skip it if the built-in collectors already cover everything you need, since a standard client library may be a smaller dependency, and note the version constraint that Ruby 3.3 or newer is required. Verify first whether you need single process or multiprocess mode, since the second needs a separate exporter process running, and that your metric names follow the Prometheus naming conventions the v1 release moved to.
Frequently asked questions
What is the difference between single process and multiprocess mode in prometheus_exporter?
In single process mode the web server, the client and the collector live in the same process and the /metrics route is served there. In multiprocess mode, which the README suggests for unicorn or puma clusters, a separate prometheus_exporter process aggregates observations from your application processes and serves the metrics on its own port, defaulting to 9394.
Which Ruby and Rails integrations does prometheus_exporter provide?
It requires Ruby 3.3 or newer. Beyond the Rack middleware there are built-in collectors for Sidekiq, Shoryuken, Resque, GoodJob, Hutch, Puma, Unicorn and ActiveRecord connection pools, plus a Delayed Job plugin, GraphQL support and a custom collector extension path documented in examples/custom_collector.rb.
How do I install prometheus_exporter?
Add gem 'prometheus_exporter' to your Gemfile and run bundle, or install it directly with gem install prometheus_exporter. The gem also ships a prometheus_exporter executable, which is what you run as the separate collector process in multiprocess mode, and a Dockerfile that exposes port 9394.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/discourse-prometheus-exporter)Was this useful?