module Jkb::Utils
  module PerThreadLogger
    def add(severity, message = nil, progname = nil)
      severity ||= UNKNOWN
      if @logdev.nil? or severity < @level
        return true
      end
      if progname.nil?
        progname = @progname
      end
      if message.nil?
        if block_given?
          message = yield
        else
          message = progname
          progname = @progname
        end
      end
      current_logdev.write(
        format_message(format_severity(severity), Time.now, progname, message))
      true
    end

    def current_logdev
      Thread.current["#{object_id}_current_logdev"] ||= find_logdev
    end

    def find_logdev
      if idle_logdevs.size > 0
          idle_logdev = idle_logdevs.last
          idle_logdev.instance_variable_set(:@_idle, false)
          idle_logdev
      else
        create_logdev
      end
    end

    def create_logdev
      _logdev = @logdev.dup
      if _logdev.dev != STDOUT
        ext = File.extname(_logdev.dev.path)
        path = _logdev.dev.path.gsub /#{Regexp.escape(ext)}$/, ".#{worker_thread_number}#{ext}"
        file = File.open(path, 'a')
        file.binmode
        file.sync = Rails.application.config.autoflush_log
        _logdev.instance_variable_set(:@dev, file)
      end
      @logdevs << _logdev
      _logdev
    end

    def worker_thread_counter
      self.instance_variable_get(:@worker_thread_counter)
    end

    def worker_thread_number
      Thread.current["#{self.object_id}_logger_num"] ||= (worker_thread_counter.increment)
    end

    def idle_logdevs
      @logdevs.select do |logdev|
        logdev.instance_variable_get(:@_idle)
      end
    end

    def release_current_thread_logdev
      current_logdev.instance_variable_set(:@_idle, true)
    end
    alias_method :release_current, :release_current_thread_logdev

    def self.new(logger = Logger.new(File::NULL))
      logger.instance_variable_set(:@worker_thread_counter, Concurrent::AtomicFixnum.new(-1))
      logger.instance_variable_set(:@logdevs, Concurrent::Array.new)
      logger.extend(self)
    end
  end
end


# threads = []
# pt_logger = Utils::PerThreadLogger.new(Logger.new("#{Rails.root}/log/test_pret_log.log"))
# 5.times do
#   threads << Thread.new do
#     pt_logger.info "hello thread"
#   end
# end
#
# threads.map(&:join)

配合sidekiq使用

Sidekiq.configure_server do |config|
  pt_logger = Jkb::Utils::PerThreadLogger.new(Sidekiq::Logging.logger)
  Sidekiq.error_handlers << Class.new do
    define_method :call do |ex, ctxHash|
      pt_logger.release_current
    end
  end.new
  #pt_logger = Sidekiq::Logging.logger
  Rails.logger = pt_logger
  ActiveRecord::Base.logger = pt_logger
end

其中加了一个error_handlers,是因为sidekiq的worker崩了之后会再重新开一个线程,导致日志数量无限增加,所以需要手动将当前logdev释放