1
0
Fork 0
mirror of https://github.com/puma/puma.git synced 2022-11-09 13:48:40 -05:00

implement debug_logger

This commit is contained in:
Vyacheslav Alexeev 2020-05-04 21:30:18 +03:00
parent 774c460e60
commit b625767440
4 changed files with 182 additions and 25 deletions

56
lib/puma/debug_logger.rb Normal file
View file

@ -0,0 +1,56 @@
# frozen_string_literal: true
module Puma
# The implementation of a logging in debug mode.
#
class DebugLogger
attr_reader :ioerr
def initialize(ioerr)
@ioerr = ioerr
@ioerr.sync = true
@debug = ENV.key? 'PUMA_DEBUG'
end
def self.stdio
new $stderr
end
# Any error has occured during debug mode.
# +error+ is an exception object, +env+ the request,
# +options+ hash with additional options:
# - +force+ (default nil) to log info even if debug mode is turned off
# - +print_title+ (default true) to log time and error object inspection
# on the first line.
# - +custom_message+ (default nil) custom string to print after title
# and before all remaining info.
#
def error_dump(error, env=nil, options={})
return unless @debug || options[:force]
options[:print_title] = true unless options.keys.include?(:print_title)
#
# TODO: add all info we have about request
#
string_block = []
if options[:print_title]
string_block << "#{Time.now}: #{error.inspect}"
end
if options[:custom_message]
string_block << "#{options[:custom_message]}"
end
if env
string_block << "Handling request { #{env['REQUEST_METHOD']} #{env['PATH_INFO']} }"
end
string_block << error.backtrace
ioerr.puts string_block.join("\n")
end
end
end

View file

@ -2,6 +2,7 @@
require 'puma/const'
require "puma/null_io"
require 'puma/debug_logger'
require 'stringio'
module Puma
@ -36,6 +37,7 @@ module Puma
@stderr.sync = true
@debug = ENV.key? 'PUMA_DEBUG'
@debug_logger = DebugLogger.new(@stderr)
@hooks = Hash.new { |h,k| h[k] = [] }
end
@ -97,6 +99,7 @@ module Puma
"(#{env[HTTP_X_FORWARDED_FOR] || env[REMOTE_ADDR]}#{env[REQUEST_PATH]}): " \
"#{error.inspect}" \
"\n---\n"
@debug_logger.error_dump(error, env, print_title: false)
end
# An SSL error has occurred.
@ -106,6 +109,7 @@ module Puma
def ssl_error(server, peeraddr, peercert, error)
subject = peercert ? peercert.subject : nil
@stderr.puts "#{Time.now}: SSL error, peer: #{peeraddr}, peer cert: #{subject}, #{error.inspect}"
@debug_logger.error_dump(error, nil, print_title: false)
end
# An unknown error has occurred.
@ -116,14 +120,7 @@ module Puma
if error.respond_to? :render
error.render "#{Time.now}: #{kind} error", @stderr
else
if env
string_block = [ "#{Time.now}: #{kind} error handling request { #{env['REQUEST_METHOD']} #{env['PATH_INFO']} }" ]
string_block << error.inspect
else
string_block = [ "#{Time.now}: #{kind} error: #{error.inspect}" ]
end
string_block << error.backtrace
@stderr.puts string_block.join("\n")
@debug_logger.error_dump(error, env, force: true, custom_message: kind)
end
end

View file

