promenade 0.2.2 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 8b4b2246b9bdf2b31727cfb898cf680facfe1bfcd9a6c3393eb7237c4c2ef870
4
- data.tar.gz: 64f386aa492f3f953cb80d8de3067e4d2565de9e0d3d9379888596f2d88cd283
3
+ metadata.gz: fcfb43aad9817592d691db8877de9ac9df46e39e98df91d600121e497dd3b1b4
4
+ data.tar.gz: e152ba9577142a1648ab18914a777144930a685359c0ea874502027d2961b467
5
5
  SHA512:
6
- metadata.gz: 7a7e963d14200c730bfbc8d609ff8571dffae97f136ec5ecf3e567877795d1d8e300c8c9256b905263eb73f422e35b46cf6cae211cbab9edf89675ade9ab1f57
7
- data.tar.gz: d2387375708b46e7f9823ba481419cf1f46b0656ce4f3e42485b420635383aaec7953185bdf714f7a4c7ecb4b38edd0a22bd1ca232705876d087a8df05668971
6
+ metadata.gz: 997977c4f41d8ecc5db991cece8ff6b7ab950104ac8ad996b273446fef94ac6d4c5ce8f5c90a515d686ccc02db828c77630d03eeb8bd83ed72b6460fc3a700ed
7
+ data.tar.gz: cdc7399c7d32986cd676d71cbddd333d535c2695068a278a01ef981ae75c88e1f43ca626341665b556cf3f421f9e0e74f0e1752f4f07cda132f249f5042efbc9
@@ -2,14 +2,14 @@ name: CI
2
2
  on:
3
3
  push:
4
4
  branches:
5
- -master
5
+ - master
6
6
  pull_request:
7
7
  jobs:
8
8
  test:
9
9
  strategy:
10
10
  fail-fast: false
11
11
  matrix:
12
- ruby: ['2.6', '2.7', '3.0']
12
+ ruby: ['2.7', '3.0', "3.1"]
13
13
  runs-on: ubuntu-latest
14
14
  steps:
15
15
  - uses: actions/checkout@v2
data/.gitignore CHANGED
@@ -1,14 +1,16 @@
1
+ .rspec_status
2
+ .byebug_history
3
+ .rubocop-https*yml
1
4
  /.bundle/
2
- /.yardoc
3
- /_yardoc/
4
5
  /coverage/
5
6
  /doc/
7
+ /log/
6
8
  /pkg/
9
+ /.sass-cache/
7
10
  /spec/reports/
11
+ /spec/dummy/log
12
+ /spec/dummy/tmp
8
13
  /tmp/
9
- .rubocop-https*yml
10
- .sass-cache/
11
- vendor
12
-
13
- # rspec failure tracking
14
- .rspec_status
14
+ /vendor/
15
+ /.yardoc
16
+ /_yardoc/
data/.rubocop.yml CHANGED
@@ -2,7 +2,7 @@ inherit_from:
2
2
  - https://raw.githubusercontent.com/cookpad/guides/master/.rubocop.yml
3
3
 
4
4
  AllCops:
5
- TargetRubyVersion: 2.5
5
+ TargetRubyVersion: 2.7
6
6
  NewCops: enable
7
7
  Exclude:
8
8
  - vendor/**/*
data/Gemfile.lock CHANGED
@@ -1,29 +1,92 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- promenade (0.2.2)
5
- activesupport
6
- prometheus-client-mmap (~> 0.12.0)
4
+ promenade (0.4.0)
5
+ actionpack
6
+ activesupport (> 6.0, < 8.0)
7
+ prometheus-client-mmap (~> 0.16.0)
7
8
  rack
8
9
 
9
10
  GEM
10
11
  remote: https://rubygems.org/
11
12
  specs:
