rack-timeout 0.2.4 → 0.3.0.pre.beta.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: e81979fd4ab255202903c4725b6e606787a88488
4
- data.tar.gz: 50c5e794cee0ac985afc5d408fb8493f61de3090
3
+ metadata.gz: 0115bc1c87b36ff9e6f74cb104e0c8fb13550685
4
+ data.tar.gz: d2eaf62f154d099701b263db687897e15d3104c5
5
5
  SHA512:
6
- metadata.gz: eed8aa26bff0e4b772202c99fe14cd3ac52c0a36be3df866db8b47badc198beef350f47eb6f4c2700d65a7247201fbb5eb09752789a8db58fd22fcebff19f223
7
- data.tar.gz: 6fb59f55bd49cd1db83c09560cc3ec81bf6e157b85ebd001aa06081c96616bd08cacbd197eb0de7cbdaf29e1b0e85e66cc77cef5801fb47a66be219761d5143a
6
+ metadata.gz: 61f9683c26d18d2da7b0b9d51834e86865af64b72ab2a54eea780b9522724c641a6156366122b9047f630298dd89d15c5d859ec539e88de0668a529aa2f71933
7
+ data.tar.gz: 75c7d46b222be49a805e992f3d7c3a9abe769614cfc7edbb65393d9a7922ad6fab5de75a5c607ac8cee2fb629822fcb3bbd3bef37cccd4c74cf0799fb3fafcc7
data/CHANGELOG ADDED
@@ -0,0 +1,59 @@
1
+ 0.3.0-beta.1
2
+ ============
3
+ - instead of inserting middleware at position 0 for rails, insert before Rack::Runtime (which is right after Rack::Lock and the static file stuff)
4
+ - reshuffle error types: RequestExpiryError is again a RuntimeError, and timeouts raise a RequestTimeoutException, an Exception, and not descending from Rack::Timeout::Error (see README for more)
5
+ - don't insert middleware for rails in test environment
6
+ - add convenience module Rack::Timeout::Logger (see README for more)
7
+ - StageChangeLoggingObserver renamed to StageChangeLoggingObserver, works slightly differently too
8
+ - file layout reorganization (see 6e82c276 for details)
9
+ - CHANGELOG file is now in the gem (@dbackeus)
10
+ - add optional and experimental support for grouping errors by url under rollbar. see "rack/timeout/rollbar" for usage
11
+
12
+ 0.2.4
13
+ =====
14
+ - Previous fix was borked.
15
+
16
+ 0.2.3
17
+ =====
18
+ - Ignore Rack::NullLogger when picking a logger
19
+
20
+ 0.2.1
21
+ =====
22
+ - Fix raised error messages
23
+
24
+ 0.2.0
25
+ =====
26
+ - Added CHANGELOG
27
+ - Rack::Timeout::Error now inherits from Exception instead of StandardError, with the hope users won't rescue from it accidentally
28
+
29
+ 0.1.2
30
+ =====
31
+ - improve RequestTimeoutError error string so @watsonian is happy
32
+
33
+ 0.1.1
34
+ =====
35
+ - README updates
36
+ - fix that setting properties to false resulted in an error
37
+
38
+ 0.1.0
39
+ =====
40
+ - Rewrote README
41
+
42
+ 0.1.0beta4
43
+ ==========
44
+ - Renamed `timeout` setting to `service_timeout`; `timeout=` still works for backwards compatibility
45
+ – `MAX_REQUEST_AGE` is gone, the `wait_timeout` setting more or less replaces it
46
+ - Renamed `overtime` setting to `wait_overtime`
47
+ - overtime setting should actually work (It had never made it to beta3)
48
+ - In the request info struct, renamed `age` to `wait`, `duration` to `service`
49
+ - Rack::Timeout::StageChangeLogger is gone, replaced by Rack::Timeout::StageChangeLoggingObserver, which is an observer class that composites with a logger, instead of inheriting from Logger. Anything logging related will likely be incompatible with previous beta release.
50
+ - Log level can no longer be set with env vars, has to be set in the logger being used. (Which can now be custom / user-provided.)
51
+
52
+ 0.1.0beta1,2,3
53
+ ==============
54
+ - Dropped ruby 1.8.x support
55
+ - Dropped rails 2 support
56
+ - Added rails 4 support
57
+ - Added much logging
58
+ – Added support for dropping requests that waited too long in the queue without ever handling them
59
+ - Other things I can't remember, see git logs :P
data/README.markdown CHANGED
@@ -1,13 +1,13 @@
1
1
  Rack::Timeout
