puma 5.6.9-java → 6.6.0-java
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 +4 -4
- data/History.md +465 -18
- data/README.md +152 -42
- data/bin/puma-wild +1 -1
- data/docs/compile_options.md +34 -0
- data/docs/fork_worker.md +12 -4
- data/docs/java_options.md +54 -0
- data/docs/kubernetes.md +12 -0
- data/docs/nginx.md +1 -1
- data/docs/plugins.md +4 -0
- data/docs/restart.md +1 -0
- data/docs/signals.md +2 -2
- data/docs/stats.md +8 -3
- data/docs/systemd.md +13 -7
- data/docs/testing_benchmarks_local_files.md +150 -0
- data/docs/testing_test_rackup_ci_files.md +36 -0
- data/ext/puma_http11/extconf.rb +27 -17
- data/ext/puma_http11/http11_parser.c +1 -1
- data/ext/puma_http11/http11_parser.h +1 -1
- data/ext/puma_http11/http11_parser.java.rl +2 -2
- data/ext/puma_http11/http11_parser.rl +2 -2
- data/ext/puma_http11/http11_parser_common.rl +2 -2
- data/ext/puma_http11/mini_ssl.c +137 -19
- data/ext/puma_http11/org/jruby/puma/Http11.java +31 -10
- data/ext/puma_http11/org/jruby/puma/Http11Parser.java +1 -1
- data/ext/puma_http11/org/jruby/puma/MiniSSL.java +157 -53
- data/ext/puma_http11/puma_http11.c +21 -10
- data/lib/puma/app/status.rb +4 -4
- data/lib/puma/binder.rb +60 -55
- data/lib/puma/cli.rb +22 -20
- data/lib/puma/client.rb +93 -30
- data/lib/puma/cluster/worker.rb +27 -17
- data/lib/puma/cluster/worker_handle.rb +8 -6
- data/lib/puma/cluster.rb +121 -47
- data/lib/puma/commonlogger.rb +21 -14
- data/lib/puma/configuration.rb +101 -65
- data/lib/puma/const.rb +141 -93
- data/lib/puma/control_cli.rb +19 -15
- data/lib/puma/detect.rb +7 -4
- data/lib/puma/dsl.rb +521 -88
- data/lib/puma/error_logger.rb +22 -13
- data/lib/puma/events.rb +6 -126
- data/lib/puma/io_buffer.rb +39 -4
- data/lib/puma/jruby_restart.rb +0 -15
- data/lib/puma/launcher/bundle_pruner.rb +104 -0
- data/lib/puma/launcher.rb +121 -181
- data/lib/puma/log_writer.rb +147 -0
- data/lib/puma/minissl/context_builder.rb +27 -12
- data/lib/puma/minissl.rb +105 -11
- data/lib/puma/null_io.rb +42 -2
- data/lib/puma/plugin/systemd.rb +90 -0
- data/lib/puma/plugin/tmp_restart.rb +1 -1
- data/lib/puma/puma_http11.jar +0 -0
- data/lib/puma/rack/builder.rb +6 -6
- data/lib/puma/rack/urlmap.rb +1 -1
- data/lib/puma/rack_default.rb +19 -4
- data/lib/puma/reactor.rb +19 -10
- data/lib/puma/request.rb +368 -169
- data/lib/puma/runner.rb +65 -22
- data/lib/puma/sd_notify.rb +146 -0
- data/lib/puma/server.rb +161 -102
- data/lib/puma/single.rb +13 -11
- data/lib/puma/state_file.rb +3 -6
- data/lib/puma/thread_pool.rb +71 -21
- data/lib/puma/util.rb +1 -12
- data/lib/puma.rb +9 -10
- data/lib/rack/handler/puma.rb +116 -86
- data/tools/Dockerfile +2 -2
- metadata +17 -12
- data/lib/puma/queue_close.rb +0 -26
- data/lib/puma/systemd.rb +0 -46
- data/lib/rack/version_restriction.rb +0 -15
data/lib/puma/launcher.rb
CHANGED
@@ -1,11 +1,12 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
3
|
+
require_relative 'log_writer'
|
4
|
+
require_relative 'events'
|
5
|
+
require_relative 'detect'
|
6
|
+
require_relative 'cluster'
|
7
|
+
require_relative 'single'
|
8
|
+
require_relative 'const'
|
9
|
+
require_relative 'binder'
|
9
10
|
|
10
11
|
module Puma
|
11
12
|
# Puma::Launcher is the single entry point for starting a Puma server based on user
|
@@ -15,18 +16,14 @@ module Puma
|
|
15
16
|
# It is responsible for either launching a cluster of Puma workers or a single
|
16
17
|
# puma server.
|
17
18
|
class Launcher
|
18
|
-
|
19
|
-
|
20
|
-
:logger, :lowlevel_error_handler,
|
21
|
-
:before_worker_shutdown, :before_worker_boot, :before_worker_fork,
|
22
|
-
:after_worker_boot, :before_fork, :on_restart
|
23
|
-
]
|
19
|
+
autoload :BundlePruner, 'puma/launcher/bundle_pruner'
|
20
|
+
|
24
21
|
# Returns an instance of Launcher
|
25
22
|
#
|
26
23
|
# +conf+ A Puma::Configuration object indicating how to run the server.
|
27
24
|
#
|
28
25
|
# +launcher_args+ A Hash that currently has one required key `:events`,
|
29
|
-
# this is expected to hold an object similar to an `Puma::
|
26
|
+
# this is expected to hold an object similar to an `Puma::LogWriter.stdio`,
|
30
27
|
# this object will be responsible for broadcasting Puma's internal state
|
31
28
|
# to a logging destination. An optional key `:argv` can be supplied,
|
32
29
|
# this should be an array of strings, these arguments are re-used when
|
@@ -40,25 +37,37 @@ module Puma
|
|
40
37
|
# [200, {}, ["hello world"]]
|
41
38
|
# end
|
42
39
|
# end
|
43
|
-
# Puma::Launcher.new(conf,
|
40
|
+
# Puma::Launcher.new(conf, log_writer: Puma::LogWriter.stdio).run
|
44
41
|
def initialize(conf, launcher_args={})
|
45
42
|
@runner = nil
|
46
|
-
@
|
43
|
+
@log_writer = launcher_args[:log_writer] || LogWriter::DEFAULT
|
44
|
+
@events = launcher_args[:events] || Events.new
|
47
45
|
@argv = launcher_args[:argv] || []
|
48
46
|
@original_argv = @argv.dup
|
49
47
|
@config = conf
|
50
48
|
|
51
|
-
|
52
|
-
@binder.create_inherited_fds(ENV).each { |k| ENV.delete k }
|
53
|
-
@binder.create_activated_fds(ENV).each { |k| ENV.delete k }
|
49
|
+
env = launcher_args.delete(:env) || ENV
|
54
50
|
|
55
|
-
@
|
51
|
+
@config.options[:log_writer] = @log_writer
|
56
52
|
|
57
53
|
# Advertise the Configuration
|
58
54
|
Puma.cli_config = @config if defined?(Puma.cli_config)
|
59
55
|
|
60
56
|
@config.load
|
61
57
|
|
58
|
+
@binder = Binder.new(@log_writer, conf)
|
59
|
+
@binder.create_inherited_fds(ENV).each { |k| ENV.delete k }
|
60
|
+
@binder.create_activated_fds(ENV).each { |k| ENV.delete k }
|
61
|
+
|
62
|
+
@environment = conf.environment
|
63
|
+
|
64
|
+
# Load the systemd integration if we detect systemd's NOTIFY_SOCKET.
|
65
|
+
# Skip this on JRuby though, because it is incompatible with the systemd
|
66
|
+
# integration due to https://github.com/jruby/jruby/issues/6504
|
67
|
+
if ENV["NOTIFY_SOCKET"] && !Puma.jruby? && !ENV["PUMA_SKIP_SYSTEMD"]
|
68
|
+
@config.plugins.create('systemd')
|
69
|
+
end
|
70
|
+
|
62
71
|
if @config.options[:bind_to_activated_sockets]
|
63
72
|
@config.options[:binds] = @binder.synthesize_binds_from_activated_fs(
|
64
73
|
@config.options[:binds],
|
@@ -69,8 +78,10 @@ module Puma
|
|
69
78
|
@options = @config.options
|
70
79
|
@config.clamp
|
71
80
|
|
72
|
-
@
|
73
|
-
@
|
81
|
+
@log_writer.formatter = LogWriter::PidFormatter.new if clustered?
|
82
|
+
@log_writer.formatter = options[:log_formatter] if @options[:log_formatter]
|
83
|
+
|
84
|
+
@log_writer.custom_logger = options[:custom_logger] if @options[:custom_logger]
|
74
85
|
|
75
86
|
generate_restart_data
|
76
87
|
|
@@ -80,26 +91,26 @@ module Puma
|
|
80
91
|
|
81
92
|
Dir.chdir(@restart_dir)
|
82
93
|
|
83
|
-
prune_bundler
|
94
|
+
prune_bundler!
|
84
95
|
|
85
96
|
@environment = @options[:environment] if @options[:environment]
|
86
97
|
set_rack_environment
|
87
98
|
|
88
99
|
if clustered?
|
89
|
-
@options[:logger] = @
|
100
|
+
@options[:logger] = @log_writer
|
90
101
|
|
91
|
-
@runner = Cluster.new(self
|
102
|
+
@runner = Cluster.new(self)
|
92
103
|
else
|
93
|
-
@runner = Single.new(self
|
104
|
+
@runner = Single.new(self)
|
94
105
|
end
|
95
106
|
Puma.stats_object = @runner
|
96
107
|
|
97
108
|
@status = :run
|
98
109
|
|
99
|
-
log_config if
|
110
|
+
log_config if env['PUMA_LOG_CONFIG']
|
100
111
|
end
|
101
112
|
|
102
|
-
attr_reader :binder, :events, :config, :options, :restart_dir
|
113
|
+
attr_reader :binder, :log_writer, :events, :config, :options, :restart_dir
|
103
114
|
|
104
115
|
# Return stats about the server
|
105
116
|
def stats
|
@@ -115,7 +126,7 @@ module Puma
|
|
115
126
|
permission = @options[:state_permission]
|
116
127
|
return unless path
|
117
128
|
|
118
|
-
|
129
|
+
require_relative 'state_file'
|
119
130
|
|
120
131
|
sf = StateFile.new
|
121
132
|
sf.pid = Process.pid
|
@@ -156,6 +167,13 @@ module Puma
|
|
156
167
|
log "* phased-restart called but not available, restarting normally."
|
157
168
|
return restart
|
158
169
|
end
|
170
|
+
|
171
|
+
if @options.file_options[:tag].nil?
|
172
|
+
dir = File.realdirpath(@restart_dir)
|
173
|
+
@options[:tag] = File.basename(dir)
|
174
|
+
set_process_title
|
175
|
+
end
|
176
|
+
|
159
177
|
true
|
160
178
|
end
|
161
179
|
|
@@ -172,16 +190,7 @@ module Puma
|
|
172
190
|
|
173
191
|
# Run the server. This blocks until the server is stopped
|
174
192
|
def run
|
175
|
-
previous_env =
|
176
|
-
if defined?(Bundler)
|
177
|
-
env = Bundler::ORIGINAL_ENV.dup
|
178
|
-
# add -rbundler/setup so we load from Gemfile when restarting
|
179
|
-
bundle = "-rbundler/setup"
|
180
|
-
env["RUBYOPT"] = [env["RUBYOPT"], bundle].join(" ").lstrip unless env["RUBYOPT"].to_s.include?(bundle)
|
181
|
-
env
|
182
|
-
else
|
183
|
-
ENV.to_h
|
184
|
-
end
|
193
|
+
previous_env = get_env
|
185
194
|
|
186
195
|
@config.clamp
|
187
196
|
|
@@ -189,24 +198,11 @@ module Puma
|
|
189
198
|
|
190
199
|
setup_signals
|
191
200
|
set_process_title
|
192
|
-
|
201
|
+
|
202
|
+
# This blocks until the server is stopped
|
193
203
|
@runner.run
|
194
204
|
|
195
|
-
|
196
|
-
when :halt
|
197
|
-
log "* Stopping immediately!"
|
198
|
-
@runner.stop_control
|
199
|
-
when :run, :stop
|
200
|
-
graceful_stop
|
201
|
-
when :restart
|
202
|
-
log "* Restarting..."
|
203
|
-
ENV.replace(previous_env)
|
204
|
-
@runner.stop_control
|
205
|
-
restart!
|
206
|
-
when :exit
|
207
|
-
# nothing
|
208
|
-
end
|
209
|
-
close_binder_listeners unless @status == :restart
|
205
|
+
do_run_finished(previous_env)
|
210
206
|
end
|
211
207
|
|
212
208
|
# Return all tcp ports the launcher may be using, TCP or SSL
|
@@ -250,31 +246,59 @@ module Puma
|
|
250
246
|
|
251
247
|
private
|
252
248
|
|
253
|
-
|
254
|
-
|
255
|
-
|
256
|
-
|
257
|
-
|
258
|
-
|
259
|
-
|
260
|
-
|
261
|
-
|
249
|
+
def get_env
|
250
|
+
if defined?(Bundler)
|
251
|
+
env = Bundler::ORIGINAL_ENV.dup
|
252
|
+
# add -rbundler/setup so we load from Gemfile when restarting
|
253
|
+
bundle = "-rbundler/setup"
|
254
|
+
env["RUBYOPT"] = [env["RUBYOPT"], bundle].join(" ").lstrip unless env["RUBYOPT"].to_s.include?(bundle)
|
255
|
+
env
|
256
|
+
else
|
257
|
+
ENV.to_h
|
262
258
|
end
|
263
259
|
end
|
264
260
|
|
265
|
-
def
|
266
|
-
|
261
|
+
def do_run_finished(previous_env)
|
262
|
+
case @status
|
263
|
+
when :halt
|
264
|
+
do_forceful_stop
|
265
|
+
when :run, :stop
|
266
|
+
do_graceful_stop
|
267
|
+
when :restart
|
268
|
+
do_restart(previous_env)
|
269
|
+
end
|
270
|
+
|
271
|
+
close_binder_listeners unless @status == :restart
|
272
|
+
end
|
273
|
+
|
274
|
+
def do_forceful_stop
|
275
|
+
log "* Stopping immediately!"
|
276
|
+
@runner.stop_control
|
277
|
+
end
|
278
|
+
|
279
|
+
def do_graceful_stop
|
280
|
+
@events.fire_on_stopped!
|
281
|
+
@runner.stop_blocked
|
282
|
+
end
|
283
|
+
|
284
|
+
def do_restart(previous_env)
|
285
|
+
log "* Restarting..."
|
286
|
+
ENV.replace(previous_env)
|
287
|
+
@runner.stop_control
|
288
|
+
restart!
|
267
289
|
end
|
268
290
|
|
269
291
|
def restart!
|
270
292
|
@events.fire_on_restart!
|
271
|
-
@config.run_hooks :on_restart, self, @
|
293
|
+
@config.run_hooks :on_restart, self, @log_writer
|
272
294
|
|
273
295
|
if Puma.jruby?
|
274
296
|
close_binder_listeners
|
275
297
|
|
276
|
-
|
277
|
-
|
298
|
+
require_relative 'jruby_restart'
|
299
|
+
argv = restart_args
|
300
|
+
JRubyRestart.chdir(@restart_dir)
|
301
|
+
Kernel.exec(*argv)
|
278
302
|
elsif Puma.windows?
|
279
303
|
close_binder_listeners
|
280
304
|
|
@@ -290,94 +314,24 @@ module Puma
|
|
290
314
|
end
|
291
315
|
end
|
292
316
|
|
293
|
-
#
|
294
|
-
|
295
|
-
|
296
|
-
|
297
|
-
|
298
|
-
|
299
|
-
|
300
|
-
|
301
|
-
|
302
|
-
Array(@options[:extra_runtime_dependencies]).map do |d_name|
|
303
|
-
if (spec = spec_for_gem(d_name))
|
304
|
-
require_paths_for_gem(spec)
|
305
|
-
else
|
306
|
-
log "* Could not load extra dependency: #{d_name}"
|
307
|
-
nil
|
308
|
-
end
|
309
|
-
end.flatten.compact
|
310
|
-
end
|
311
|
-
|
312
|
-
# @!attribute [r] puma_wild_location
|
313
|
-
def puma_wild_location
|
314
|
-
puma = spec_for_gem("puma")
|
315
|
-
dirs = require_paths_for_gem(puma)
|
316
|
-
puma_lib_dir = dirs.detect { |x| File.exist? File.join(x, '../bin/puma-wild') }
|
317
|
-
File.expand_path(File.join(puma_lib_dir, "../bin/puma-wild"))
|
318
|
-
end
|
319
|
-
|
320
|
-
def prune_bundler
|
321
|
-
return if ENV['PUMA_BUNDLER_PRUNED']
|
322
|
-
return unless defined?(Bundler)
|
323
|
-
require_rubygems_min_version!(Gem::Version.new("2.2"), "prune_bundler")
|
324
|
-
unless puma_wild_location
|
325
|
-
log "! Unable to prune Bundler environment, continuing"
|
326
|
-
return
|
327
|
-
end
|
328
|
-
|
329
|
-
dirs = files_to_require_after_prune
|
330
|
-
|
331
|
-
log '* Pruning Bundler environment'
|
332
|
-
home = ENV['GEM_HOME']
|
333
|
-
bundle_gemfile = Bundler.original_env['BUNDLE_GEMFILE']
|
334
|
-
bundle_app_config = Bundler.original_env['BUNDLE_APP_CONFIG']
|
335
|
-
with_unbundled_env do
|
336
|
-
ENV['GEM_HOME'] = home
|
337
|
-
ENV['BUNDLE_GEMFILE'] = bundle_gemfile
|
338
|
-
ENV['PUMA_BUNDLER_PRUNED'] = '1'
|
339
|
-
ENV["BUNDLE_APP_CONFIG"] = bundle_app_config
|
340
|
-
args = [Gem.ruby, puma_wild_location, '-I', dirs.join(':')] + @original_argv
|
341
|
-
# Ruby 2.0+ defaults to true which breaks socket activation
|
342
|
-
args += [{:close_others => false}]
|
343
|
-
Kernel.exec(*args)
|
344
|
-
end
|
345
|
-
end
|
346
|
-
|
347
|
-
#
|
348
|
-
# Puma's systemd integration allows Puma to inform systemd:
|
349
|
-
# 1. when it has successfully started
|
350
|
-
# 2. when it is starting shutdown
|
351
|
-
# 3. periodically for a liveness check with a watchdog thread
|
352
|
-
#
|
353
|
-
|
354
|
-
def integrate_with_systemd
|
355
|
-
return unless ENV["NOTIFY_SOCKET"]
|
356
|
-
|
357
|
-
begin
|
358
|
-
require 'puma/systemd'
|
359
|
-
rescue LoadError
|
360
|
-
log "Systemd integration failed. It looks like you're trying to use systemd notify but don't have sd_notify gem installed"
|
361
|
-
return
|
317
|
+
# If configured, write the pid of the current process out
|
318
|
+
# to a file.
|
319
|
+
def write_pid
|
320
|
+
path = @options[:pidfile]
|
321
|
+
return unless path
|
322
|
+
cur_pid = Process.pid
|
323
|
+
File.write path, cur_pid, mode: 'wb:UTF-8'
|
324
|
+
at_exit do
|
325
|
+
delete_pidfile if cur_pid == Process.pid
|
362
326
|
end
|
363
|
-
|
364
|
-
log "* Enabling systemd notification integration"
|
365
|
-
|
366
|
-
systemd = Systemd.new(@events)
|
367
|
-
systemd.hook_events
|
368
|
-
systemd.start_watchdog
|
369
327
|
end
|
370
328
|
|
371
|
-
def
|
372
|
-
|
373
|
-
end
|
374
|
-
|
375
|
-
def require_paths_for_gem(gem_spec)
|
376
|
-
gem_spec.full_require_paths
|
329
|
+
def reload_worker_directory
|
330
|
+
@runner.reload_worker_directory if @runner.respond_to?(:reload_worker_directory)
|
377
331
|
end
|
378
332
|
|
379
333
|
def log(str)
|
380
|
-
@
|
334
|
+
@log_writer.log(str)
|
381
335
|
end
|
382
336
|
|
383
337
|
def clustered?
|
@@ -385,15 +339,10 @@ module Puma
|
|
385
339
|
end
|
386
340
|
|
387
341
|
def unsupported(str)
|
388
|
-
@
|
342
|
+
@log_writer.error(str)
|
389
343
|
raise UnsupportedOption
|
390
344
|
end
|
391
345
|
|
392
|
-
def graceful_stop
|
393
|
-
@events.fire_on_stopped!
|
394
|
-
@runner.stop_blocked
|
395
|
-
end
|
396
|
-
|
397
346
|
def set_process_title
|
398
347
|
Process.respond_to?(:setproctitle) ? Process.setproctitle(title) : $0 = title
|
399
348
|
end
|
@@ -419,6 +368,11 @@ module Puma
|
|
419
368
|
@options[:prune_bundler] && clustered? && !@options[:preload_app]
|
420
369
|
end
|
421
370
|
|
371
|
+
def prune_bundler!
|
372
|
+
return unless prune_bundler?
|
373
|
+
BundlePruner.new(@original_argv, @options[:extra_runtime_dependencies], @log_writer).prune
|
374
|
+
end
|
375
|
+
|
422
376
|
def generate_restart_data
|
423
377
|
if dir = @options[:directory]
|
424
378
|
@restart_dir = dir
|
@@ -465,12 +419,14 @@ module Puma
|
|
465
419
|
end
|
466
420
|
|
467
421
|
def setup_signals
|
468
|
-
|
469
|
-
|
470
|
-
|
422
|
+
unless ENV["PUMA_SKIP_SIGUSR2"]
|
423
|
+
begin
|
424
|
+
Signal.trap "SIGUSR2" do
|
425
|
+
restart
|
426
|
+
end
|
427
|
+
rescue Exception
|
428
|
+
log "*** SIGUSR2 not implemented, signal based restart unavailable!"
|
471
429
|
end
|
472
|
-
rescue Exception
|
473
|
-
log "*** SIGUSR2 not implemented, signal based restart unavailable!"
|
474
430
|
end
|
475
431
|
|
476
432
|
unless Puma.jruby?
|
@@ -485,7 +441,8 @@ module Puma
|
|
485
441
|
|
486
442
|
begin
|
487
443
|
Signal.trap "SIGTERM" do
|
488
|
-
|
444
|
+
# Shortcut the control flow in case raise_exception_on_sigterm is true
|
445
|
+
do_graceful_stop
|
489
446
|
|
490
447
|
raise(SignalException, "SIGTERM") if @options[:raise_exception_on_sigterm]
|
491
448
|
end
|
@@ -517,8 +474,8 @@ module Puma
|
|
517
474
|
unless Puma.jruby? # INFO in use by JVM already
|
518
475
|
Signal.trap "SIGINFO" do
|
519
476
|
thread_status do |name, backtrace|
|
520
|
-
@
|
521
|
-
@
|
477
|
+
@log_writer.log(name)
|
478
|
+
@log_writer.log(backtrace.map { |bt| " #{bt}" })
|
522
479
|
end
|
523
480
|
end
|
524
481
|
end
|
@@ -528,23 +485,6 @@ module Puma
|
|
528
485
|
end
|
529
486
|
end
|
530
487
|
|
531
|
-
def require_rubygems_min_version!(min_version, feature)
|
532
|
-
return if min_version <= Gem::Version.new(Gem::VERSION)
|
533
|
-
|
534
|
-
raise "#{feature} is not supported on your version of RubyGems. " \
|
535
|
-
"You must have RubyGems #{min_version}+ to use this feature."
|
536
|
-
end
|
537
|
-
|
538
|
-
# @version 5.0.0
|
539
|
-
def with_unbundled_env
|
540
|
-
bundler_ver = Gem::Version.new(Bundler::VERSION)
|
541
|
-
if bundler_ver < Gem::Version.new('2.1.0')
|
542
|
-
Bundler.with_clean_env { yield }
|
543
|
-
else
|
544
|
-
Bundler.with_unbundled_env { yield }
|
545
|
-
end
|
546
|
-
end
|
547
|
-
|
548
488
|
def log_config
|
549
489
|
log "Configuration:"
|
550
490
|
|
@@ -0,0 +1,147 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'null_io'
|
4
|
+
require_relative 'error_logger'
|
5
|
+
require 'stringio'
|
6
|
+
require 'io/wait' unless Puma::HAS_NATIVE_IO_WAIT
|
7
|
+
|
8
|
+
module Puma
|
9
|
+
|
10
|
+
# Handles logging concerns for both standard messages
|
11
|
+
# (+stdout+) and errors (+stderr+).
|
12
|
+
class LogWriter
|
13
|
+
|
14
|
+
class DefaultFormatter
|
15
|
+
def call(str)
|
16
|
+
str
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
class PidFormatter
|
21
|
+
def call(str)
|
22
|
+
"[#{$$}] #{str}"
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
LOG_QUEUE = Queue.new
|
27
|
+
|
28
|
+
attr_reader :stdout,
|
29
|
+
:stderr
|
30
|
+
|
31
|
+
attr_accessor :formatter, :custom_logger
|
32
|
+
|
33
|
+
# Create a LogWriter that prints to +stdout+ and +stderr+.
|
34
|
+
def initialize(stdout, stderr, env: ENV)
|
35
|
+
@formatter = DefaultFormatter.new
|
36
|
+
@custom_logger = nil
|
37
|
+
@stdout = stdout
|
38
|
+
@stderr = stderr
|
39
|
+
|
40
|
+
@debug = env.key?('PUMA_DEBUG')
|
41
|
+
@error_logger = ErrorLogger.new(@stderr, env: env)
|
42
|
+
end
|
43
|
+
|
44
|
+
DEFAULT = new(STDOUT, STDERR)
|
45
|
+
|
46
|
+
# Returns an LogWriter object which writes its status to
|
47
|
+
# two StringIO objects.
|
48
|
+
def self.strings(env: ENV)
|
49
|
+
LogWriter.new(StringIO.new, StringIO.new, env: env)
|
50
|
+
end
|
51
|
+
|
52
|
+
def self.stdio(env: ENV)
|
53
|
+
LogWriter.new($stdout, $stderr, env: env)
|
54
|
+
end
|
55
|
+
|
56
|
+
def self.null(env: ENV)
|
57
|
+
n = NullIO.new
|
58
|
+
LogWriter.new(n, n, env: env)
|
59
|
+
end
|
60
|
+
|
61
|
+
# Write +str+ to +@stdout+
|
62
|
+
def log(str)
|
63
|
+
if @custom_logger&.respond_to?(:write)
|
64
|
+
@custom_logger.write(format(str))
|
65
|
+
else
|
66
|
+
internal_write "#{@formatter.call str}\n"
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
def write(str)
|
71
|
+
internal_write @formatter.call(str)
|
72
|
+
end
|
73
|
+
|
74
|
+
def internal_write(str)
|
75
|
+
LOG_QUEUE << str
|
76
|
+
while (w_str = LOG_QUEUE.pop(true)) do
|
77
|
+
begin
|
78
|
+
@stdout.is_a?(IO) and @stdout.wait_writable(1)
|
79
|
+
@stdout.write w_str
|
80
|
+
@stdout.flush unless @stdout.sync
|
81
|
+
rescue Errno::EPIPE, Errno::EBADF, IOError, Errno::EINVAL
|
82
|
+
# 'Invalid argument' (Errno::EINVAL) may be raised by flush
|
83
|
+
end
|
84
|
+
end
|
85
|
+
rescue ThreadError
|
86
|
+
end
|
87
|
+
private :internal_write
|
88
|
+
|
89
|
+
def debug?
|
90
|
+
@debug
|
91
|
+
end
|
92
|
+
|
93
|
+
def debug(str)
|
94
|
+
log("% #{str}") if @debug
|
95
|
+
end
|
96
|
+
|
97
|
+
# Write +str+ to +@stderr+
|
98
|
+
def error(str)
|
99
|
+
@error_logger.info(text: @formatter.call("ERROR: #{str}"))
|
100
|
+
exit 1
|
101
|
+
end
|
102
|
+
|
103
|
+
def format(str)
|
104
|
+
formatter.call(str)
|
105
|
+
end
|
106
|
+
|
107
|
+
# An HTTP connection error has occurred.
|
108
|
+
# +error+ a connection exception, +req+ the request,
|
109
|
+
# and +text+ additional info
|
110
|
+
# @version 5.0.0
|
111
|
+
def connection_error(error, req, text="HTTP connection error")
|
112
|
+
@error_logger.info(error: error, req: req, text: text)
|
113
|
+
end
|
114
|
+
|
115
|
+
# An HTTP parse error has occurred.
|
116
|
+
# +error+ a parsing exception,
|
117
|
+
# and +req+ the request.
|
118
|
+
def parse_error(error, req)
|
119
|
+
@error_logger.info(error: error, req: req, text: 'HTTP parse error, malformed request')
|
120
|
+
end
|
121
|
+
|
122
|
+
# An SSL error has occurred.
|
123
|
+
# @param error <Puma::MiniSSL::SSLError>
|
124
|
+
# @param ssl_socket <Puma::MiniSSL::Socket>
|
125
|
+
def ssl_error(error, ssl_socket)
|
126
|
+
peeraddr = ssl_socket.peeraddr.last rescue "<unknown>"
|
127
|
+
peercert = ssl_socket.peercert
|
128
|
+
subject = peercert&.subject
|
129
|
+
@error_logger.info(error: error, text: "SSL error, peer: #{peeraddr}, peer cert: #{subject}")
|
130
|
+
end
|
131
|
+
|
132
|
+
# An unknown error has occurred.
|
133
|
+
# +error+ an exception object, +req+ the request,
|
134
|
+
# and +text+ additional info
|
135
|
+
def unknown_error(error, req=nil, text="Unknown error")
|
136
|
+
@error_logger.info(error: error, req: req, text: text)
|
137
|
+
end
|
138
|
+
|
139
|
+
# Log occurred error debug dump.
|
140
|
+
# +error+ an exception object, +req+ the request,
|
141
|
+
# and +text+ additional info
|
142
|
+
# @version 5.0.0
|
143
|
+
def debug_error(error, req=nil, text="")
|
144
|
+
@error_logger.debug(error: error, req: req, text: text)
|
145
|
+
end
|
146
|
+
end
|
147
|
+
end
|