FEATURE: improve extensibility of default labels

FEATURE: improve extensibility of default labels

Middleware can now be cleanly overridden to provide custom default labels

diff --git a/README.md b/README.md
index 89e1485..c883ad4 100644
--- a/README.md
+++ b/README.md
@@ -218,6 +218,32 @@ class MyMiddleware < PrometheusExporter::Middleware
 end
 `‍``
 
+If you're not using Rails like framework, you can extend `PrometheusExporter::Middleware#default_labels` in a way to add more relevant labels.
+For example you can mimic [prometheus-client](https://github.com/prometheus/client_ruby) labels with code like this:
+`‍``ruby
+class MyMiddleware < PrometheusExporter::Middleware
+  def default_labels(env, result)
+    status = (result && result[0]) || -1
+    path = [env["SCRIPT_NAME"], env["PATH_INFO"]].join
+    {
+      path: strip_ids_from_path(path),
+      method: env["REQUEST_METHOD"],
+      status: status
+    }
+  end
+
+  def strip_ids_from_path(path)
+    path
+      .gsub(%r{/[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}(/|$)}, '/:uuid\\1')
+      .gsub(%r{/\d+(/|$)}, '/:id\\1')
+  end
+end
+`‍``
+That way you won't have all metrics labeled with `controller=other` and `action=other`, but have labels such as
+`‍``
+ruby_http_duration_seconds{path="/api/v1/teams/:id",method="GET",status="200",quantile="0.99"} 0.009880661998977303
+`‍``
+
 ¹) Only available when Redis is used.
 ²) Only available when Mysql or PostgreSQL are used.
 ³) Only available when [Instrumenting Request Queueing Time](#instrumenting-request-queueing-time) is set up.
diff --git a/lib/prometheus_exporter/middleware.rb b/lib/prometheus_exporter/middleware.rb
index 03e114c..2242895 100644
--- a/lib/prometheus_exporter/middleware.rb
+++ b/lib/prometheus_exporter/middleware.rb
@@ -36,21 +36,12 @@ class PrometheusExporter::Middleware
 
     result
   ensure
-    status = (result && result[0]) || -1
-    params = env["action_dispatch.request.parameters"]
-    action, controller = nil
-    if params
-      action = params["action"]
-      controller = params["controller"]
-    end
 
     obj = {
       type: "web",
       timings: info,
       queue_time: queue_time,
-      action: action,
-      controller: controller,
-      status: status
+      default_labels: default_labels(env, result)
     }
     labels = custom_labels(env)
     if labels
@@ -60,6 +51,22 @@ class PrometheusExporter::Middleware
     @client.send_json(obj)
   end
 
+  def default_labels(env, result)
+    status = (result && result[0]) || -1
+    params = env["action_dispatch.request.parameters"]
+    action = controller = nil
+    if params
+      action = params["action"]
+      controller = params["controller"]
+    end
+
+    {
+      action: action || "other",
+      controller: controller || "other",
+      status: status
+    }
+  end
+
   # allows subclasses to add custom labels based on env
   def custom_labels(env)
     nil
diff --git a/lib/prometheus_exporter/server/web_collector.rb b/lib/prometheus_exporter/server/web_collector.rb
index 4703756..f477d8f 100644
--- a/lib/prometheus_exporter/server/web_collector.rb
+++ b/lib/prometheus_exporter/server/web_collector.rb
@@ -56,14 +56,11 @@ module PrometheusExporter::Server
     end
 
     def observe(obj)
-      default_labels = {
-        controller: obj['controller'] || 'other',
-        action: obj['action'] || 'other'
-      }
+      default_labels = obj['default_labels']
       custom_labels = obj['custom_labels']
       labels = custom_labels.nil? ? default_labels : default_labels.merge(custom_labels)
 
-      @http_requests_total.observe(1, labels.merge(status: obj["status"]))
+      @http_requests_total.observe(1, labels)
 
       if timings = obj["timings"]
         @http_duration_seconds.observe(timings["total_duration"], labels)
diff --git a/test/server/web_collector_test.rb b/test/server/web_collector_test.rb
index 4595204..6b27193 100644
--- a/test/server/web_collector_test.rb
+++ b/test/server/web_collector_test.rb
@@ -12,11 +12,13 @@ class PrometheusWebCollectorTest < Minitest::Test
 
   def test_collecting_metrics_without_specific_timings
     collector.collect(
-      type: "web",
-      timings: nil,
-      action: 'index',
-      controller: 'home',
-      status: 200
+      "type" => "web",
+      "timings" => nil,
+      "default_labels" => {
+        "action" => 'index',
+        "controller" => 'home',
+        "status": 200
+      },
     )
 
     metrics = collector.metrics
@@ -39,9 +41,11 @@ class PrometheusWebCollectorTest < Minitest::Test
         "queue" => 0.03,
         "total_duration" => 1.0
       },
-      "action" => 'index',
-      "controller" => 'home',
-      "status" => 200
+      'default_labels' => {
+        'action' => 'index',
+        'controller' => 'home',
+        "status" => 200
+      },
     )
 
     metrics = collector.metrics
@@ -52,9 +56,11 @@ class PrometheusWebCollectorTest < Minitest::Test
     collector.collect(
       'type' => 'web',
       'timings' => nil,
-      'action' => 'index',
-      'controller' => 'home',
-      'status' => 200,
+      'default_labels' => {
+        'controller' => 'home',
+        'action' => 'index',
+        'status' => 200,
+      },
       'custom_labels' => {
         'service' => 'service1'
       }
@@ -63,6 +69,6 @@ class PrometheusWebCollectorTest < Minitest::Test
     metrics = collector.metrics
 
     assert_equal 5, metrics.size
-    assert(metrics.first.metric_text.include?('http_requests_total{controller="home",action="index",service="service1",status="200"}'))
+    assert(metrics.first.metric_text.include?('http_requests_total{controller="home",action="index",status="200",service="service1"}'))
   end
 end

GitHub sha: 717a69c0

This commit appears in #148 which was approved by SamSaffron. It was merged by SamSaffron.