2
2
  =============
3
3
 
4
- Abort requests that are taking too long; a subclass of `Rack::Timeout::Error` is raised.
4
+ Abort requests that are taking too long; an exception is raised.
5
5
 
6
6
  A generous timeout of 15s is the default. It's recommended to set the timeout as low as realistically viable for your application. Most applications will do fine with a setting between 2 and 5 seconds.
7
7
 
8
8
  There's a handful of other settings, read on for details.
9
9
 
10
- Rack::Timeout is not a solution to the problem of long-running requests, it's a debug and remediation tool. App developers should track rack-timeout's data and address recurring instances of particular timeouts, for example by refactoring code so it runs faster or offseting lengthy work to happen asynchronously.
10
+ Rack::Timeout is not a solution to the problem of long-running requests, it's a debug and remediation tool. App developers should track rack-timeout's data and address recurring instances of particular timeouts, for example by refactoring code so it runs faster or offsetting lengthy work to happen asynchronously.
11
11
 
12
12
 
13
13
  Basic Usage
@@ -22,13 +22,34 @@ The following covers currently supported versions of Rails, Rack, Ruby, and Bund
22
22
  gem "rack-timeout"
23
23
  ```
24
24
 
25
- That's all that's required if you want to use the default timeout of 15s. To use a custom timeout, create an initializer file:
25
+ That'll load rack-timeout and set it up as a Rails middleware using the default timeout of 15s. The middleware is not inserted for the test environment.
26
+
27
+ To use a custom timeout, create an initializer file:
26
28
 
27
29
  ```ruby
28
30
  # config/initializers/timeout.rb
29
31
  Rack::Timeout.timeout = 5 # seconds
30
32
  ```
31
33
 
34
+ ### Rails apps, manually
35
+
36
+ You'll need to do this if you removed `Rack::Runtime` from the middleware stack, or if you want to determine yourself where in the stack `Rack::Timeout` gets inserted.
37
+
38
+ ```ruby
39
+ # Gemfile
40
+ gem "rack-timeout", require:"rack/timeout/base"
41
+ ```
42
+
43
+ ```ruby
44
+ # config/initializers/timeout.rb
45
+
46
+ # insert middleware wherever you want in the stack
47
+ Rails.application.config.middleware.insert_before Rack::Runtime, Rack::Timeout
48
+
49
+ # customize seconds before timeout
50
+ Rack::Timeout.timeout = 5
51
+ ```
52
+
32
53
  ### Sinatra and other Rack apps
33
54
 
34
55
  ```ruby
@@ -46,7 +67,7 @@ The Rabbit Hole
46
67
 
47
68
  `Rack::Timeout.timeout` (or `Rack::Timeout.service_timeout`) is our principal setting.
48
69
 
49
- *Service time* is the time taken from when a request first enters rack to when its response is sent back. When the application takes longer than `service_timeout` to process a request, the request's status is logged as `timed_out` and a `Rack::Timeout::RequestTimeoutError` error is raised on the application thread. This may be automatically caught by the framework or plugins, so beware. Also, the error is not guaranteed to be raised in a timely fashion, see section below about IO blocks.
70
+ *Service time* is the time taken from when a request first enters rack to when its response is sent back. When the application takes longer than `service_timeout` to process a request, the request's status is logged as `timed_out` and `Rack::Timeout::RequestTimeoutException` or `Rack::Timeout::RequestTimeoutError` is raised on the application thread. This may be automatically caught by the framework or plugins, so beware. Also, the exception is not guaranteed to be raised in a timely fashion, see section below about IO blocks.
50
71
 
51
72
  Service timeout can be disabled entirely by setting the property to `0` or `false`, at which point the request skips Rack::Timeout's machinery (so no logging will be present).
52
73
 
@@ -104,7 +125,7 @@ More detailed explanations of the issues surrounding timing out in ruby during I
104
125
 
105
126
  ### Timing Out Inherently Unsafe
106
127
 
