puma 3.6.2 → 3.7.0
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of puma might be problematic. Click here for more details.
- checksums.yaml +4 -4
- data/Gemfile +3 -4
- data/{History.txt → History.md} +121 -82
- data/Manifest.txt +1 -3
- data/README.md +7 -1
- data/Rakefile +4 -4
- data/docs/systemd.md +27 -0
- data/ext/puma_http11/http11_parser.c +1 -0
- data/ext/puma_http11/http11_parser.h +1 -0
- data/ext/puma_http11/http11_parser.rl +1 -0
- data/ext/puma_http11/io_buffer.c +7 -7
- data/ext/puma_http11/mini_ssl.c +17 -6
- data/ext/puma_http11/puma_http11.c +1 -0
- data/lib/puma.rb +5 -5
- data/lib/puma/binder.rb +6 -3
- data/lib/puma/cli.rb +3 -0
- data/lib/puma/client.rb +4 -4
- data/lib/puma/cluster.rb +25 -4
- data/lib/puma/commonlogger.rb +19 -20
- data/lib/puma/compat.rb +3 -7
- data/lib/puma/configuration.rb +3 -1
- data/lib/puma/const.rb +7 -36
- data/lib/puma/control_cli.rb +27 -27
- data/lib/puma/detect.rb +3 -1
- data/lib/puma/events.rb +5 -6
- data/lib/puma/io_buffer.rb +1 -1
- data/lib/puma/launcher.rb +10 -9
- data/lib/puma/null_io.rb +6 -13
- data/lib/puma/rack/builder.rb +3 -0
- data/lib/puma/rack/urlmap.rb +9 -8
- data/lib/puma/runner.rb +11 -0
- data/lib/puma/single.rb +2 -0
- data/lib/puma/thread_pool.rb +13 -13
- data/lib/puma/util.rb +1 -5
- data/lib/rack/handler/puma.rb +9 -2
- data/puma.gemspec +1 -1
- data/tools/jungle/init.d/README.md +7 -2
- data/tools/jungle/init.d/puma +1 -1
- metadata +7 -9
- data/lib/puma/rack/backports/uri/common_18.rb +0 -59
- data/lib/puma/rack/backports/uri/common_192.rb +0 -55
data/Manifest.txt
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
DEPLOYMENT.md
|
2
2
|
Gemfile
|
3
|
-
History.
|
3
|
+
History.md
|
4
4
|
LICENSE
|
5
5
|
Manifest.txt
|
6
6
|
README.md
|
@@ -51,8 +51,6 @@ lib/puma/minissl.rb
|
|
51
51
|
lib/puma/null_io.rb
|
52
52
|
lib/puma/plugin.rb
|
53
53
|
lib/puma/plugin/tmp_restart.rb
|
54
|
-
lib/puma/rack/backports/uri/common_18.rb
|
55
|
-
lib/puma/rack/backports/uri/common_192.rb
|
56
54
|
lib/puma/rack/backports/uri/common_193.rb
|
57
55
|
lib/puma/rack/builder.rb
|
58
56
|
lib/puma/rack/urlmap.rb
|
data/README.md
CHANGED
@@ -213,6 +213,12 @@ Puma comes with a builtin status/control app that can be used to query and contr
|
|
213
213
|
|
214
214
|
This directs Puma to start the control server on localhost port 9293. Additionally, all requests to the control server will need to include `token=foo` as a query parameter. This allows for simple authentication. Check out [status.rb](https://github.com/puma/puma/blob/master/lib/puma/app/status.rb) to see what the app has available.
|
215
215
|
|
216
|
+
Keep in mind that the status/control server accepts `pumactl` commands. To demonstrate, you can run the following command with the foo `--control-token` as such to restart:
|
217
|
+
|
218
|
+
$ pumactl restart --control-token foo
|
219
|
+
|
220
|
+
To see a list of `pumactl` options, please see `pumactl --help` as stated in the [`pumactl`](https://github.com/puma/puma#pumactl) section.
|
221
|
+
|
216
222
|
### Configuration file
|
217
223
|
|
218
224
|
You can also provide a configuration file which Puma will use with the `-C` (or `--config`) flag:
|
@@ -269,7 +275,7 @@ A detailed guide to using UNIX signals with Puma can be found in the [signals do
|
|
269
275
|
|
270
276
|
### Release Directory
|
271
277
|
|
272
|
-
If
|
278
|
+
If your symlink releases into a common working directory (i.e., `/current` from Capistrano), Puma won't pick up your new changes when running phased restarts without additional configuration. You should set your working directory within Puma's config to specify the directory it should use. This is a change from earlier versions of Puma (< 2.15) that would infer the directory for you.
|
273
279
|
|
274
280
|
```ruby
|
275
281
|
# config/puma.rb
|
data/Rakefile
CHANGED
@@ -1,3 +1,4 @@
|
|
1
|
+
require "bundler/setup"
|
1
2
|
require "hoe"
|
2
3
|
require "rake/extensiontask"
|
3
4
|
require "rake/javaextensiontask"
|
@@ -18,9 +19,9 @@ HOE = Hoe.spec "puma" do
|
|
18
19
|
spec_extras[:executables] = ['puma', 'pumactl']
|
19
20
|
spec_extras[:homepage] = self.urls.first
|
20
21
|
|
21
|
-
require_ruby_version ">= 1.
|
22
|
+
require_ruby_version ">= 1.9.3"
|
22
23
|
|
23
|
-
dependency "rack", [">= 1.1", "<
|
24
|
+
dependency "rack", [">= 1.1", "< 3.0"], :development
|
24
25
|
|
25
26
|
extra_dev_deps << ["rake-compiler", "~> 0.8"]
|
26
27
|
end
|
@@ -66,7 +67,7 @@ task "changelog" do
|
|
66
67
|
$changes[code] << line
|
67
68
|
end
|
68
69
|
|
69
|
-
puts "
|
70
|
+
puts "## #{ENV['VERSION'] || 'NEXT'} / #{now}"
|
70
71
|
puts
|
71
72
|
changelog_section :major
|
72
73
|
changelog_section :minor
|
@@ -155,4 +156,3 @@ namespace :test do
|
|
155
156
|
task :all => [:test, "test:integration"]
|
156
157
|
end
|
157
158
|
end
|
158
|
-
|
data/docs/systemd.md
CHANGED
@@ -168,3 +168,30 @@ Apr 07 08:40:19 hx puma[28320]: * Activated tcp://0.0.0.0:9233
|
|
168
168
|
Apr 07 08:40:19 hx puma[28320]: * Activated ssl://0.0.0.0:9234?key=key.pem&cert=cert.pem
|
169
169
|
Apr 07 08:40:19 hx puma[28320]: Use Ctrl-C to stop
|
170
170
|
~~~~
|
171
|
+
|
172
|
+
## Alternative background process configuration
|
173
|
+
|
174
|
+
If Capistrano and [capistrano3-puma](https://github.com/seuros/capistrano-puma) tasks are used you can use the following configuration. In this case, you would skip systemd Socket Activation, since Puma handles the socket by itself:
|
175
|
+
|
176
|
+
~~~~
|
177
|
+
[Service]
|
178
|
+
# Background process configuration (use with --daemon in ExecStart)
|
179
|
+
Type=forking
|
180
|
+
|
181
|
+
# To learn which exact command is to be used to execute at "ExecStart" of this
|
182
|
+
# Service, ask Capistrano: `cap <stage> puma:start --dry-run`. Your result
|
183
|
+
# may differ from this example, for example if you use a Ruby version
|
184
|
+
# manager. `<WD>` is short for "your working directory". Replace it with your
|
185
|
+
# path.
|
186
|
+
ExecStart=bundle exec puma -C <WD>/shared/puma.rb --daemon
|
187
|
+
|
188
|
+
# To learn which exact command is to be used to execute at "ExecStop" of this
|
189
|
+
# Service, ask Capistrano: `cap <stage> puma:stop --dry-run`. Your result
|
190
|
+
# may differ from this example, for example if you use a Ruby version
|
191
|
+
# manager. `<WD>` is short for "your working directory". Replace it with your
|
192
|
+
# path.
|
193
|
+
ExecStop=bundle exec pumactl -S <WD>/shared/tmp/pids/puma.state stop
|
194
|
+
|
195
|
+
# PIDFile setting is required in order to work properly
|
196
|
+
PIDFile=<WD>/shared/tmp/pids/puma.pid
|
197
|
+
~~~~
|
data/ext/puma_http11/io_buffer.c
CHANGED
@@ -14,8 +14,8 @@ struct buf_int {
|
|
14
14
|
#define BUF_TOLERANCE 32
|
15
15
|
|
16
16
|
static void buf_free(struct buf_int* internal) {
|
17
|
-
|
18
|
-
|
17
|
+
xfree(internal->top);
|
18
|
+
xfree(internal);
|
19
19
|
}
|
20
20
|
|
21
21
|
static VALUE buf_alloc(VALUE self) {
|
@@ -25,7 +25,7 @@ static VALUE buf_alloc(VALUE self) {
|
|
25
25
|
buf = Data_Make_Struct(self, struct buf_int, 0, buf_free, internal);
|
26
26
|
|
27
27
|
internal->size = BUF_DEFAULT_SIZE;
|
28
|
-
internal->top =
|
28
|
+
internal->top = ALLOC_N(uint8_t, BUF_DEFAULT_SIZE);
|
29
29
|
internal->cur = internal->top;
|
30
30
|
|
31
31
|
return buf;
|
@@ -51,13 +51,13 @@ static VALUE buf_append(VALUE self, VALUE str) {
|
|
51
51
|
|
52
52
|
new_size = (n > new_size ? n : new_size + BUF_TOLERANCE);
|
53
53
|
|
54
|
-
top =
|
54
|
+
top = ALLOC_N(uint8_t, new_size);
|
55
55
|
old = b->top;
|
56
56
|
memcpy(top, old, used);
|
57
57
|
b->top = top;
|
58
58
|
b->cur = top + used;
|
59
59
|
b->size = new_size;
|
60
|
-
|
60
|
+
xfree(old);
|
61
61
|
}
|
62
62
|
|
63
63
|
memcpy(b->cur, RSTRING_PTR(str), str_len);
|
@@ -92,13 +92,13 @@ static VALUE buf_append2(int argc, VALUE* argv, VALUE self) {
|
|
92
92
|
|
93
93
|
new_size = (n > new_size ? n : new_size + BUF_TOLERANCE);
|
94
94
|
|
95
|
-
top =
|
95
|
+
top = ALLOC_N(uint8_t, new_size);
|
96
96
|
old = b->top;
|
97
97
|
memcpy(top, old, used);
|
98
98
|
b->top = top;
|
99
99
|
b->cur = top + used;
|
100
100
|
b->size = new_size;
|
101
|
-
|
101
|
+
xfree(old);
|
102
102
|
}
|
103
103
|
|
104
104
|
for(i = 0; i < argc; i++) {
|
data/ext/puma_http11/mini_ssl.c
CHANGED
@@ -87,6 +87,8 @@ DH *get_dh1024() {
|
|
87
87
|
|
88
88
|
DH *dh;
|
89
89
|
dh = DH_new();
|
90
|
+
|
91
|
+
#if OPENSSL_VERSION_NUMBER < 0x10100005L
|
90
92
|
dh->p = BN_bin2bn(dh1024_p, sizeof(dh1024_p), NULL);
|
91
93
|
dh->g = BN_bin2bn(dh1024_g, sizeof(dh1024_g), NULL);
|
92
94
|
|
@@ -94,6 +96,18 @@ DH *get_dh1024() {
|
|
94
96
|
DH_free(dh);
|
95
97
|
return NULL;
|
96
98
|
}
|
99
|
+
#else
|
100
|
+
BIGNUM *p, *g;
|
101
|
+
p = BN_bin2bn(dh1024_p, sizeof(dh1024_p), NULL);
|
102
|
+
g = BN_bin2bn(dh1024_g, sizeof(dh1024_g), NULL);
|
103
|
+
|
104
|
+
if (p == NULL || g == NULL || !DH_set0_pqg(dh, p, NULL, g)) {
|
105
|
+
DH_free(dh);
|
106
|
+
BN_free(p);
|
107
|
+
BN_free(g);
|
108
|
+
return NULL;
|
109
|
+
}
|
110
|
+
#endif
|
97
111
|
|
98
112
|
return dh;
|
99
113
|
}
|
@@ -157,7 +171,7 @@ VALUE engine_init_server(VALUE self, VALUE mini_ssl_ctx) {
|
|
157
171
|
StringValue(ca);
|
158
172
|
SSL_CTX_load_verify_locations(ctx, RSTRING_PTR(ca), NULL);
|
159
173
|
}
|
160
|
-
|
174
|
+
|
161
175
|
SSL_CTX_set_options(ctx, SSL_OP_CIPHER_SERVER_PREFERENCE | SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3 | SSL_OP_SINGLE_DH_USE | SSL_OP_SINGLE_ECDH_USE | SSL_OP_NO_COMPRESSION);
|
162
176
|
SSL_CTX_set_session_cache_mode(ctx, SSL_SESS_CACHE_OFF);
|
163
177
|
|
@@ -329,8 +343,7 @@ VALUE engine_extract(VALUE self) {
|
|
329
343
|
|
330
344
|
VALUE engine_shutdown(VALUE self) {
|
331
345
|
ms_conn* conn;
|
332
|
-
int ok
|
333
|
-
char buf[512];
|
346
|
+
int ok;
|
334
347
|
|
335
348
|
Data_Get_Struct(self, ms_conn, conn);
|
336
349
|
|
@@ -346,8 +359,6 @@ VALUE engine_shutdown(VALUE self) {
|
|
346
359
|
|
347
360
|
VALUE engine_init(VALUE self) {
|
348
361
|
ms_conn* conn;
|
349
|
-
int ok, err;
|
350
|
-
char buf[512];
|
351
362
|
|
352
363
|
Data_Get_Struct(self, ms_conn, conn);
|
353
364
|
|
@@ -404,7 +415,7 @@ void Init_mini_ssl(VALUE puma) {
|
|
404
415
|
OpenSSL_add_ssl_algorithms();
|
405
416
|
SSL_load_error_strings();
|
406
417
|
ERR_load_crypto_strings();
|
407
|
-
|
418
|
+
|
408
419
|
mod = rb_define_module_under(puma, "MiniSSL");
|
409
420
|
eng = rb_define_class_under(mod, "Engine", rb_cObject);
|
410
421
|
|
data/lib/puma.rb
CHANGED
@@ -1,7 +1,6 @@
|
|
1
1
|
# Standard libraries
|
2
2
|
require 'socket'
|
3
3
|
require 'tempfile'
|
4
|
-
require 'yaml'
|
5
4
|
require 'time'
|
6
5
|
require 'etc'
|
7
6
|
require 'uri'
|
@@ -9,7 +8,8 @@ require 'stringio'
|
|
9
8
|
|
10
9
|
require 'thread'
|
11
10
|
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
11
|
+
module Puma
|
12
|
+
autoload :Const, 'puma/const'
|
13
|
+
autoload :Server, 'puma/server'
|
14
|
+
autoload :Launcher, 'puma/launcher'
|
15
|
+
end
|
data/lib/puma/binder.rb
CHANGED
@@ -1,5 +1,8 @@
|
|
1
|
-
require 'puma/const'
|
2
1
|
require 'uri'
|
2
|
+
require 'socket'
|
3
|
+
|
4
|
+
require 'puma/const'
|
5
|
+
require 'puma/util'
|
3
6
|
|
4
7
|
module Puma
|
5
8
|
class Binder
|
@@ -140,11 +143,11 @@ module Puma
|
|
140
143
|
|
141
144
|
@listeners << [str, io]
|
142
145
|
when "ssl"
|
143
|
-
MiniSSL.check
|
144
|
-
|
145
146
|
params = Util.parse_query uri.query
|
146
147
|
require 'puma/minissl'
|
147
148
|
|
149
|
+
MiniSSL.check
|
150
|
+
|
148
151
|
ctx = MiniSSL::Context.new
|
149
152
|
|
150
153
|
if defined?(JRUBY_VERSION)
|
data/lib/puma/cli.rb
CHANGED
data/lib/puma/client.rb
CHANGED
@@ -155,7 +155,8 @@ module Puma
|
|
155
155
|
len = line.strip.to_i(16)
|
156
156
|
if len == 0
|
157
157
|
@body.rewind
|
158
|
-
|
158
|
+
rest = io.read
|
159
|
+
@buffer = rest.empty? ? nil : rest
|
159
160
|
@requests_served += 1
|
160
161
|
@ready = true
|
161
162
|
return true
|
@@ -215,14 +216,13 @@ module Puma
|
|
215
216
|
end
|
216
217
|
|
217
218
|
def setup_body
|
218
|
-
@in_data_phase = true
|
219
219
|
@read_header = false
|
220
220
|
|
221
221
|
body = @parser.body
|
222
222
|
|
223
223
|
te = @env[TRANSFER_ENCODING2]
|
224
224
|
|
225
|
-
if te ==
|
225
|
+
if te && CHUNKED.casecmp(te) == 0
|
226
226
|
return setup_chunked_body(body)
|
227
227
|
end
|
228
228
|
|
@@ -290,7 +290,7 @@ module Puma
|
|
290
290
|
raise HttpParserError,
|
291
291
|
"HEADER is longer than allowed, aborting client early."
|
292
292
|
end
|
293
|
-
|
293
|
+
|
294
294
|
false
|
295
295
|
end
|
296
296
|
|
data/lib/puma/cluster.rb
CHANGED
@@ -1,8 +1,13 @@
|
|
1
1
|
require 'puma/runner'
|
2
|
+
require 'puma/util'
|
3
|
+
require 'puma/plugin'
|
4
|
+
|
2
5
|
require 'time'
|
3
6
|
|
4
7
|
module Puma
|
5
8
|
class Cluster < Runner
|
9
|
+
WORKER_CHECK_INTERVAL = 5
|
10
|
+
|
6
11
|
def initialize(cli, events)
|
7
12
|
super cli, events
|
8
13
|
|
@@ -110,6 +115,7 @@ module Puma
|
|
110
115
|
|
111
116
|
def spawn_workers
|
112
117
|
diff = @options[:workers] - @workers.size
|
118
|
+
return if diff < 1
|
113
119
|
|
114
120
|
master = Process.pid
|
115
121
|
|
@@ -135,6 +141,21 @@ module Puma
|
|
135
141
|
end
|
136
142
|
end
|
137
143
|
|
144
|
+
def cull_workers
|
145
|
+
diff = @workers.size - @options[:workers]
|
146
|
+
return if diff < 1
|
147
|
+
|
148
|
+
debug "Culling #{diff.inspect} workers"
|
149
|
+
|
150
|
+
workers_to_cull = @workers[-diff,diff]
|
151
|
+
debug "Workers to cull: #{workers_to_cull.inspect}"
|
152
|
+
|
153
|
+
workers_to_cull.each do |worker|
|
154
|
+
log "- Worker #{worker.index} (pid: #{worker.pid}) terminating"
|
155
|
+
worker.term
|
156
|
+
end
|
157
|
+
end
|
158
|
+
|
138
159
|
def next_worker_index
|
139
160
|
all_positions = 0...@options[:workers]
|
140
161
|
occupied_positions = @workers.map { |w| w.index }
|
@@ -149,7 +170,7 @@ module Puma
|
|
149
170
|
def check_workers(force=false)
|
150
171
|
return if !force && @next_check && @next_check >= Time.now
|
151
172
|
|
152
|
-
@next_check = Time.now +
|
173
|
+
@next_check = Time.now + WORKER_CHECK_INTERVAL
|
153
174
|
|
154
175
|
any = false
|
155
176
|
|
@@ -175,6 +196,7 @@ module Puma
|
|
175
196
|
|
176
197
|
@workers.delete_if(&:dead?)
|
177
198
|
|
199
|
+
cull_workers
|
178
200
|
spawn_workers
|
179
201
|
|
180
202
|
if all_workers_booted?
|
@@ -253,7 +275,7 @@ module Puma
|
|
253
275
|
base_payload = "p#{Process.pid}"
|
254
276
|
|
255
277
|
while true
|
256
|
-
sleep
|
278
|
+
sleep WORKER_CHECK_INTERVAL
|
257
279
|
begin
|
258
280
|
b = server.backlog
|
259
281
|
r = server.running
|
@@ -337,7 +359,6 @@ module Puma
|
|
337
359
|
|
338
360
|
Signal.trap "TTOU" do
|
339
361
|
@options[:workers] -= 1 if @options[:workers] >= 2
|
340
|
-
@workers.last.term
|
341
362
|
wakeup!
|
342
363
|
end
|
343
364
|
|
@@ -445,7 +466,7 @@ module Puma
|
|
445
466
|
|
446
467
|
force_check = false
|
447
468
|
|
448
|
-
res = IO.select([read], nil, nil,
|
469
|
+
res = IO.select([read], nil, nil, WORKER_CHECK_INTERVAL)
|
449
470
|
|
450
471
|
if res
|
451
472
|
req = read.read_nonblock(1)
|
data/lib/puma/commonlogger.rb
CHANGED
@@ -21,6 +21,13 @@ module Puma
|
|
21
21
|
# %{%s - %s [%s] "%s %s%s %s" %d %s\n} %
|
22
22
|
FORMAT = %{%s - %s [%s] "%s %s%s %s" %d %s %0.4f\n}
|
23
23
|
|
24
|
+
HIJACK_FORMAT = %{%s - %s [%s] "%s %s%s %s" HIJACKED -1 %0.4f\n}
|
25
|
+
|
26
|
+
CONTENT_LENGTH = 'Content-Length'.freeze
|
27
|
+
PATH_INFO = 'PATH_INFO'.freeze
|
28
|
+
QUERY_STRING = 'QUERY_STRING'.freeze
|
29
|
+
REQUEST_METHOD = 'REQUEST_METHOD'.freeze
|
30
|
+
|
24
31
|
def initialize(app, logger=nil)
|
25
32
|
@app = app
|
26
33
|
@logger = logger
|
@@ -42,36 +49,23 @@ module Puma
|
|
42
49
|
[status, header, body]
|
43
50
|
end
|
44
51
|
|
45
|
-
HIJACK_FORMAT = %{%s - %s [%s] "%s %s%s %s" HIJACKED -1 %0.4f\n}
|
46
|
-
|
47
52
|
private
|
48
53
|
|
49
54
|
def log_hijacking(env, status, header, began_at)
|
50
55
|
now = Time.now
|
51
56
|
|
52
|
-
|
53
|
-
logger.write HIJACK_FORMAT % [
|
57
|
+
msg = HIJACK_FORMAT % [
|
54
58
|
env['HTTP_X_FORWARDED_FOR'] || env["REMOTE_ADDR"] || "-",
|
55
59
|
env["REMOTE_USER"] || "-",
|
56
60
|
now.strftime("%d/%b/%Y %H:%M:%S"),
|
57
|
-
env[
|
58
|
-
env[
|
59
|
-
env[
|
61
|
+
env[REQUEST_METHOD],
|
62
|
+
env[PATH_INFO],
|
63
|
+
env[QUERY_STRING].empty? ? "" : "?#{env[QUERY_STRING]}",
|
60
64
|
env["HTTP_VERSION"],
|
61
65
|
now - began_at ]
|
62
|
-
end
|
63
66
|
|
64
|
-
|
65
|
-
|
66
|
-
SCRIPT_NAME = 'SCRIPT_NAME'.freeze
|
67
|
-
QUERY_STRING = 'QUERY_STRING'.freeze
|
68
|
-
CACHE_CONTROL = 'Cache-Control'.freeze
|
69
|
-
CONTENT_LENGTH = 'Content-Length'.freeze
|
70
|
-
CONTENT_TYPE = 'Content-Type'.freeze
|
71
|
-
|
72
|
-
GET = 'GET'.freeze
|
73
|
-
HEAD = 'HEAD'.freeze
|
74
|
-
|
67
|
+
write(msg)
|
68
|
+
end
|
75
69
|
|
76
70
|
def log(env, status, header, began_at)
|
77
71
|
now = Time.now
|
@@ -83,13 +77,18 @@ module Puma
|
|
83
77
|
now.strftime("%d/%b/%Y:%H:%M:%S %z"),
|
84
78
|
env[REQUEST_METHOD],
|
85
79
|
env[PATH_INFO],
|
86
|
-
env[QUERY_STRING].empty? ? "" : "
|
80
|
+
env[QUERY_STRING].empty? ? "" : "?#{env[QUERY_STRING]}",
|
87
81
|
env["HTTP_VERSION"],
|
88
82
|
status.to_s[0..3],
|
89
83
|
length,
|
90
84
|
now - began_at ]
|
91
85
|
|
86
|
+
write(msg)
|
87
|
+
end
|
88
|
+
|
89
|
+
def write(msg)
|
92
90
|
logger = @logger || env['rack.errors']
|
91
|
+
|
93
92
|
# Standard library logger doesn't support write but it supports << which actually
|
94
93
|
# calls to write on the log device without formatting
|
95
94
|
if logger.respond_to?(:write)
|