12
- activesupport (6.1.3.2)
13
+ actioncable (7.0.3)
14
+ actionpack (= 7.0.3)
15
+ activesupport (= 7.0.3)
16
+ nio4r (~> 2.0)
17
+ websocket-driver (>= 0.6.1)
18
+ actionmailbox (7.0.3)
19
+ actionpack (= 7.0.3)
20
+ activejob (= 7.0.3)
21
+ activerecord (= 7.0.3)
22
+ activestorage (= 7.0.3)
23
+ activesupport (= 7.0.3)
24
+ mail (>= 2.7.1)
25
+ net-imap
26
+ net-pop
27
+ net-smtp
28
+ actionmailer (7.0.3)
29
+ actionpack (= 7.0.3)
30
+ actionview (= 7.0.3)
31
+ activejob (= 7.0.3)
32
+ activesupport (= 7.0.3)
33
+ mail (~> 2.5, >= 2.5.4)
34
+ net-imap
35
+ net-pop
36
+ net-smtp
37
+ rails-dom-testing (~> 2.0)
38
+ actionpack (7.0.3)
39
+ actionview (= 7.0.3)
40
+ activesupport (= 7.0.3)
41
+ rack (~> 2.0, >= 2.2.0)
42
+ rack-test (>= 0.6.3)
43
+ rails-dom-testing (~> 2.0)
44
+ rails-html-sanitizer (~> 1.0, >= 1.2.0)
45
+ actiontext (7.0.3)
46
+ actionpack (= 7.0.3)
47
+ activerecord (= 7.0.3)
48
+ activestorage (= 7.0.3)
49
+ activesupport (= 7.0.3)
50
+ globalid (>= 0.6.0)
51
+ nokogiri (>= 1.8.5)
52
+ actionview (7.0.3)
53
+ activesupport (= 7.0.3)
54
+ builder (~> 3.1)
55
+ erubi (~> 1.4)
56
+ rails-dom-testing (~> 2.0)
57
+ rails-html-sanitizer (~> 1.1, >= 1.2.0)
58
+ activejob (7.0.3)
59
+ activesupport (= 7.0.3)
60
+ globalid (>= 0.3.6)
61
+ activemodel (7.0.3)
62
+ activesupport (= 7.0.3)
63
+ activerecord (7.0.3)
64
+ activemodel (= 7.0.3)
65
+ activesupport (= 7.0.3)
66
+ activestorage (7.0.3)
67
+ actionpack (= 7.0.3)
68
+ activejob (= 7.0.3)
69
+ activerecord (= 7.0.3)
70
+ activesupport (= 7.0.3)
71
+ marcel (~> 1.0)
72
+ mini_mime (>= 1.1.0)
73
+ activesupport (7.0.3)
13
74
  concurrent-ruby (~> 1.0, >= 1.0.2)
14
75
  i18n (>= 1.6, < 2)
15
76
  minitest (>= 5.1)
16
77
  tzinfo (~> 2.0)
17
- zeitwerk (~> 2.3)
18
78
  ast (2.4.2)
19
- backports (3.21.0)
79
+ backports (3.23.0)
20
80
  binding_of_caller (1.0.0)
21
81
  debug_inspector (>= 0.0.1)
22
- climate_control (1.0.0)
23
- codecov (0.5.2)
82
+ builder (3.2.4)
83
+ byebug (11.1.3)
84
+ climate_control (1.1.1)
85
+ codecov (0.6.0)
24
86
  simplecov (>= 0.15, < 0.22)
25
87
  coderay (1.1.3)
26
- concurrent-ruby (1.1.8)
88
+ concurrent-ruby (1.1.10)
89
+ crass (1.0.6)
27
90
  debug_inspector (1.1.0)
28
91
  deep-cover (1.1.0)
29
92
  deep-cover-core (= 1.1.0)
@@ -37,53 +100,120 @@ GEM
37
100
  pry
38
101
  term-ansicolor
39
102
  terminal-table
40
- diff-lcs (1.4.4)
103
+ diff-lcs (1.5.0)
104
+ digest (3.1.0)
41
105
  docile (1.4.0)
106
+ erubi (1.10.0)
107
+ globalid (1.0.0)
108
+ activesupport (>= 5.0)
42
109
  highline (2.0.3)
43
- i18n (1.8.10)
110
+ i18n (1.10.0)
44
111
  concurrent-ruby (~> 1.0)
112
+ loofah (2.18.0)
113
+ crass (~> 1.0.2)
114
+ nokogiri (>= 1.5.9)
115
+ mail (2.7.1)
116
+ mini_mime (>= 0.1.1)
117
+ marcel (1.0.2)
45
118
  method_source (1.0.0)
46
- minitest (5.14.4)
47
- parallel (1.20.1)
48
- parser (3.0.1.1)
119
+ mini_mime (1.1.2)
120
+ mini_portile2 (2.8.0)
121
+ minitest (5.16.0)
122
+ net-imap (0.2.3)
123
+ digest
124
+ net-protocol
125
+ strscan
126
+ net-pop (0.1.1)
127
+ digest
128
+ net-protocol
129
+ timeout
130
+ net-protocol (0.1.3)
131
+ timeout
132
+ net-smtp (0.3.1)
133
+ digest
134
+ net-protocol
135
+ timeout
136
+ nio4r (2.5.8)
137
+ nokogiri (1.13.6)
138
+ mini_portile2 (~> 2.8.0)
139
+ racc (~> 1.4)
140
+ parallel (1.22.1)
141
+ parser (3.1.2.0)
49
142
  ast (~> 2.4.1)
50
- prometheus-client-mmap (0.12.0)
143
+ prometheus-client-mmap (0.16.0)
51
144
  pry (0.14.1)
52
145
  coderay (~> 1.1)
53
146
  method_source (~> 1.0)