107
- Raising mid-flight in stateful applications is inherently unsafe. A request can be aborted at any moment in the code flow, and the application cam be left in an inconsistent state. There's little way rack-timeout could be aware of ongoing state changes. Applications that rely on a set of globals (like class variables) or any other state that lives beyond a single request may find those left in an unexpected/inconsistent state after an aborted request. Some cleanup code might not have run, or only half of a set of related changes may have been applied.
128
+ Raising mid-flight in stateful applications is inherently unsafe. A request can be aborted at any moment in the code flow, and the application can be left in an inconsistent state. There's little way rack-timeout could be aware of ongoing state changes. Applications that rely on a set of globals (like class variables) or any other state that lives beyond a single request may find those left in an unexpected/inconsistent state after an aborted request. Some cleanup code might not have run, or only half of a set of related changes may have been applied.
108
129
 
109
130
  A lot more can go wrong. An intricate explanation of the issue by JRuby's Charles Nutter can be found [here][broken-timeout].
110
131
 
@@ -136,13 +157,13 @@ The value of that entry is an instance of `Rack::Timeout::RequestDetails`, which
136
157
 
137
158
  * `state`: the possible states, and their log level, are:
138
159
 
139
- * `expired` (`ERROR`): the request is considered too old and is skipped entirely. This happens when `X-Request-Start` is present and older than `wait_timeout`. When in this state, a `Rack::Timeout::RequestExpiryError` exception is raised. See earlier discussion about the `wait_overtime` setting, too.
160
+ * `expired` (`ERROR`): the request is considered too old and is skipped entirely. This happens when `X-Request-Start` is present and older than `wait_timeout`. When in this state, `Rack::Timeout::RequestExpiryError` is raised. See earlier discussion about the `wait_overtime` setting, too.
140
161
 
141
162
  * `ready` (`INFO`): this is the state a request is in right before it's passed down the middleware chain. Once it's being processed, it'll move on to `active`, and then on to `timed_out` and/or `completed`.
142
163
 
143
164
  * `active` (`DEBUG`): the request is being actively processed in the application thread. This is signaled repeatedly every ~1s until the request completes or times out.
144
165
 
