Search code examples
rubymultithreadingdebuggingthread-safetysidekiq

Thread-safe: Capturing the output of $stdout


I wonder how to capture the output of $stdout in a threaded environment in ruby.

A few details. I use the capture for logging purpose and I have sidekiq to process jobs in the background so the threads. Ive coded:

@previous_stdout, $stdout = $stdout, StringIO.new
self.stdout = $stdout.string

It throws me (for certain threads):

WARN: undefined method `string' for #<IO:<STDOUT>>

Solution

  • If you are logging things yourself, keep a StringIO log per job and write to it:

    @log = StringIO.new
    @log.write 'debug message'
    

    Sidekiq also offers logging options:

    https://github.com/mperham/sidekiq/wiki/Logging

    Otherwise you could try something real hacky like overwriting the printing methods on Kernel and synchronizing it so you never accidentally write to the wrong $stdout. Something like:

    require 'stringio'
    
    module Kernel
      @@io_semaphore = Mutex.new
    
      [ :printf, :p, :print, :puts ].each do |io_write|
        hidden_io_write = "__#{io_write}__"
    
        alias_method hidden_io_write, io_write
    
        define_method(io_write) do |*args|
          @@io_semaphore.synchronize do
            $stdout = Thread.current[:log] || STDOUT
            self.__send__(hidden_io_write, *args)
            $stdout = STDOUT
          end
        end
      end
    end
    
    threads = 3.times.map do
      Thread.new do
        Thread.current[:log] = log = StringIO.new
        sleep(rand)
        puts "testing..."
        log.string
      end
    end
    
    logs = threads.map(&:value)
    p logs
    # => ["testing...\n", "testing...\n", "testing...\n"]