54
- rack (2.2.3)
55
- rainbow (3.0.0)
56
- rake (13.0.3)
57
- regexp_parser (2.1.1)
147
+ racc (1.6.0)
148
+ rack (2.2.3.1)
149
+ rack-test (1.1.0)
150
+ rack (>= 1.0, < 3)
151
+ rails (7.0.3)
152
+ actioncable (= 7.0.3)
153
+ actionmailbox (= 7.0.3)
154
+ actionmailer (= 7.0.3)
155
+ actionpack (= 7.0.3)
156
+ actiontext (= 7.0.3)
157
+ actionview (= 7.0.3)
158
+ activejob (= 7.0.3)
159
+ activemodel (= 7.0.3)
160
+ activerecord (= 7.0.3)
161
+ activestorage (= 7.0.3)
162
+ activesupport (= 7.0.3)
163
+ bundler (>= 1.15.0)
164
+ railties (= 7.0.3)
165
+ rails-dom-testing (2.0.3)
166
+ activesupport (>= 4.2.0)
167
+ nokogiri (>= 1.6)
168
+ rails-html-sanitizer (1.4.3)
169
+ loofah (~> 2.3)
170
+ railties (7.0.3)
171
+ actionpack (= 7.0.3)
172
+ activesupport (= 7.0.3)
173
+ method_source
174
+ rake (>= 12.2)
175
+ thor (~> 1.0)
176
+ zeitwerk (~> 2.5)
177
+ rainbow (3.1.1)
178
+ rake (13.0.6)
179
+ regexp_parser (2.5.0)
58
180
  rexml (3.2.5)
59
- rspec (3.10.0)
60
- rspec-core (~> 3.10.0)
61
- rspec-expectations (~> 3.10.0)
62
- rspec-mocks (~> 3.10.0)
63
- rspec-core (3.10.1)
64
- rspec-support (~> 3.10.0)
65
- rspec-expectations (3.10.1)
181
+ rspec (3.11.0)
182
+ rspec-core (~> 3.11.0)
183
+ rspec-expectations (~> 3.11.0)
184
+ rspec-mocks (~> 3.11.0)
185
+ rspec-core (3.11.0)
186
+ rspec-support (~> 3.11.0)
187
+ rspec-expectations (3.11.0)
66
188
  diff-lcs (>= 1.2.0, < 2.0)
67
- rspec-support (~> 3.10.0)
68
- rspec-mocks (3.10.2)
189
+ rspec-support (~> 3.11.0)
190
+ rspec-mocks (3.11.1)
69
191
  diff-lcs (>= 1.2.0, < 2.0)
70
- rspec-support (~> 3.10.0)
71
- rspec-support (3.10.2)
72
- rubocop (1.15.0)
192
+ rspec-support (~> 3.11.0)
193
+ rspec-rails (5.1.2)
194
+ actionpack (>= 5.2)
195
+ activesupport (>= 5.2)
196
+ railties (>= 5.2)
197
+ rspec-core (~> 3.10)
198
+ rspec-expectations (~> 3.10)
199
+ rspec-mocks (~> 3.10)
200
+ rspec-support (~> 3.10)
201
+ rspec-support (3.11.0)
202
+ rubocop (1.30.1)
73
203
  parallel (~> 1.10)
74
- parser (>= 3.0.0.0)
204
+ parser (>= 3.1.0.0)
75
205
  rainbow (>= 2.2.2, < 4.0)
76
206
  regexp_parser (>= 1.8, < 3.0)
77
- rexml
78
- rubocop-ast (>= 1.5.0, < 2.0)
207
+ rexml (>= 3.2.5, < 4.0)
208
+ rubocop-ast (>= 1.18.0, < 2.0)
79
209
  ruby-progressbar (~> 1.7)
80
210
  unicode-display_width (>= 1.4.0, < 3.0)
81
- rubocop-ast (1.5.0)
82
- parser (>= 3.0.1.1)
83
- rubocop-performance (1.11.3)
211
+ rubocop-ast (1.18.0)
212
+ parser (>= 3.1.1.0)
213
+ rubocop-performance (1.14.2)
84
214
  rubocop (>= 1.7.0, < 2.0)
85
215
  rubocop-ast (>= 0.4.0)
86
- rubocop-rails (2.10.1)
216
+ rubocop-rails (2.15.0)
87
217
  activesupport (>= 4.2.0)
88
218
  rack (>= 1.1)
89
219
  rubocop (>= 1.7.0, < 2.0)
@@ -93,34 +223,42 @@ GEM
93
223
  simplecov-html (~> 0.11)
94
224
  simplecov_json_formatter (~> 0.1)
95
225
  simplecov-html (0.12.3)
96
- simplecov_json_formatter (0.1.3)
226
+ simplecov_json_formatter (0.1.4)
227
+ strscan (3.0.3)
97
228
  sync (0.5.0)
98
229
  term-ansicolor (1.7.1)
99
230
  tins (~> 1.0)
100
- terminal-table (3.0.1)
231
+ terminal-table (3.0.2)
101
232
  unicode-display_width (>= 1.1.1, < 3)
102
- thor (1.1.0)
103
- tins (1.29.1)
233
+ thor (1.2.1)
234
+ timeout (0.3.0)
235
+ tins (1.31.1)
104
236
  sync
105
237
  tzinfo (2.0.4)
106
238
  concurrent-ruby (~> 1.0)
107
- unicode-display_width (2.0.0)
239
+ unicode-display_width (2.1.0)
108
240
  webrick (1.7.0)
241
+ websocket-driver (0.7.5)
242
+ websocket-extensions (>= 0.1.0)
243
+ websocket-extensions (0.1.5)
109
244
  with_progress (1.0.1)
