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 +4 -4
- data/CHANGELOG +59 -0
- data/README.markdown +53 -17
- data/lib/rack-timeout.rb +2 -11
- data/lib/rack/timeout/base.rb +4 -0
- data/lib/rack/{timeout.rb → timeout/core.rb} +31 -14
- data/lib/rack/timeout/logger.rb +36 -55
- data/lib/rack/timeout/logging-observer.rb +53 -0
- data/lib/rack/timeout/rails.rb +8 -0
- data/lib/rack/timeout/rollbar.rb +31 -0
- metadata +10 -5
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 0115bc1c87b36ff9e6f74cb104e0c8fb13550685
|
4
|
+
data.tar.gz: d2eaf62f154d099701b263db687897e15d3104c5
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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;
|
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
|
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'
|
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
|
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
|
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,
|
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.
|
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
|
174
|
+
Rack::Timeout can raise three types of exceptions. They are:
|
154
175
|
|
155
|
-
|
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::
|
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::
|
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
|
-
|
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::
|
236
|
+
Rack::Timeout::Logger.logger = Logger.new
|
208
237
|
```
|
209
238
|
|
210
|
-
|
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
|
248
|
+
Logging is enabled by default, but can be removed with:
|
213
249
|
|
214
250
|
```ruby
|
215
|
-
Rack::Timeout.
|
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-
|
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
|
-
|
2
|
-
|
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)
|
@@ -1,11 +1,23 @@
|
|
1
1
|
# encoding: utf-8
|
2
|
-
require
|
2
|
+
require "securerandom"
|
3
3
|
|
4
4
|
module Rack
|
5
5
|
class Timeout
|
6
|
-
|
7
|
-
|
8
|
-
|
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
|
-
|
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 =
|
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[
|
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
|
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(
|
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[
|
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[
|
145
|
-
return false if env[
|
146
|
-
return false if env[
|
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
|
|
data/lib/rack/timeout/logger.rb
CHANGED
@@ -1,58 +1,39 @@
|
|
1
|
-
require
|
2
|
-
|
3
|
-
|
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,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.
|
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-
|
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:
|
47
|
+
version: 1.3.1
|
43
48
|
requirements: []
|
44
49
|
rubyforge_project:
|
45
50
|
rubygems_version: 2.4.5
|