Fix a concurrency issue with the Webserver when overrideing Puma STDERR and STDOUT.

This PR changes how we modify the STDOUT/STDERR in puma, instande of
using `const_set`, we override the constants using a module.
This operation should be thread safe and will make sure the STDERR are
correctly visible.

After when we receive the logger we can swap the null logger with the
real thing in a thread safe way.

Fixes: #5912

Fixes #5918
This commit is contained in:
Pier-Hugues Pellerin 2016-09-14 12:59:01 -04:00
parent 0daeec7e04
commit f0ae34e25a
3 changed files with 50 additions and 23 deletions

View file

@ -0,0 +1,44 @@
# encoding: utf-8
#
# Patch to replace the usage of STDERR and STDOUT
# see: https://github.com/elastic/logstash/issues/5912
module LogStash
class NullLogger
def self.debug(message)
end
end
# Puma uses by default the STDERR an the STDOUT for all his error
# handling, the server class accept custom a events object that can accept custom io object,
# so I just wrap the logger into an IO like object.
class IOWrappedLogger
def initialize(new_logger)
@logger_lock = Mutex.new
@logger = new_logger
end
def sync=(v)
# noop
end
def logger=(logger)
@logger_lock.synchronize { @logger = logger }
end
def puts(str)
# The logger only accept a str as the first argument
@logger_lock.synchronize { @logger.debug(str.to_s) }
end
alias_method :write, :puts
alias_method :<<, :puts
end
end
# Reopen the puma class to create a scoped STDERR and STDOUT
# This operation is thread safe since its done at the class level
# and force JRUBY to flush his classes cache.
module Puma
STDERR = LogStash::IOWrappedLogger.new(LogStash::NullLogger)
STDOUT = LogStash::IOWrappedLogger.new(LogStash::NullLogger)
end

View file

@ -2,7 +2,9 @@
require "logstash/api/rack_app"
require "puma"
require "puma/server"
require "logstash/patches/puma"
require "concurrent"
require "thread"
module LogStash
class WebServer
@ -70,32 +72,13 @@ module LogStash
@server.stop(true) if @server
end
# Puma uses by default the STDERR an the STDOUT for all his error
# handling, the server class accept custom a events object that can accept custom io object,
# so I just wrap the logger into an IO like object.
class IOWrappedLogger
def initialize(logger)
@logger = logger
end
def sync=(v)
end
def puts(str)
# The logger only accept a str as the first argument
@logger.debug(str.to_s)
end
alias_method :write, :puts
alias_method :<<, :puts
end
def start_webserver(port)
# wrap any output that puma could generate into a wrapped logger
# use the puma namespace to override STDERR, STDOUT in that scope.
io_wrapped_logger = IOWrappedLogger.new(@logger)
Puma::STDERR.logger = logger
Puma::STDOUT.logger = logger
::Puma.const_set("STDERR", io_wrapped_logger) unless ::Puma.const_defined?("STDERR")
::Puma.const_set("STDOUT", io_wrapped_logger) unless ::Puma.const_defined?("STDOUT")
io_wrapped_logger = LogStash::IOWrappedLogger.new(logger)
app = LogStash::Api::RackApp.app(logger, agent, http_environment)

View file

@ -126,7 +126,7 @@ describe LogStash::WebServer do
end
end
describe LogStash::WebServer::IOWrappedLogger do
describe LogStash::IOWrappedLogger do
let(:logger) { spy("logger") }
let(:message) { "foobar" }