110
245
  ruby-progressbar (~> 1.4)
111
- zeitwerk (2.4.2)
246
+ zeitwerk (2.6.0)
112
247
 
113
248
  PLATFORMS
114
249
  ruby
115
250
 
116
251
  DEPENDENCIES
117
252
  bundler (~> 2.0)
253
+ byebug
118
254
  climate_control
119
255
  codecov
120
256
  deep-cover
121
257
  promenade!
122
- rake (~> 13.0)
123
- rspec (~> 3.0)
258
+ rails (> 3.0, < 8.0)
259
+ rake
260
+ rspec (~> 3.11)
261
+ rspec-rails (~> 5.1)
124
262
  rubocop
125
263
  rubocop-performance
126
264
  rubocop-rails
data/README.md CHANGED
@@ -129,6 +129,58 @@ This is ideal if you are worried about accidentally exposing your metrics, are c
129
129
 
130
130
  The exporter runs by default on port `9394` and the metrics are available at the standard path of `/metrics`, the stand-alone exporter is configured to use gzip.
131
131
 
132
+
133
+ ### Rails Middleware
134
+
135
+ Promenade provides custom Rack middleware to track HTTP response times for requests in your Rails application.
136
+
137
+ This was originally inspired by [prometheus-client-mmap](https://gitlab.com/gitlab-org/prometheus-client-mmap/-/blob/master/lib/prometheus/client/rack/collector.rb).
138
+
139
+ **This middleware is automatically added to your Rack stack if your application is a Ruby on Rails app.**
140
+
141
+ We recommend you add the middleware after `ActionDispatch::ShowExceptions` in your stack, so you can accurately record the controller and action where an exception was raised.
142
+
143
+ If you want to change the position, or customise the labels and exception handling behaviour, simply remove the middleware from the stack and re-insert it with your own preferences.
144
+
145
+ ``` ruby
146
+ Rails.application.middleware.delete(Promenade::Client::Rack::Collector)
147
+ Rails.application.middleware.insert_after(Rails::Rack::Logger, Promenade::Client::Rack::Collector)
148
+ ```
149
+
150
+ #### Customising the labels recorded for each request
151
+
152
+ If you would like to collect different labels with each request, you may do so by customising the middleware installation:
153
+
154
+ ``` ruby
155
+ label_builder = Proc.new do |env|
156
+ {
157
+ method: env["REQUEST_METHOD"].to_s.downcase,
158
+ host: env["HTTP_HOST"].to_s,
159
+ controller: env.dig("action_dispatch.request.parameters", "controller") || "unknown",
160
+ action: env.dig("action_dispatch.request.parameters", "action") || "unknown"
161
+ }
162
+ end
163
+ Rails.application.config.middleware.insert_after ActionDispatch::ShowExceptions,
164
+ Promenade::Client::Rack::Collector
165
+ label_builder: label_builder
166
+ ```
167
+
168
+ #### Customising how the middleware handles exceptions
169
+
170
+ The default implementation will capture exceptions, count the execption class name (e.g. `"StandardError"`), and then re-raise the exception.
171
+
172
+ If you would like to customise this behaviour, you may do so by customising the middleware installation:
173
+
174
+ ``` ruby
175
+ exception_handler = Proc.new do |exception, exception_counter, env_hash, request_duration_seconds|
176
+ # This simple example just re-raises the execption
177
+ raise exception
178
+ end
179
+ Rails.application.config.middleware.insert_after ActionDispatch::ShowExceptions,
180
+ Promenade::Client::Rack::Collector
181
+ exception_handler: exception_handler
182
+ ```
183
+
132
184
  ### Configuration
133
185
 
134
186
  If you are using rails it should load a railtie and configure promenade.
data/bin/integration_test CHANGED
@@ -4,14 +4,23 @@ require "bundler/setup"
4
4
  require "promenade"
5
5
  require "fileutils"
6
6
  require "net/http"
7
+ require "./spec/support/integration_tests/metrics_line"
8
+ require "./spec/support/integration_tests/label_value"
7
9
 
8
10
  def test_http_body(expected)
11
+ expectation = MetricsLine.new(expected)
9
12
  uri = URI("http://localhost:9394/metrics")
10
13
 
11
14
  Net::HTTP.start(uri.host, uri.port) do |http|
12
15
  request = Net::HTTP::Get.new uri
13
16
  response = http.request request
14
- fail "#{response.body} didn't include #{expected}" unless response.body.split("\n").any?(expected)
17
+ unless response.body.each_line.detect do |string|
18
+ next if string.start_with?("#")
19
+
20
+ MetricsLine.new(string) == expectation
21
+ end
22
+ fail "#{response.body} didn't include #{expected}"
23
+ end
15
24
  end
16
25
  end
17
26
 
data/bin/rails ADDED
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+ # This command will automatically be run when you run "rails" with Rails gems
3
+ # installed from the root of your application.
4
+
5
+ ENGINE_ROOT = File.expand_path("..", __dir__)
6
+ ENGINE_PATH = File.expand_path("../lib/promenade/engine", __dir__)
7
+ APP_PATH = File.expand_path("../spec/dummy/config/application", __dir__)
8
+
9
+ # Set up gems listed in the Gemfile.
10
+ ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../Gemfile", __dir__)
11
+ require "bundler/setup" if File.exist?(ENV["BUNDLE_GEMFILE"])
12
+
13
+ require "rails/all"
14
+ require "rails/engine/commands"
@@ -0,0 +1,111 @@
1
+ require "prometheus/client"
2
+ require_relative "request_labeler"
3
+ require_relative "exception_handler"
4
+
5
+ module Promenade
6
+ module Client
7
+ module Rack
8
+ # Original code taken from Prometheus Client MMap
9
+ # https://gitlab.com/gitlab-org/prometheus-client-mmap/-/blob/master/lib/prometheus/client/rack/collector.rb
10
+ #
11
+ # Collector is a Rack middleware that provides a sample implementation of
12
+ # a HTTP tracer. The default label builder can be modified to export a
13
+ # different set of labels per recorded metric.
14
+ class Collector
15
+ REQUEST_METHOD = "REQUEST_METHOD".freeze
16
+
17
+ HTTP_HOST = "HTTP_HOST".freeze
18
+
19
+ PATH_INFO = "PATH_INFO".freeze
20
+
21
+ HISTOGRAM_NAME = :http_req_duration_seconds
22
+
23
+ REQUESTS_COUNTER_NAME = :http_requests_total
24
+
25
+ EXCEPTIONS_COUNTER_NAME = :http_exceptions_total
26
+
27
+ private_constant *%i(
28
+ REQUEST_METHOD
29
+ HTTP_HOST
30
+ PATH_INFO
31
+ HISTOGRAM_NAME
32
+ REQUESTS_COUNTER_NAME
33
+ EXCEPTIONS_COUNTER_NAME
34
+ )
35
+
36
+ def initialize(app,
37
+ registry: ::Prometheus::Client.registry,
38
+ label_builder: RequestLabeler,
39
+ exception_handler: nil)
40
+ @app = app
41
+ @registry = registry
42
+ @label_builder = label_builder
43
+ @exception_handler = exception_handler || default_exception_handler
44
+ register_metrics!
45
+ end
46
+
47
+ def call(env)
48
+ trace(env) { app.call(env) }
49
+ end
50
+
51
+ private
52
+
53
+ attr_reader :app,
54
+ :registry,
55
+ :label_builder,
56
+ :exception_handler
57
+
58
+ def trace(env)
59
+ start = current_time
60
+ begin
61
+ response = yield
62
+ record(labels(env, response), duration_since(start))
63
+ response
64
+ rescue StandardError => e
65
+ exception_handler.call(e, env, duration_since(start))
66
+ end
67
+ end
68
+
69
+ def labels(env, response)
70
+ label_builder.call(env).merge!(code: response.first.to_s)
71
+ end
72
+
73
+ def record(labels, duration)
74
+ requests_counter.increment(labels)
75
+ durations_histogram.observe(labels, duration)
76
+ end
77
+
78
+ def duration_since(start_time)
79
+ current_time - start_time
80
+ end
81
+
82
+ def current_time
83
+ Process.clock_gettime(Process::CLOCK_MONOTONIC)
84
+ end
85
+
86
+ def durations_histogram
87
+ registry.get(HISTOGRAM_NAME)
88
+ end
89
+
90
+ def requests_counter
91
+ registry.get(REQUESTS_COUNTER_NAME)
92
+ end
93
+
94
+ def register_metrics!
95
+ registry.counter(REQUESTS_COUNTER_NAME, "A counter of the total number of HTTP requests made.")
96
+ registry.histogram(HISTOGRAM_NAME, "A histogram of the response latency.")
97
+ registry.counter(EXCEPTIONS_COUNTER_NAME, "A counter of the total number of exceptions raised.")
98
+ end
99
+
100
+ def default_exception_handler
101
+ ExceptionHandler.initialize_singleton(
102
+ histogram_name: HISTOGRAM_NAME,
103
+ requests_counter_name: REQUESTS_COUNTER_NAME,
104
+ exceptions_counter_name: EXCEPTIONS_COUNTER_NAME,
105
+ registry: registry,
106
+ )
107
+ end
108
+ end
109
+ end
110
+ end
111
+ end
@@ -0,0 +1,51 @@
1
+ require "action_dispatch/middleware/exception_wrapper"
2
+ require_relative "singleton_caller"
3
+ require_relative "request_labeler"
4
+
5
+ module Promenade
6
+ module Client
7
+ module Rack
8
+ class ExceptionHandler
9
+ extend SingletonCaller
10
+
11
+ attr_reader :histogram_name, :requests_counter_name, :exceptions_counter_name, :registry
12
+
13
+ def initialize(histogram_name:, requests_counter_name:, exceptions_counter_name:, registry:)
14
+ @histogram_name = histogram_name
15
+ @requests_counter_name = requests_counter_name
16
+ @exceptions_counter_name = exceptions_counter_name
17
+ @registry = registry
18
+ end
19
+
20
+ def call(exception, env_hash, duration)
21
+ labels = RequestLabeler.call(env_hash)
22
+ labels.merge!(code: status_code_for_exception(exception))
23
+
24
+ histogram.observe(labels, duration.to_f)
25
+ requests_counter.increment(labels)
26
+ exceptions_counter.increment(exception: exception.class.name)
27
+
28
+ raise exception
29
+ end
30
+
31
+ private
32
+
33
+ def histogram
34
+ registry.get(histogram_name)
35
+ end
36
+
37
+ def requests_counter
38
+ registry.get(requests_counter_name)
39
+ end
40
+
41
+ def exceptions_counter
42
+ registry.get(exceptions_counter_name)
43
+ end
44
+
45
+ def status_code_for_exception(exception)
46
+ ActionDispatch::ExceptionWrapper.new(nil, exception).status_code.to_s
47
+ end
48
+ end
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,50 @@
1
+ module Promenade
2
+ module Client
3
+ module Rack
4
+ class RequestLabeler
5
+ require_relative "singleton_caller"
6
+ extend SingletonCaller
7
+
8
+ REQUEST_METHOD = "REQUEST_METHOD".freeze
9
+
10
+ HTTP_HOST = "HTTP_HOST".freeze
11
+
12
+ PARAMS_KEY = "action_dispatch.request.parameters".freeze
13
+
14
+ PATH_PARAMS_KEY = "action_dispatch.request.path_parameters".freeze
15
+
16
+ CONTROLLER = "controller".freeze
17
+
18
+ ACTION = "action".freeze
19
+
20
+ UNKNOWN = "unknown".freeze
21
+
22
+ SEPARATOR = "#".freeze
23
+
24
+ private_constant :REQUEST_METHOD, :HTTP_HOST, :PARAMS_KEY, :CONTROLLER, :ACTION, :UNKNOWN, :SEPARATOR
25
+
26
+ def call(env)
27
+ {
28
+ method: env[REQUEST_METHOD].to_s.downcase,
29
+ host: env[HTTP_HOST].to_s,
30
+ controller_action: controller_action_from_env(env),
31
+ }
32
+ end
33
+
34
+ private
35
+
36
+ def controller_action_from_env(env)
37
+ controller = env.dig(PARAMS_KEY, CONTROLLER) ||
38
+ env.dig(PATH_PARAMS_KEY, CONTROLLER.to_sym) ||
39
+ UNKNOWN
40
+
41
+ action = env.dig(PARAMS_KEY, ACTION) ||
42
+ env.dig(PATH_PARAMS_KEY, ACTION.to_sym) ||
43
+ UNKNOWN
44
+
45
+ [controller, action].join(SEPARATOR)
46
+ end
47
+ end
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,19 @@
1
+ module Promenade
2
+ module Client
3
+ module Rack
4
+ module SingletonCaller
5
+ def initialize_singleton(...)
6
+ @singleton = new(...)
7
+ end
8
+
9
+ def call(...)
10
+ singleton.call(...)
11
+ end
12
+
13
+ def singleton
14
+ @singleton || initialize_singleton
15
+ end
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,5 @@
1
+ module Promenade
2
+ class Engine < ::Rails::Engine
3
+ isolate_namespace Promenade
4
+ end
5
+ end
@@ -35,7 +35,7 @@ module Promenade
35
35
  class Options
36
36
  BUCKET_PRESETS = {
37
37
  network: [0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1, 2.5, 5, 10].freeze,
38
- memory: (0..10).map { |i| 128 * 2**i },
38
+ memory: (0..10).map { |i| 128 * (2**i) },
39
39
  }.freeze
40
40
 
41
41
  def initialize
@@ -1,9 +1,13 @@
1
1
  require "promenade/setup"
2
+ require "promenade/engine"
3
+ require "promenade/client/rack/collector"
2
4
 
3
5
  module Promenade
4
6
  class Railtie < ::Rails::Railtie
5
7
  initializer "promenade.configure_rails_initialization" do
6
8
  Promenade.setup
9
+ Rails.application.config.middleware.insert_after ActionDispatch::ShowExceptions,
10
+ Promenade::Client::Rack::Collector
7
11
  end
8
12
  end
9
13
  end
@@ -1,16 +1,25 @@
1
1
  require "pathname"
2
2
 
3
3
  module Promenade
4
- def self.root_dir
5
- rails_root = defined?(Rails) && Rails.root
6
- rails_root || Pathname.new(ENV.fetch("RAILS_ROOT", Dir.pwd))
4
+ module_function
5
+
6
+ def root_dir
7
+ if rails_defined?
8
+ Rails.root
9
+ else
10
+ Pathname.new(ENV.fetch("RAILS_ROOT", Dir.pwd))
11
+ end
12
+ end
13
+
14
+ def rails_defined?
15
+ defined?(Rails)
7
16
  end
8
17
 
9
- def self.multiprocess_files_dir
18
+ def multiprocess_files_dir
10
19
  ENV.fetch("PROMETHEUS_MULTIPROC_DIR", root_dir.join("tmp", "promenade"))
11
20
  end
12
21
 
13
- def self.setup
22
+ def setup
14
23
  unless File.directory? multiprocess_files_dir
15
24
  FileUtils.mkdir_p multiprocess_files_dir
16
25
  end
@@ -1,3 +1,3 @@
1
1
  module Promenade
2
- VERSION = "0.2.2".freeze
2
+ VERSION = "0.4.0".freeze
3
3
  end
data/promenade.gemspec CHANGED
@@ -22,19 +22,23 @@ Gem::Specification.new do |spec| # rubocop:disable Metrics/BlockLength
22
22
  spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
23
23
  spec.require_paths = ["lib"]
24
24
 
25
- spec.required_ruby_version = ">= 2.5", "< 4"
25
+ spec.required_ruby_version = ">= 2.7", "< 3.2"
26
26
 
27
- spec.add_dependency "activesupport"
28
- spec.add_dependency "prometheus-client-mmap", "~> 0.12.0"
27
+ spec.add_dependency "actionpack"
28
+ spec.add_dependency "activesupport", "> 6.0", "< 8.0"
29
+ spec.add_dependency "prometheus-client-mmap", "~> 0.16.0"
29
30
  spec.add_dependency "rack"
30
-
31
31
  spec.add_development_dependency "bundler", "~> 2.0"
32
+ spec.add_development_dependency "byebug"
32
33
  spec.add_development_dependency "climate_control"
33
34
  spec.add_development_dependency "deep-cover"
34
- spec.add_development_dependency "rake", "~> 13.0"
35
- spec.add_development_dependency "rspec", "~> 3.0"
35
+ spec.add_development_dependency "rails", "> 3.0", "< 8.0"
36
+ spec.add_development_dependency "rake"
37
+ spec.add_development_dependency "rspec", "~> 3.11"
38
+ spec.add_development_dependency "rspec-rails", "~> 5.1"
36
39
  spec.add_development_dependency "rubocop"
37
40
  spec.add_development_dependency "rubocop-performance"
38
41
  spec.add_development_dependency "rubocop-rails"
39
42
  spec.add_development_dependency "simplecov"
43
+ spec.metadata["rubygems_mfa_required"] = "true"
40
44
  end
metadata CHANGED
@@ -1,17 +1,17 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: promenade
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.2
4
+ version: 0.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ed Robinson
8
- autorequire:
8
+ autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2021-05-21 00:00:00.000000000 Z
11
+ date: 2022-06-16 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
- name: activesupport
14
+ name: actionpack
15
15
  requirement: !ruby/object:Gem::Requirement
16
16
  requirements:
17
17
  - - ">="
@@ -24,20 +24,40 @@ dependencies:
24
24
  - - ">="
25
25
  - !ruby/object:Gem::Version
26
26
  version: '0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: activesupport
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">"
32
+ - !ruby/object:Gem::Version
33
+ version: '6.0'
34
+ - - "<"
35
+ - !ruby/object:Gem::Version
36
+ version: '8.0'
37
+ type: :runtime
38
+ prerelease: false
39
+ version_requirements: !ruby/object:Gem::Requirement
40
+ requirements:
41
+ - - ">"
42
+ - !ruby/object:Gem::Version
43
+ version: '6.0'
44
+ - - "<"
45
+ - !ruby/object:Gem::Version
46
+ version: '8.0'
27
47
  - !ruby/object:Gem::Dependency
28
48
  name: prometheus-client-mmap
29
49
  requirement: !ruby/object:Gem::Requirement
30
50
  requirements:
31
51
  - - "~>"
32
52
  - !ruby/object:Gem::Version
33
- version: 0.12.0
53
+ version: 0.16.0
34
54
  type: :runtime
35
55
  prerelease: false
36
56
  version_requirements: !ruby/object:Gem::Requirement
37
57
  requirements:
38
58
  - - "~>"
39
59
  - !ruby/object:Gem::Version
40
- version: 0.12.0
60
+ version: 0.16.0
41
61
  - !ruby/object:Gem::Dependency
42
62
  name: rack
43
63
  requirement: !ruby/object:Gem::Requirement
@@ -66,6 +86,20 @@ dependencies:
66
86
  - - "~>"
67
87
  - !ruby/object:Gem::Version
68
88
  version: '2.0'
89
+ - !ruby/object:Gem::Dependency
90
+ name: byebug
91
+ requirement: !ruby/object:Gem::Requirement
92
+ requirements:
93
+ - - ">="
94
+ - !ruby/object:Gem::Version
95
+ version: '0'
96
+ type: :development
97
+ prerelease: false
98
+ version_requirements: !ruby/object:Gem::Requirement
99
+ requirements:
100
+ - - ">="
101
+ - !ruby/object:Gem::Version
102
+ version: '0'
69
103
  - !ruby/object:Gem::Dependency
70
104
  name: climate_control
71
105
  requirement: !ruby/object:Gem::Requirement
@@ -94,34 +128,68 @@ dependencies:
94
128
  - - ">="
95
129
  - !ruby/object:Gem::Version
96
130
  version: '0'
131
+ - !ruby/object:Gem::Dependency
132
+ name: rails
133
+ requirement: !ruby/object:Gem::Requirement
134
+ requirements:
135
+ - - ">"
136
+ - !ruby/object:Gem::Version
137
+ version: '3.0'
138
+ - - "<"
139
+ - !ruby/object:Gem::Version
140
+ version: '8.0'
141
+ type: :development
142
+ prerelease: false
143
+ version_requirements: !ruby/object:Gem::Requirement
144
+ requirements:
145
+ - - ">"
146
+ - !ruby/object:Gem::Version
147
+ version: '3.0'
148
+ - - "<"
149
+ - !ruby/object:Gem::Version
150
+ version: '8.0'
97
151
  - !ruby/object:Gem::Dependency
98
152
  name: rake
153
+ requirement: !ruby/object:Gem::Requirement
154
+ requirements:
155
+ - - ">="
156
+ - !ruby/object:Gem::Version
157
+ version: '0'
158
+ type: :development
159
+ prerelease: false
160
+ version_requirements: !ruby/object:Gem::Requirement
161
+ requirements:
162
+ - - ">="
163
+ - !ruby/object:Gem::Version
164
+ version: '0'
165
+ - !ruby/object:Gem::Dependency
166
+ name: rspec
99
167
  requirement: !ruby/object:Gem::Requirement
100
168
  requirements:
101
169
  - - "~>"
102
170
  - !ruby/object:Gem::Version
103
- version: '13.0'
171
+ version: '3.11'
104
172
  type: :development
105
173
  prerelease: false
106
174
  version_requirements: !ruby/object:Gem::Requirement
107
175
  requirements:
108
176
  - - "~>"
109
177
  - !ruby/object:Gem::Version
110
- version: '13.0'
178
+ version: '3.11'
111
179
  - !ruby/object:Gem::Dependency
112
- name: rspec
180
+ name: rspec-rails
113
181
  requirement: !ruby/object:Gem::Requirement
114
182
  requirements:
115
183
  - - "~>"
116
184
  - !ruby/object:Gem::Version
117
- version: '3.0'
185
+ version: '5.1'
118
186
  type: :development
119
187
  prerelease: false
120
188
  version_requirements: !ruby/object:Gem::Requirement
121
189
  requirements:
122
190
  - - "~>"
123
191
  - !ruby/object:Gem::Version
124
- version: '3.0'
192
+ version: '5.1'
125
193
  - !ruby/object:Gem::Dependency
126
194
  name: rubocop
127
195
  requirement: !ruby/object:Gem::Requirement
@@ -178,7 +246,7 @@ dependencies:
178
246
  - - ">="
179
247
  - !ruby/object:Gem::Version
180
248
  version: '0'
181
- description:
249
+ description:
182
250
  email:
183
251
  - edward-robinson@cookpad.com
184
252
  executables:
@@ -199,9 +267,15 @@ files:
199
267
  - Rakefile
200
268
  - bin/console
201
269
  - bin/integration_test
270
+ - bin/rails
202
271
  - bin/setup
203
272
  - exe/promenade
204
273
  - lib/promenade.rb
274
+ - lib/promenade/client/rack/collector.rb
275
+ - lib/promenade/client/rack/exception_handler.rb
276
+ - lib/promenade/client/rack/request_labeler.rb
277
+ - lib/promenade/client/rack/singleton_caller.rb
278
+ - lib/promenade/engine.rb
205
279
  - lib/promenade/kafka.rb
206
280
  - lib/promenade/kafka/async_producer_subscriber.rb
207
281
  - lib/promenade/kafka/connection_subscriber.rb
@@ -217,8 +291,9 @@ files:
217
291
  homepage: https://github.com/errm/promenade
218
292
  licenses:
219
293
  - MIT
220
- metadata: {}
221
- post_install_message:
294
+ metadata:
295
+ rubygems_mfa_required: 'true'
296
+ post_install_message:
222
297
  rdoc_options: []
223
298
  require_paths:
224
299
  - lib
@@ -226,18 +301,18 @@ required_ruby_version: !ruby/object:Gem::Requirement
226
301
  requirements:
227
302
  - - ">="
228
303
  - !ruby/object:Gem::Version
229
- version: '2.5'
304
+ version: '2.7'
230
305
  - - "<"
231
306
  - !ruby/object:Gem::Version
232
- version: '4'
307
+ version: '3.2'
233
308
  required_rubygems_version: !ruby/object:Gem::Requirement
234
309
  requirements:
235
310
  - - ">="
236
311
  - !ruby/object:Gem::Version
237
312
  version: '0'
238
313
  requirements: []
239
- rubygems_version: 3.1.2
240
- signing_key:
314
+ rubygems_version: 3.3.8
315
+ signing_key:
241
316
  specification_version: 4
242
317
  summary: Promenade makes it simple to instrument Ruby apps for prometheus scraping
243
318
  test_files: []