50 lines
1.3 KiB
Ruby
50 lines
1.3 KiB
Ruby
|
# frozen_string_literal: true
|
||
|
|
||
|
# This class is used to log messages to a specified logger from within a `Signal.trap` context. Most loggers rely on
|
||
|
# methods that are prohibited within a `Signal.trap` context, so this class is used to queue up log messages and then
|
||
|
# log them from a separate thread outside of the `Signal.trap` context.
|
||
|
#
|
||
|
# Example:
|
||
|
# Signal.trap("USR1") do
|
||
|
# SignalTrapLogger.instance.log(Rails.logger, "Received USR1 signal")
|
||
|
# end
|
||
|
#
|
||
|
# Do note that you need to call `SignalTrapLogger.instance.after_fork` after forking a new process to ensure that the
|
||
|
# logging thread is running in the new process.
|
||
|
class SignalTrapLogger
|
||
|
include Singleton
|
||
|
|
||
|
def initialize
|
||
|
@queue = Queue.new
|
||
|
ensure_logging_thread_running
|
||
|
end
|
||
|
|
||
|
def log(logger, message, level: :info)
|
||
|
@queue << { logger:, message:, level: }
|
||
|
end
|
||
|
|
||
|
def after_fork
|
||
|
ensure_logging_thread_running
|
||
|
end
|
||
|
|
||
|
private
|
||
|
|
||
|
def ensure_logging_thread_running
|
||
|
return if @thread&.alive?
|
||
|
|
||
|
@thread =
|
||
|
Thread.new do
|
||
|
loop do
|
||
|
begin
|
||
|
log_entry = @queue.pop
|
||
|
log_entry[:logger].public_send(log_entry[:level], log_entry[:message])
|
||
|
rescue => error
|
||
|
Rails.logger.error(
|
||
|
"Error in SignalTrapLogger thread: #{error.message}\n#{error.backtrace.join("\n")}",
|
||
|
)
|
||
|
end
|
||
|
end
|
||
|
end
|
||
|
end
|
||
|
end
|