145
- * `timed_out` (`ERROR`): the request ran for longer than the determined timeout and was aborted. A `Rack::Timeout::RequestTimeoutError` error is raised in the application when this occurs. If this error gets caught, handled, and not re-raised in the app or framework (which will generally happen with Rails and Sinatra), this state will not be final, `completed` will be set after the framework is done with it.
166
+ * `timed_out` (`ERROR`): the request ran for longer than the determined timeout and was aborted. `Rack::Timeout::RequestTimeoutException` is raised in the application when this occurs. If this exception gets caught, handled, and not re-raised in the app or framework (which will generally happen with Rails and Sinatra), this state will not be final, `completed` will be set after the framework is done with it. (If the exception does bubble up, it's caught by rack-timeout and re-raised as `Rack::Timeout::RequestTimeoutError`, which descends from RuntimeError.)
146
167
 
147
168
  * `completed` (`INFO`): the request completed and Rack::Timeout is done with it. This does not mean the request completed *successfully*. Rack::Timeout does not concern itself with that. As mentioned just above, a timed out request may still end up with a `completed` state if the framework has dealt with the timeout exception.
148
169
 
@@ -150,17 +171,23 @@ The value of that entry is an instance of `Rack::Timeout::RequestDetails`, which
150
171
  Errors
151
172
  ------
152
173
 
153
- Rack::Timeout can raise two types of exceptions. Both descend from `Rack::Timeout::Error`, which itself descends from `RuntimeError`. They are:
174
+ Rack::Timeout can raise three types of exceptions. They are:
154
175
 
155
- * `Rack::Timeout::RequestTimeoutError`: this is raised when a request has run for longer than the specified timeout. It's raised by the rack-timeout timer thread in the application thread, at the point in the stack the app happens to be in when the timeout is triggered. This exception can generally be caught within the application, but in doing so you're working past the timeout. This is ok for quick cleanup work but shouldn't be abused as Rack::Timeout will not kick in twice for the same request.
176
+ Two descend from `Rack::Timeout::Error`, which itself descends from `RuntimeError` and is generally caught by an unqualified `raise`. The third, `RequestTimeoutException`, is more complicated and the most important.
156
177
 
157
178
  * `Rack::Timeout::RequestExpiryError`: this is raised when a request is skipped for being too old (see Wait Timeout section). This error cannot generally be rescued from inside a Rails controller action as it happens before the request has a chance to enter Rails.
158
179
 
159
180
  This shouldn't be different for other frameworks, unless you have something above Rack::Timeout in the middleware stack, which you generally shouldn't.
160
181
 
182
+ * `Rack::Timeout::RequestTimeoutException`: this is raised when a request has run for longer than the specified timeout. This descends from `Exception`, not from `Rack::Timeout::Error` (it has to be rescued from explicitly). It's raised by the rack-timeout timer thread in the application thread, at the point in the stack the app happens to be in when the timeout is triggered. This exception could be caught explicitly within the application, but in doing so you're working past the timeout. This is ok for quick cleanup work but shouldn't be abused as Rack::Timeout will not kick in twice for the same request.
183
+
184
+ Rails will generally intercept `Exception`s, but in plain Rack apps, this exception will be caught by rack-timeout and re-raised as a `Rack::Timeout::RequestTimeoutError`. This is to prevent an `Exception` from bubbling up beyond rack-timeout and to the server.
185
+
186
+ * `Rack::Timeout::RequestTimeoutError` is the third type, it descends from `Rack::Timeout::Error`, but it's only really seen in the case described above. It'll not be seen in a standard Rails app, and will only be seen in Sinatra if rescuing from exceptions is disabled.
187
+
161
188
  You shouldn't rescue from these errors for reporting purposes. Instead, you can subscribe for state change notifications with observers.
162
189
 
163
- If you're trying to test that a `Rack::Timeout::RequestTimeoutError` is raised in an action in your Rails application, you **must do so in integration tests**. Please note that Rack::Timeout will not kick in for functional tests as they bypass the rack middleware stack.
190
+ If you're trying to test that a `Rack::Timeout::RequestTimeoutException` is raised in an action in your Rails application, you **must do so in integration tests**. Please note that Rack::Timeout will not kick in for functional tests as they bypass the rack middleware stack.
164
191
 
165
192
  [More details about testing middleware with Rails here][pablobm].
166
193
 
@@ -187,7 +214,7 @@ Rack::Timeout.unregister_state_change_observer(:a_unique_name)
187
214
  ```
188
215
 
189
216
 
190
- rack-timeout's logging is implemented using an observer; see `Rack::Timeout::StageChangeLoggingObserver` in logger.rb for the implementation.
217
+ rack-timeout's logging is implemented using an observer; see `Rack::Timeout::StateChangeLoggingObserver` in logging-observer.rb for the implementation.
191
218
 
192
219
  Custom observers might be used to do cleanup, store statistics on request length, timeouts, etc., and potentially do performance tuning on the fly.
193
220
 
@@ -201,18 +228,27 @@ Request state changes into `timed_out` and `expired` are logged at the `ERROR` l
201
228
 
202
229
  Rack::Timeout will try to use `Rails.logger` if present, otherwise it'll look for a logger in `env['rack.logger']`, and if neither are present, it'll create its own logger, either writing to `env['rack.errors']`, or to `$stderr` if the former is not set.
203
230
 
204
- A custom logger can be set via `Rack::Timeout::StageChangeLoggingObserver.logger`. This takes priority over the automatic logger detection:
231
+ When creating its own logger, rack-timeout will use a log level of `INFO`. Otherwise whatever log level is already set on the logger being used continues in effect.
232
+
233
+ A custom logger can be set via `Rack::Timeout::Logger.logger`. This takes priority over the automatic logger detection:
205
234
 
206
235
  ```ruby
207
- Rack::Timeout::StageChangeLoggingObserver.logger = Logger.new
236
+ Rack::Timeout::Logger.logger = Logger.new
208
237
  ```
209
238
 
210
- When creating its own logger, rack-timeout will use a log level of `INFO`. Otherwise whatever log level is already set on the logger being used continues in effect.
239
+ There are helper setters that replace the logger:
240
+
241
+ ```ruby
242
+ Rack::Timeout::Logger.device = $stderr
243
+ Rack::Timeout::Logger.level = Logger::INFO
244
+ ```
245
+
246
+ Although each call replaces the logger, these can be use together and the final logger will retain both properties. (If only one is called, the defaults used above apply.)
211
247
 
212
- Logging is enabled by default if Rack::Timeout is loaded via the `rack-timeout` file (recommended), but can be removed by unregistering its observer:
248
+ Logging is enabled by default, but can be removed with:
213
249
 
214
250
  ```ruby
215
- Rack::Timeout.unregister_state_change_observer(:logger)
251
+ Rack::Timeout::Logger.disable
216
252
  ```
217
253
 
218
254
  Each log line is a set of `key=value` pairs, containing the entries from the `env["rack-timeout.info"]` struct that are not `nil`. See the Request Lifetime section above for a description of each field. Note that while the values for `wait`, `timeout`, and `service` are stored internally as seconds, they are logged as milliseconds for readability.
@@ -237,5 +273,5 @@ For applications running Ruby 1.8.x and/or Rails 2.x, use [version 0.0.4][v0.0.4
237
273
 
238
274
 
239
275
  ---
240
- Copyright © 2010-2014 Caio Chassot, released under the MIT license
276
+ Copyright © 2010-2015 Caio Chassot, released under the MIT license
241
277
  <http://github.com/heroku/rack-timeout>
data/lib/rack-timeout.rb CHANGED
@@ -1,11 +1,2 @@
1
- # encoding: utf-8
2
- require 'rack/timeout'
3
- require 'rack/timeout/logger'
4
-
5
- if defined?(Rails) && [3,4].include?(Rails::VERSION::MAJOR)
6
- class Rack::Timeout::Railtie < Rails::Railtie
7
- initializer('rack-timeout.prepend') { |app| app.config.middleware.insert 0, Rack::Timeout }
8
- end
9
- end
10
-
11
- Rack::Timeout::StageChangeLoggingObserver.register!
1
+ require_relative "rack/timeout/base"
2
+ require_relative "rack/timeout/rails" if defined?(Rails) && [3,4].include?(Rails::VERSION::MAJOR)
@@ -0,0 +1,4 @@
1
+ require_relative "core"
2
+ require_relative "logger"
3
+
4
+ Rack::Timeout::Logger.init
@@ -1,11 +1,23 @@
1
1
  # encoding: utf-8
2
- require 'securerandom'
2
+ require "securerandom"
3
3
 
4
4
  module Rack
5
5
  class Timeout
6
- class Error < Exception; end # superclass for the following…
7
- class RequestExpiryError < Error; end # raised when a request is dropped without being given a chance to run (because too old)
8
- class RequestTimeoutError < Error; end # raised when a request has run for too long
6
+ module ExceptionWithEnv # shared by the following exceptions, allows them to receive the current env
7
+ attr :env
8
+ def initialize(env)
9
+ @env = env
10
+ end
11
+ end
12
+
13
+ class Error < RuntimeError
14
+ include ExceptionWithEnv
15
+ end
16
+ class RequestExpiryError < Error; end # raised when a request is dropped without being given a chance to run (because too old)
17
+ class RequestTimeoutError < Error; end # raised when a request has run for too long
18
+ class RequestTimeoutException < Exception # This is first raised to help prevent an application from inadvertently catching the above. It's then caught by rack-timeout and replaced with RequestTimeoutError to bubble up to wrapping middlewares and the web server
19
+ include ExceptionWithEnv
20
+ end
9
21
 
10
22
  RequestDetails = Struct.new(
11
23
  :id, # a unique identifier for the request. informative-only.
@@ -15,7 +27,7 @@ module Rack
15
27
  :state, # the request's current state, see below:
16
28
  ) {
17
29
  def ms(k) # helper method used for formatting values in milliseconds
18
- '%.fms' % (self[k] * 1000) if self[k]
30
+ "%.fms" % (self[k] * 1000) if self[k]
19
31
  end
20
32
  }
21
33
  VALID_STATES = [
@@ -25,7 +37,7 @@ module Rack
25
37
  :timed_out, # This request has run for too long and we're raising a timeout error in it
26
38
  :completed, # We're done with this request (also set after having timed out a request)
27
39
  ]
28
- ENV_INFO_KEY = 'rack-timeout.info' # key under which each request's RequestDetails instance is stored in its env.
40
+ ENV_INFO_KEY = "rack-timeout.info" # key under which each request's RequestDetails instance is stored in its env.
29
41
 
30
42
  # helper methods to setup getter/setters for timeout properties. Ensure they're always positive numbers or false. When set to false (or 0), their behaviour is disabled.
31
43
  class << self
@@ -64,10 +76,10 @@ module Rack
64
76
  RT = self # shorthand reference
65
77
  def call(env)
66
78
  info = (env[ENV_INFO_KEY] ||= RequestDetails.new)
67
- info.id ||= env['HTTP_X_REQUEST_ID'] || SecureRandom.hex
79
+ info.id ||= env["HTTP_X_REQUEST_ID"] || SecureRandom.hex
68
80
 
69
81
  time_started_service = Time.now # The time the request started being processed by rack
70
- time_started_wait = RT._read_x_request_start(env) # The time the request was initially receibed by the web server (if available)
82
+ time_started_wait = RT._read_x_request_start(env) # The time the request was initially received by the web server (if available)
71
83
  effective_overtime = (RT.wait_overtime && RT._request_has_body?(env)) ? RT.wait_overtime : 0 # additional wait timeout (if set and applicable)
72
84
  seconds_service_left = nil
73
85
 
@@ -80,7 +92,7 @@ module Rack
80
92
  info.wait, info.timeout = seconds_waited, final_wait_timeout # updating the info properties; info.timeout will be the wait timeout at this point
81
93
  if seconds_service_left <= 0 # expire requests that have waited for too long in the queue (as they are assumed to have been dropped by the web server / routing layer at this point)
82
94
  RT._set_state! env, :expired
83
- raise RequestExpiryError, "Request older than #{info.ms(:timeout)}."
95
+ raise RequestExpiryError.new(env), "Request older than #{info.ms(:timeout)}."
84
96
  end
85
97
  end
86
98
 
@@ -103,9 +115,14 @@ module Rack
103
115
  sleep(sleep_seconds)
104
116
  end
105
117
  RT._set_state! env, :timed_out
106
- app_thread.raise(RequestTimeoutError, "Request #{"waited #{info.ms(:wait)}, then " if info.wait}ran for longer than #{info.ms(:timeout)}")
118
+ app_thread.raise(RequestTimeoutException.new(env), "Request #{"waited #{info.ms(:wait)}, then " if info.wait}ran for longer than #{info.ms(:timeout)}")
107
119
  end
120
+
108
121
  response = @app.call(env)
122
+
123
+ rescue RequestTimeoutException => e
124
+ raise RequestTimeoutError.new(env), e.message, e.backtrace
125
+
109
126
  ensure
110
127
  timeout_thread.kill
111
128
  timeout_thread.join
@@ -133,7 +150,7 @@ module Rack
133
150
  RX_NGINX_X_REQUEST_START = /^(?:t=)?(\d+)\.(\d{3})$/
134
151
  RX_HEROKU_X_REQUEST_START = /^(\d+)$/
135
152
  def self._read_x_request_start(env)
136
- return unless s = env['HTTP_X_REQUEST_START']
153
+ return unless s = env["HTTP_X_REQUEST_START"]
137
154
  return unless m = s.match(RX_HEROKU_X_REQUEST_START) || s.match(RX_NGINX_X_REQUEST_START)
138
155
  Time.at(m[1,2].join.to_f / 1000)
139
156
  end
@@ -141,9 +158,9 @@ module Rack
141
158
  # This method determines if a body is present. requests with a body (generally POST, PUT) can have a lengthy body which may have taken a while to be received by the web server, inflating their computed wait time. This in turn could lead to unwanted expirations. See wait_overtime property as a way to overcome those.
142
159
  # This is a code extraction for readability, this method is only called from a single point.
143
160
  def self._request_has_body?(env)
144
- return true if env['HTTP_TRANSFER_ENCODING'] == 'chunked'
145
- return false if env['CONTENT_LENGTH'].nil?
146
- return false if env['CONTENT_LENGTH'].to_i.zero?
161
+ return true if env["HTTP_TRANSFER_ENCODING"] == "chunked"
162
+ return false if env["CONTENT_LENGTH"].nil?
163
+ return false if env["CONTENT_LENGTH"].to_i.zero?
147
164
  true
148
165
  end
149
166
 
@@ -1,58 +1,39 @@
1
- require 'logger'
2
-
3
- class Rack::Timeout
4
- class StageChangeLoggingObserver
5
- STATE_LOG_LEVEL = { :expired => :error,
6
- :ready => :info,
7
- :active => :debug,
8
- :timed_out => :error,
9
- :completed => :info,
10
- }
11
-
12
- # creates a logger and registers for state change notifications in Rack::Timeout
13
- def self.register!(logger = nil)
14
- new.register!(logger)
15
- end
16
-
17
- # registers for state change notifications in Rack::Timeout (or other explicit target (potentially useful for testing))
18
- def register!(logger = nil, target = ::Rack::Timeout)
19
- @logger = logger
20
- target.register_state_change_observer(:logger, &method(:log_state_change))
21
- end
22
-
23
- SIMPLE_FORMATTER = ->(severity, timestamp, progname, msg) { "#{msg} at=#{severity.downcase}\n" }
24
- def self.mk_logger(device, level = ::Logger::INFO)
25
- ::Logger.new(device).tap do |logger|
26
- logger.level = level
27
- logger.formatter = SIMPLE_FORMATTER
28
- end
29
- end
30
-
31
- class << self
32
- attr_accessor :logger
33
- end
34
- def logger(env = nil)
35
- self.class.logger ||
36
- (defined?(::Rails) && Rails.logger) ||
37
- (env && !env['rack.logger'].is_a?(::Rack::NullLogger) && env['rack.logger']) ||
38
- (env && env['rack.errors'] && self.class.mk_logger(env['rack.errors'])) ||
39
- (@fallback_logger ||= self.class.mk_logger($stderr))
40
- end
41
-
42
- # generates the actual log string
43
- def log_state_change(env)
44
- info = env[ENV_INFO_KEY]
45
- level = STATE_LOG_LEVEL[info.state]
46
- logger(env).send(level) do
47
- s = 'source=rack-timeout'
48
- s << ' id=' << info.id if info.id
49
- s << ' wait=' << info.ms(:wait) if info.wait
50
- s << ' timeout=' << info.ms(:timeout) if info.timeout
51
- s << ' service=' << info.ms(:service) if info.service
52
- s << ' state=' << info.state.to_s if info.state
53
- s
54
- end
55
- end
1
+ require "logger"
2
+ require_relative "core"
3
+ require_relative "logging-observer"
56
4
 
5
+ module Rack::Timeout::Logger
6
+ extend self
7
+ attr :device, :level, :logger
8
+
9
+ def device=(new_device)
10
+ update(new_device, level)
11
+ end
12
+
13
+ def level=(new_level)
14
+ update(device, new_level)
15
+ end
16
+
17
+ def logger=(new_logger)
18
+ @logger = @observer.logger = new_logger
19
+ end
20
+
21
+ def init
22
+ @observer = ::Rack::Timeout::StateChangeLoggingObserver.new
23
+ ::Rack::Timeout.register_state_change_observer(:logger, &@observer.callback)
24
+ @inited = true
25
+ end
26
+
27
+ def disable
28
+ @observer, @logger, @level, @device, @inited = nil
29
+ ::Rack::Timeout.unregister_state_change_observer(:logger)
57
30
  end
31
+
32
+ def update(new_device, new_level)
33
+ init unless @inited
34
+ @device = new_device || $stderr
35
+ @level = new_level || ::Logger::INFO
36
+ self.logger = ::Rack::Timeout::StateChangeLoggingObserver.mk_logger(device, level)
37
+ end
38
+
58
39
  end
@@ -0,0 +1,53 @@
1
+ require "logger"
2
+ require_relative "core"
3
+
4
+ class Rack::Timeout::StateChangeLoggingObserver
5
+ STATE_LOG_LEVEL = { :expired => :error,
6
+ :ready => :info,
7
+ :active => :debug,
8
+ :timed_out => :error,
9
+ :completed => :info,
10
+ }
11
+
12
+ # returns the Proc to be used as the observer callback block
13
+ def callback
14
+ method(:log_state_change)
15
+ end
16
+
17
+ SIMPLE_FORMATTER = ->(severity, timestamp, progname, msg) { "#{msg} at=#{severity.downcase}\n" }
18
+ def self.mk_logger(device, level = ::Logger::INFO)
19
+ ::Logger.new(device).tap do |logger|
20
+ logger.level = level
21
+ logger.formatter = SIMPLE_FORMATTER
22
+ end
23
+ end
24
+
25
+
26
+ attr_writer :logger
27
+
28
+ private
29
+
30
+ def logger(env = nil)
31
+ @logger ||
32
+ (defined?(::Rails) && ::Rails.logger) ||
33
+ (env && !env["rack.logger"].is_a?(::Rack::NullLogger) && env["rack.logger"]) ||
34
+ (env && env["rack.errors"] && self.class.mk_logger(env["rack.errors"])) ||
35
+ (@fallback_logger ||= self.class.mk_logger($stderr))
36
+ end
37
+
38
+ # generates the actual log string
39
+ def log_state_change(env)
40
+ info = env[::Rack::Timeout::ENV_INFO_KEY]
41
+ level = STATE_LOG_LEVEL[info.state]
42
+ logger(env).send(level) do
43
+ s = "source=rack-timeout"
44
+ s << " id=" << info.id if info.id
45
+ s << " wait=" << info.ms(:wait) if info.wait
46
+ s << " timeout=" << info.ms(:timeout) if info.timeout
47
+ s << " service=" << info.ms(:service) if info.service
48
+ s << " state=" << info.state.to_s if info.state
49
+ s
50
+ end
51
+ end
52
+
53
+ end
@@ -0,0 +1,8 @@
1
+ require_relative "base"
2
+
3
+ class Rack::Timeout::Railtie < Rails::Railtie
4
+ initializer("rack-timeout.prepend") do |app|
5
+ next if Rails.env.test?
6
+ app.config.middleware.insert_before Rack::Runtime, Rack::Timeout
7
+ end
8
+ end
@@ -0,0 +1,31 @@
1
+ require_relative "core"
2
+
3
+ # Groups timeout exceptions in rollbar by exception class, http method, and url.
4
+ # Usage: after requiring rollbar, call:
5
+ # require "rack/timeout/rollbar"
6
+ #
7
+ # This is somewhat experimental and very lightly tested.
8
+
9
+ module Rack::Timeout::Rollbar
10
+ def build_payload(level, message, exception, extra)
11
+ payload = super(level, message, exception, extra)
12
+
13
+ return payload unless exception.is_a? ::Rack::Timeout::ExceptionWithEnv
14
+ return payload unless payload.respond_to? :[]
15
+
16
+ data = payload["data"]
17
+ return data unless data.respond_to? :[]=
18
+
19
+ request = ::Rack::Request.new(exception.env)
20
+
21
+ data["fingerprint"] = [
22
+ exception.class.name,
23
+ request.request_method,
24
+ request.fullpath
25
+ ].join(" ")
26
+
27
+ return payload
28
+ end
29
+ end
30
+
31
+ ::Rollbar::Notifier.prepend ::Rack::Timeout::Rollbar
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rack-timeout
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.4
4
+ version: 0.3.0.pre.beta.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Caio Chassot
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2015-03-31 00:00:00.000000000 Z
11
+ date: 2015-08-14 00:00:00.000000000 Z
12
12
  dependencies: []
13
13
  description: Rack middleware which aborts requests that have been running for longer
14
14
  than a specified timeout.
@@ -17,11 +17,16 @@ executables: []
17
17
  extensions: []
18
18
  extra_rdoc_files: []
19
19
  files:
20
+ - CHANGELOG
20
21
  - MIT-LICENSE
21
22
  - README.markdown
22
23
  - lib/rack-timeout.rb
23
- - lib/rack/timeout.rb
24
+ - lib/rack/timeout/base.rb
25
+ - lib/rack/timeout/core.rb
24
26
  - lib/rack/timeout/logger.rb
27
+ - lib/rack/timeout/logging-observer.rb
28
+ - lib/rack/timeout/rails.rb
29
+ - lib/rack/timeout/rollbar.rb
25
30
  homepage: http://github.com/heroku/rack-timeout
26
31
  licenses:
27
32
  - MIT
@@ -37,9 +42,9 @@ required_ruby_version: !ruby/object:Gem::Requirement
37
42
  version: '0'
38
43
  required_rubygems_version: !ruby/object:Gem::Requirement
39
44
  requirements:
40
- - - ">="
45
+ - - ">"
41
46
  - !ruby/object:Gem::Version
42
- version: '0'
47
+ version: 1.3.1
43
48
  requirements: []
44
49
  rubyforge_project:
45
50
  rubygems_version: 2.4.5