Skip to content

Commit e2afbe7

Browse files
authored
Merge pull request #251 from prometheus/sinjo-remove-framework-specific-routing
Remove framework-specific route detection from collector middleware
2 parents 134c1fd + 1dfc1ad commit e2afbe7

File tree

3 files changed

+34
-104
lines changed

3 files changed

+34
-104
lines changed

examples/rack/README.md

Lines changed: 29 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -54,22 +54,42 @@ example, if you want to [change the way IDs are stripped from the
5454
path](https://github.com/prometheus/client_ruby/blob/982fe2e3c37e2940d281573c7689224152dd791f/lib/prometheus/middleware/collector.rb#L97-L101)
5555
you could override the appropriate method:
5656

57-
```Ruby
57+
```ruby
5858
require 'prometheus/middleware/collector'
59-
module Prometheus
60-
module Middleware
61-
class MyCollector < Collector
62-
def strip_ids_from_path(path)
63-
super(path)
64-
.gsub(/8675309/, ':jenny\\1')
65-
end
66-
end
59+
60+
class MyCollector < Prometheus::Middleware::Collector
61+
def strip_ids_from_path(path)
62+
super(path)
63+
.gsub(/8675309/, ':jenny\\1')
6764
end
6865
end
6966
```
7067

7168
and use your class in `config.ru` instead.
7269

70+
If you want to completely customise how the `path` label is generated, you can
71+
override `generate_path`. For example, to use
72+
[Sinatra](https://github.com/sinatra/sinatra)'s framework-specific route info
73+
from the request environment:
74+
75+
```ruby
76+
require 'prometheus/middleware/collector'
77+
78+
class MyCollector < Prometheus::Middleware::Collector
79+
def generate_path(env)
80+
# `sinatra.route` contains both the request method and the route, separated
81+
# by a space (e.g. "GET /payments/:id"). To get just the request path, you
82+
# can partition the string on " ".
83+
env['sinatra.route'].partition(' ').last
84+
end
85+
end
86+
```
87+
88+
Just make sure that your custom path generation logic strips IDs from the path
89+
it returns, or gets the path from a source that would never contain them in the
90+
first place (such as `sinatra.route`), otherwise you'll generate a huge number
91+
of label values!
92+
7393
**Note:** `Prometheus::Middleware::Collector` isn't explicitly designed to be
7494
subclassed, so the internals are liable to change at any time, including in
7595
patch releases. Overriding its methods is done at your own risk!

lib/prometheus/middleware/collector.rb

Lines changed: 5 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -72,12 +72,12 @@ def record(env, code, duration)
7272
counter_labels = {
7373
code: code,
7474
method: env['REQUEST_METHOD'].downcase,
75-
path: strip_ids_from_path(path),
75+
path: path,
7676
}
7777

7878
duration_labels = {
7979
method: env['REQUEST_METHOD'].downcase,
80-
path: strip_ids_from_path(path),
80+
path: path,
8181
}
8282

8383
@requests.increment(labels: counter_labels)
@@ -87,52 +87,10 @@ def record(env, code, duration)
8787
nil
8888
end
8989

90-
# While `PATH_INFO` is framework agnostic, and works for any Rack app, some Ruby web
91-
# frameworks pass a more useful piece of information into the request env - the
92-
# route that the request matched.
93-
#
94-
# This means that rather than using our generic `:id` and `:uuid` replacements in
95-
# the `path` label for any path segments that look like dynamic IDs, we can put the
96-
# actual route that matched in there, with correctly named parameters. For example,
97-
# if a Sinatra app defined a route like:
98-
#
99-
# get "/foo/:bar" do
100-
# ...
101-
# end
102-
#
103-
# instead of containing `/foo/:id`, the `path` label would contain `/foo/:bar`.
104-
#
105-
# Sadly, Rails is a notable exception, and (as far as I can tell at the time of
106-
# writing) doesn't provide this info in the request env.
10790
def generate_path(env)
108-
if env['sinatra.route']
109-
route = env['sinatra.route'].partition(' ').last
110-
elsif env['grape.routing_args']
111-
# We are deep in the weeds of an object that Grape passes into the request env,
112-
# but don't document any explicit guarantees about. Let's have a fallback in
113-
# case they change it down the line.
114-
#
115-
# This code would be neater with the safe navigation operator (`&.`) here rather
116-
# than the much more verbose `respond_to?` calls, but unlike Rails' `try`
117-
# method, it still raises an error if the object is non-nil, but doesn't respond
118-
# to the method being called on it.
119-
route = nil
120-
121-
route_info = env.dig('grape.routing_args', :route_info)
122-
if route_info.respond_to?(:pattern)
123-
pattern = route_info.pattern
124-
if pattern.respond_to?(:origin)
125-
route = pattern.origin
126-
end
127-
end
128-
129-
# Fall back to PATH_INFO if Grape change the structure of `grape.routing_args`
130-
route ||= env['PATH_INFO']
131-
else
132-
route = env['PATH_INFO']
133-
end
134-
135-
[env['SCRIPT_NAME'], route].join
91+
full_path = [env['SCRIPT_NAME'], env['PATH_INFO']].join
92+
93+
strip_ids_from_path(full_path)
13694
end
13795

13896
def strip_ids_from_path(path)

spec/prometheus/middleware/collector_spec.rb

Lines changed: 0 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -123,54 +123,6 @@
123123
expect(registry.get(metric).get(labels: labels)).to include("0.1" => 0, "0.5" => 1)
124124
end
125125

126-
it 'prefers sinatra.route to PATH_INFO' do
127-
metric = :http_server_requests_total
128-
129-
env('sinatra.route', 'GET /foo/:named_param')
130-
get '/foo/7'
131-
env('sinatra.route', nil)
132-
expect(registry.get(metric).values.keys.last[:path]).to eql("/foo/:named_param")
133-
end
134-
135-
it 'prefers grape.routing_args to PATH_INFO' do
136-
metric = :http_server_requests_total
137-
138-
# request.env["grape.routing_args"][:route_info].pattern.origin
139-
#
140-
# Yes, this is the object you have to traverse to get the path.
141-
#
142-
# No, I'm not happy about it either.
143-
grape_routing_args = {
144-
route_info: double(:route_info,
145-
pattern: double(:pattern,
146-
origin: '/foo/:named_param'
147-
)
148-
)
149-
}
150-
151-
env('grape.routing_args', grape_routing_args)
152-
get '/foo/7'
153-
env('grape.routing_args', nil)
154-
expect(registry.get(metric).values.keys.last[:path]).to eql("/foo/:named_param")
155-
end
156-
157-
it "falls back to PATH_INFO if the structure of grape.routing_args changes" do
158-
metric = :http_server_requests_total
159-
160-
grape_routing_args = {
161-
route_info: double(:route_info,
162-
pattern: double(:pattern,
163-
origin_but_different: '/foo/:named_param'
164-
)
165-
)
166-
}
167-
168-
env('grape.routing_args', grape_routing_args)
169-
get '/foo/7'
170-
env('grape.routing_args', nil)
171-
expect(registry.get(metric).values.keys.last[:path]).to eql("/foo/:id")
172-
end
173-
174126
context 'when the app raises an exception' do
175127
let(:original_app) do
176128
lambda do |env|

0 commit comments

Comments
 (0)