require 'time' require 'logger' module Sidekiq module Logging class Pretty < Logger::Formatter SPACE = " " # Provide a call() method that returns the formatted message. def call(severity, time, program_name, message) "#{time.utc.iso8601(3)} #{::Process.pid} TID-#{Thread.current.object_id.to_s(36)}#{context} #{severity}: #{message}\n" end def context c = Thread.current[:sidekiq_context] " #{c.join(SPACE)}" if c && c.any? end end class WithoutTimestamp < Pretty def call(severity, time, program_name, message) "#{::Process.pid} TID-#{Thread.current.object_id.to_s(36)}#{context} #{severity}: #{message}\n" end end def self.with_context(msg) Thread.current[:sidekiq_context] ||= [] Thread.current[:sidekiq_context] << msg yield ensure Thread.current[:sidekiq_context].pop end def self.initialize_logger(log_target = STDOUT) oldlogger = defined?(@logger) ? @logger : nil @logger = Logger.new(log_target) @logger.level = Logger::INFO @logger.formatter = ENV['DYNO'] ? WithoutTimestamp.new : Pretty.new oldlogger.close if oldlogger && !$TESTING # don't want to close testing's STDOUT logging @logger end def self.logger defined?(@logger) ? @logger : initialize_logger end def self.logger=(log) @logger = (log ? log : Logger.new('/dev/null')) end # This reopens ALL logfiles in the process that have been rotated # using logrotate(8) (without copytruncate) or similar tools. # A +File+ object is considered for reopening if it is: # 1) opened with the O_APPEND and O_WRONLY flags # 2) the current open file handle does not match its original open path # 3) unbuffered (as far as userspace buffering goes, not O_SYNC) # Returns the number of files reopened def self.reopen_logs to_reopen = [] append_flags = File::WRONLY | File::APPEND ObjectSpace.each_object(File) do |fp| begin if !fp.closed? && fp.stat.file? && fp.sync && (fp.fcntl(Fcntl::F_GETFL) & append_flags) == append_flags to_reopen << fp end rescue IOError, Errno::EBADF end end nr = 0 to_reopen.each do |fp| orig_st = begin fp.stat rescue IOError, Errno::EBADF next end begin b = File.stat(fp.path) next if orig_st.ino == b.ino && orig_st.dev == b.dev rescue Errno::ENOENT end begin File.open(fp.path, 'a') { |tmpfp| fp.reopen(tmpfp) } fp.sync = true nr += 1 rescue IOError, Errno::EBADF # not much we can do... end end nr rescue RuntimeError => ex # RuntimeError: ObjectSpace is disabled; each_object will only work with Class, pass -X+O to enable puts "Unable to reopen logs: #{ex.message}" end def logger Sidekiq::Logging.logger end end end