lib/puma/error_logger.rb
# frozen_string_literal: true
require_relative 'const'
module Puma
# The implementation of a detailed error logging.
# @version 5.0.0
#
class ErrorLogger
include Const
attr_reader :ioerr
REQUEST_FORMAT = %{"%s %s%s" - (%s)}
LOG_QUEUE = Queue.new
def initialize(ioerr)
@ioerr = ioerr
@debug = ENV.key? 'PUMA_DEBUG'
end
def self.stdio
new $stderr
end
# Print occurred error details.
# +options+ hash with additional options:
# - +error+ is an exception object
# - +req+ the http request
# - +text+ (default nil) custom string to print in title
# and before all remaining info.
#
def info(options={})
internal_write title(options)
end
# Print occurred error details only if
# environment variable PUMA_DEBUG is defined.
# +options+ hash with additional options:
# - +error+ is an exception object
# - +req+ the http request
# - +text+ (default nil) custom string to print in title
# and before all remaining info.
#
def debug(options={})
return unless @debug
error = options[:error]
req = options[:req]
string_block = []
string_block << title(options)
string_block << request_dump(req) if request_parsed?(req)
string_block << error.backtrace if error
internal_write string_block.join("\n")
end
def title(options={})
text = options[:text]
req = options[:req]
error = options[:error]
string_block = ["#{Time.now}"]
string_block << " #{text}" if text
string_block << " (#{request_title(req)})" if request_parsed?(req)
string_block << ": #{error.inspect}" if error
string_block.join('')
end
def request_dump(req)
"Headers: #{request_headers(req)}\n" \
"Body: #{req.body}"
end
def request_title(req)
env = req.env
REQUEST_FORMAT % [
env[REQUEST_METHOD],
env[REQUEST_PATH] || env[PATH_INFO],
env[QUERY_STRING] || "",
env[HTTP_X_FORWARDED_FOR] || env[REMOTE_ADDR] || "-"
]
end
def request_headers(req)
headers = req.env.select { |key, _| key.start_with?('HTTP_') }
headers.map { |key, value| [key[5..-1], value] }.to_h.inspect
end
def request_parsed?(req)
req && req.env[REQUEST_METHOD]
end
def internal_write(str)
LOG_QUEUE << str
while (w_str = LOG_QUEUE.pop(true)) do
begin
@ioerr.is_a?(IO) and @ioerr.wait_writable(1)
@ioerr.write "#{w_str}\n"
@ioerr.flush unless @ioerr.sync
rescue Errno::EPIPE, Errno::EBADF, IOError, Errno::EINVAL
# 'Invalid argument' (Errno::EINVAL) may be raised by flush
end
end
rescue ThreadError
end
private :internal_write
end
end