@ -12,6 +12,7 @@ require 'puma/binder'
require 'puma/accept_nonblock'
require 'puma/util'
require 'puma/io_buffer'
require 'puma/debug_logger'
require 'puma/puma_http11'
@ -58,6 +59,7 @@ module Puma
def initialize(app, events=Events.stdio, options={})
@app = app
@events = events
@debug_logger = DebugLogger.stdio
@check, @notify = Puma::Util.pipe
@ -111,16 +113,18 @@ module Puma
def cork_socket(socket)
begin
socket.setsockopt(6, 3, 1) if socket.kind_of? TCPSocket
rescue IOError, SystemCallError
rescue IOError, SystemCallError => e
Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
@debug_logger.error_dump(e)
end
end
def uncork_socket(socket)
begin
socket.setsockopt(6, 3, 0) if socket.kind_of? TCPSocket
rescue IOError, SystemCallError
rescue IOError, SystemCallError => e
Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
@debug_logger.error_dump(e)
end
end
@ -214,7 +218,8 @@ module Puma
client.close
@events.parse_error self, client.env, e
rescue ConnectionError, EOFError
rescue ConnectionError, EOFError => e
@debug_logger.error_dump(e, client.env)
client.close
else
if process_now
@ -293,14 +298,19 @@ module Puma
pool << client
end
rescue SystemCallError
rescue SystemCallError => e
# TODO: check if we able to use client here
@debug_logger.error_dump(e, client&.env)
# nothing
rescue Errno::ECONNABORTED
# client closed the socket even before accept
# TODO: check if we able to use client here
@debug_logger.error_dump(e, client&.env, custom_message: 'Client closed the socket even before accept')
begin
io.close
rescue
rescue => e
Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
# TODO: check if we able to use client here
@debug_logger.error_dump(e, client&.env)
end
end
end
@ -319,8 +329,7 @@ module Puma
end
graceful_shutdown if @status == :stop || @status == :restart
rescue Exception => e
STDERR.puts "Exception handling servers: #{e.message} (#{e.class})"
STDERR.puts e.backtrace
@debug_logger.error_dump(e, force: true, custom_message: 'Exception handling servers')
ensure
@check.close unless @check.closed? # Ruby 2.2 issue
@notify.close
@ -397,7 +406,8 @@ module Puma
end
# The client disconnected while we were reading data
rescue ConnectionError
rescue ConnectionError => e
@debug_logger.error_dump(e, client.env, custom_message: 'The client disconnected while we were reading data')
# Swallow them. The ensure tries to close +client+ down
# SSL handshake error
@ -433,8 +443,9 @@ module Puma
begin
client.close if close_socket
rescue IOError, SystemCallError
rescue IOError, SystemCallError => e
Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
@debug_logger.error_dump(e, client.env)
# Already closed
rescue StandardError => e
@events.unknown_error self, e, "Client"
@ -485,10 +496,11 @@ module Puma
unless env.key?(REMOTE_ADDR)
begin
addr = client.peerip
rescue Errno::ENOTCONN
rescue Errno::ENOTCONN => e
# Client disconnects can result in an inability to get the
# peeraddr from the socket; default to localhost.
addr = LOCALHOST_IP
@debug_logger.error_dump(e, custom_message: 'Client disconnects can result in an inability to get the peeraddr from the socket')
end
# Set unix socket addrs to localhost
@ -562,8 +574,9 @@ module Puma
end
fast_write client, "\r\n".freeze
rescue ConnectionError
rescue ConnectionError => e
# noop, if we lost the socket we just won't send the early hints
@debug_logger.error_dump(e, env)
end
}
end
@ -591,10 +604,12 @@ module Puma
rescue ThreadPool::ForceShutdown => e
@events.unknown_error self, e, "Rack app", env
@events.log "Detected force shutdown of a thread"
@debug_logger.error_dump(e, env)
status, headers, res_body = lowlevel_error(e, env, 503)
rescue Exception => e
@events.unknown_error self, e, "Rack app", env
@debug_logger.error_dump(e, env)
status, headers, res_body = lowlevel_error(e, env, 500)
end
@ -731,7 +746,8 @@ module Puma
fast_write client, CLOSE_CHUNKED
client.flush
end
rescue SystemCallError, IOError
rescue SystemCallError, IOError => e
@debug_logger.error_dump(e, env)
raise ConnectionError, "Connection error detected during write"
end
@ -859,7 +875,8 @@ module Puma
client = Client.new io, @binder.env(sock)
@thread_pool << client
end
rescue SystemCallError
rescue SystemCallError => e
@debug_logger.error_dump(e)
end
end
end
@ -883,10 +900,12 @@ module Puma
def notify_safely(message)
begin
@notify << message
rescue IOError
rescue IOError => e
# The server, in another thread, is shutting down
Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
@debug_logger.error_dump(e)
rescue RuntimeError => e
@debug_logger.error_dump(e)
# Temporary workaround for https://bugs.ruby-lang.org/issues/13239
if e.message.include?('IOError')
Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
@ -919,13 +938,15 @@ module Puma
while true
begin
n = io.syswrite str
rescue Errno::EAGAIN, Errno::EWOULDBLOCK
rescue Errno::EAGAIN, Errno::EWOULDBLOCK => e
@debug_logger.error_dump(e)
if !IO.select(nil, [io], nil, WRITE_TIMEOUT)
raise ConnectionError, "Socket timeout writing data"
end
retry
rescue Errno::EPIPE, SystemCallError, IOError
rescue Errno::EPIPE, SystemCallError, IOError => e
@debug_logger.error_dump(e)
raise ConnectionError, "Socket timeout writing data"
end

83
test/test_debug_logger.rb Normal file
View file

@ -0,0 +1,83 @@
require_relative "helper"
class TestDebugLogger < Minitest::Test
def setup
@debug_logger = Puma::DebugLogger.stdio
end
def test_stdio
debug_logger = Puma::DebugLogger.stdio
assert_equal STDERR, debug_logger.ioerr
end
def test_error_dump_if_debug_false
_, err = capture_io do
@debug_logger.error_dump(StandardError.new('ready'))
end
assert_empty err
end
def test_error_dump_force
_, err = capture_io do
Puma::DebugLogger.stdio.error_dump(StandardError.new('ready'), nil, force: true)
end
assert_match %r!ready!, err
end
def test_error_dump_with_only_error
with_debug_mode do
_, err = capture_io do
Puma::DebugLogger.stdio.error_dump(StandardError.new('ready'), nil)
end
assert_match %r!#<StandardError: ready>!, err
end
end
def test_error_dump_with_env
with_debug_mode do
env = {
'REQUEST_METHOD' => 'GET',
'PATH_INFO' => '/debug'
}
_, err = capture_io do
Puma::DebugLogger.stdio.error_dump(StandardError.new, env)
end
assert_match %r!Handling request { GET /debug }!, err
end
end
def test_error_dump_without_title
with_debug_mode do
_, err = capture_io do
Puma::DebugLogger.stdio.error_dump(StandardError.new('ready'), nil, print_title: false)
end
refute_match %r!#<StandardError: ready>!, err
end
end
def test_error_dump_with_custom_message
with_debug_mode do
_, err = capture_io do
Puma::DebugLogger.stdio.error_dump(StandardError.new, nil, custom_message: 'The client disconnected while we were reading data')
end
assert_match %r!The client disconnected while we were reading data!, err
end
end
private
def with_debug_mode
original_debug, ENV["PUMA_DEBUG"] = ENV["PUMA_DEBUG"], "1"
yield
ensure
ENV["PUMA_DEBUG"] = original_debug
end
end