ฉันจะมีเอาต์พุตบันทึกทับทิมเป็น stdout และไฟล์ได้อย่างไร


96

คล้ายกับฟังก์ชันทีออฟในคนตัดไม้


1
เพิ่มก่อนที่ไฟล์ทำงานสำหรับฉันดังนั้น| tee สังเกตท่อ นี่คือเคล็ดลับในcoderwall.com/p/y_b3ra/…Logger.new("| tee test.log")
Mike W

@mjwatts ใช้tee --append test.logเพื่อป้องกันการเขียนทับ
fangxing

คำตอบ:


124

คุณสามารถเขียนIOคลาสหลอกที่จะเขียนลงในIOวัตถุหลายชิ้น สิ่งที่ต้องการ:

class MultiIO
  def initialize(*targets)
     @targets = targets
  end

  def write(*args)
    @targets.each {|t| t.write(*args)}
  end

  def close
    @targets.each(&:close)
  end
end

จากนั้นตั้งเป็นไฟล์บันทึกของคุณ:

log_file = File.open("log/debug.log", "a")
Logger.new MultiIO.new(STDOUT, log_file)

ทุกครั้งที่LoggerเรียกวัตถุputsของคุณMultiIOมันจะเขียนถึงทั้งสองอย่างSTDOUTและไฟล์บันทึกของคุณ

แก้ไข:ฉันดำเนินการต่อและหาส่วนที่เหลือของอินเทอร์เฟซ อุปกรณ์บันทึกต้องตอบสนองwriteและclose(ไม่puts) ตราบเท่าที่MultiIOตอบสนองต่อสิ่งเหล่านั้นและมอบฉันทะให้กับอ็อบเจ็กต์ IO จริงสิ่งนี้ควรใช้งานได้


ถ้าคุณดู ctor ของคนตัดไม้คุณจะเห็นว่าสิ่งนี้จะทำให้การหมุนบันทึกยุ่งเหยิง def initialize(log = nil, opt = {}) @dev = @filename = @shift_age = @shift_size = nil @mutex = LogDeviceMutex.new if log.respond_to?(:write) and log.respond_to?(:close) @dev = log else @dev = open_logfile(log) @dev.sync = true @filename = log @shift_age = opt[:shift_age] || 7 @shift_size = opt[:shift_size] || 1048576 end end
JeffCharter

3
หมายเหตุใน Ruby 2.2 @targets.each(&:close)มีค่าเสื่อมราคา
xis

ทำงานให้ฉันจนกว่าฉันจะรู้ว่าฉันจำเป็นต้องโทรเป็นระยะ ๆ : ปิด log_file เพื่อรับ log_file เพื่ออัปเดตสิ่งที่คนตัดไม้บันทึกไว้ (โดยพื้นฐานแล้วคือ "บันทึก") STDOUT ไม่ชอบ: การถูกเรียกอย่างใกล้ชิดการเอาชนะแนวคิด MultoIO เพิ่มแฮ็คเพื่อข้าม: ปิดยกเว้นไฟล์คลาส แต่หวังว่าฉันจะมีโซลูชันที่หรูหรากว่านี้
Kim Miller

48

@ วิธีแก้ของเดวิดดีมาก ฉันได้สร้างคลาส delegator ทั่วไปสำหรับหลายเป้าหมายตามรหัสของเขา

require 'logger'

class MultiDelegator
  def initialize(*targets)
    @targets = targets
  end

  def self.delegate(*methods)
    methods.each do |m|
      define_method(m) do |*args|
        @targets.map { |t| t.send(m, *args) }
      end
    end
    self
  end

  class <<self
    alias to new
  end
end

log_file = File.open("debug.log", "a")
log = Logger.new MultiDelegator.delegate(:write, :close).to(STDOUT, log_file)

คุณช่วยอธิบายได้ไหมว่าวิธีนี้ดีกว่าอย่างไรหรืออะไรคือสิ่งที่ได้รับการปรับปรุงสาธารณูปโภคของแนวทางนี้มากกว่าวิธีธรรมดาที่แนะนำโดย David
Manish Sapariya

5
เป็นการแยกความกังวล MultiDelegator รู้เฉพาะเกี่ยวกับการมอบหมายการโทรไปยังหลายเป้าหมาย ความจริงที่ว่าอุปกรณ์บันทึกข้อมูลจำเป็นต้องมีการเขียนและมีการนำวิธีการปิดมาใช้ในผู้โทร สิ่งนี้ทำให้ MultiDelegator สามารถใช้งานได้ในสถานการณ์อื่นนอกเหนือจากการบันทึก
jonas054

ทางออกที่ดี ฉันพยายามใช้สิ่งนี้เพื่อทีเอาต์พุตจากงานคราดไปยังไฟล์บันทึก เพื่อให้สามารถใช้งานได้ (เพื่อให้สามารถเรียก $ stdout.puts โดยไม่ได้รับ "private method` `puts 'called") ฉันต้องเพิ่มวิธีการอีกสองสามวิธี: log_file = File.open ("tmp / rake.log "," a ") $ stdout = MultiDelegator.delegate (: write,: close,: puts,: print) .to (STDOUT, log_file) จะดีถ้าสามารถสร้างคลาส Tee ที่สืบทอดมาจาก MultiDelegator เช่นเดียวกับที่คุณสามารถทำได้กับคลาส Delegator ใน stdlib ...
Tyler Rick

ฉันมาพร้อมกับการใช้งานแบบ Delegator ซึ่งฉันเรียกว่า DelegatorToAll วิธีนี้คุณไม่จำเป็นต้องแสดงรายการวิธีการทั้งหมดที่คุณต้องการมอบสิทธิ์เนื่องจากจะมอบหมายวิธีการทั้งหมดที่กำหนดไว้ในคลาส delegate (IO): คลาสที <DelegateToAllClass (IO) end $ stdout = Tee.new (STDOUT , File.open ("# { FILE } .log", "a")) ดูรายละเอียดเพิ่มเติมที่gist.github.com/TylerRick/4990898
Tyler Rick

1
ฉันชอบโซลูชันของคุณมาก แต่มันไม่ดีในฐานะตัวแทนทั่วไปที่สามารถใช้งานได้หลายครั้งเนื่องจากการมอบหมายทุกครั้งจะก่อมลพิษทุกอินสแตนซ์ด้วยวิธีการใหม่ ๆ ฉันโพสต์คำตอบ ( stackoverflow.com/a/36659911/123376 ) ที่แก้ไขปัญหานี้ ฉันโพสต์คำตอบแทนการแก้ไขเนื่องจากอาจเป็นการศึกษาเพื่อดูความแตกต่างระหว่างการใช้งานทั้งสองอย่างขณะที่ฉันโพสต์ตัวอย่าง
Rado

35

หากคุณอยู่ใน Rails 3 หรือ 4 ตามที่บล็อกโพสต์นี้ชี้ให้เห็นRails 4 มีฟังก์ชันนี้ในตัว คุณสามารถทำได้:

# config/environment/production.rb
file_logger = Logger.new(Rails.root.join("log/alternative-output.log"))
config.logger.extend(ActiveSupport::Logger.broadcast(file_logger))

หรือถ้าคุณใช้ Rails 3 คุณสามารถย้อนกลับได้:

# config/initializers/alternative_output_log.rb

# backported from rails4
module ActiveSupport
  class Logger < ::Logger
    # Broadcasts logs to multiple loggers. Returns a module to be
    # `extended`'ed into other logger instances.
    def self.broadcast(logger)
      Module.new do
        define_method(:add) do |*args, &block|
          logger.add(*args, &block)
          super(*args, &block)
        end

        define_method(:<<) do |x|
          logger << x
          super(x)
        end

        define_method(:close) do
          logger.close
          super()
        end

        define_method(:progname=) do |name|
          logger.progname = name
          super(name)
        end

        define_method(:formatter=) do |formatter|
          logger.formatter = formatter
          super(formatter)
        end

        define_method(:level=) do |level|
          logger.level = level
          super(level)
        end
      end
    end
  end
end

file_logger = Logger.new(Rails.root.join("log/alternative-output.log"))
Rails.logger.extend(ActiveSupport::Logger.broadcast(file_logger))

ใช้ได้กับภายนอกรางหรือรางเท่านั้น?
Ed Sykes

ขึ้นอยู่กับ ActiveSupport ดังนั้นหากคุณมีการพึ่งพานั้นอยู่แล้วคุณสามารถอินสแตนซ์extendใดก็ได้ActiveSupport::Loggerตามที่แสดงด้านบน
phillbaker

ขอบคุณมันมีประโยชน์
Lucas

ฉันคิดว่านี่เป็นคำตอบที่ง่ายและมีประสิทธิภาพที่สุดแม้ว่าฉันจะมีความแปลกประหลาดบ้างโดยใช้การconfig.logger.extend()กำหนดค่าสภาพแวดล้อมภายในของฉัน แต่ฉันตั้งค่าconfig.loggerเป็นSTDOUTในสภาพแวดล้อมของฉันจากนั้นขยายตัวบันทึกในตัวเริ่มต้นอื่น
mattsch

14

สำหรับผู้ที่ชอบความเรียบง่าย:

log = Logger.new("| tee test.log") # note the pipe ( '|' )
log.info "hi" # will log to both STDOUT and test.log

แหล่งที่มา

หรือพิมพ์ข้อความในฟอร์แมตเตอร์ Logger:

log = Logger.new("test.log")
log.formatter = proc do |severity, datetime, progname, msg|
    puts msg
    msg
end
log.info "hi" # will log to both STDOUT and test.log

ฉันใช้เทคนิคนี้ในการพิมพ์ไปยังไฟล์บันทึกบริการ Cloud Logger (logentries) และถ้าเป็นสภาพแวดล้อม dev - พิมพ์ไปยัง STDOUT ด้วย


2
"| tee test.log"จะเขียนทับเอาต์พุตเก่าอาจเป็น"| tee -a test.log"แทน
fangxing

13

แม้ว่าฉันจะชอบคำแนะนำอื่น ๆ แต่ฉันพบว่าฉันมีปัญหาเดียวกันนี้ แต่ต้องการความสามารถในการมีระดับการบันทึกที่แตกต่างกันสำหรับ STDERR และไฟล์

ฉันลงเอยด้วยกลยุทธ์การกำหนดเส้นทางที่มัลติเพล็กซ์ที่ระดับคนตัดไม้แทนที่จะเป็นระดับ IO เพื่อให้คนตัดไม้แต่ละคนสามารถทำงานในระดับการบันทึกอิสระได้:

class MultiLogger
  def initialize(*targets)
    @targets = targets
  end

  %w(log debug info warn error fatal unknown).each do |m|
    define_method(m) do |*args|
      @targets.map { |t| t.send(m, *args) }
    end
  end
end

stderr_log = Logger.new(STDERR)
file_log = Logger.new(File.open('logger.log', 'a'))

stderr_log.level = Logger::INFO
file_log.level = Logger::DEBUG

log = MultiLogger.new(stderr_log, file_log)

1
ฉันชอบโซลูชันนี้มากที่สุดเนื่องจากเป็น (1) เรียบง่ายและ (2) สนับสนุนให้คุณใช้คลาส Logger ของคุณซ้ำแทนที่จะสมมติว่าทุกอย่างไปที่ไฟล์ ในกรณีของฉันฉันต้องการเข้าสู่ระบบ STDOUT และแอป GELF สำหรับ Graylog การมีMultiLoggerคำอธิบายที่เหมือน @dsz นั้นเหมาะสมอย่างยิ่ง ขอบคุณสำหรับการแบ่งปัน!
Eric Kramer

เพิ่มส่วนเพื่อจัดการ pseudovariables (setters / getters)
Eric Kramer

12

คุณยังสามารถเพิ่มฟังก์ชันการบันทึกหลายอุปกรณ์ลงใน Logger ได้โดยตรง:

require 'logger'

class Logger
  # Creates or opens a secondary log file.
  def attach(name)
    @logdev.attach(name)
  end

  # Closes a secondary log file.
  def detach(name)
    @logdev.detach(name)
  end

  class LogDevice # :nodoc:
    attr_reader :devs

    def attach(log)
      @devs ||= {}
      @devs[log] = open_logfile(log)
    end

    def detach(log)
      @devs ||= {}
      @devs[log].close
      @devs.delete(log)
    end

    alias_method :old_write, :write
    def write(message)
      old_write(message)

      @devs ||= {}
      @devs.each do |log, dev|
        dev.write(message)
      end
    end
  end
end

ตัวอย่างเช่น:

logger = Logger.new(STDOUT)
logger.warn('This message goes to stdout')

logger.attach('logfile.txt')
logger.warn('This message goes both to stdout and logfile.txt')

logger.detach('logfile.txt')
logger.warn('This message goes just to stdout')

9

นี่คือการนำไปใช้งานอื่นที่ได้รับแรงบันดาลใจจากคำตอบของ@ jonas054

สิ่งนี้ใช้รูปแบบที่คล้ายกับDelegator. ด้วยวิธีนี้คุณไม่จำเป็นต้องแสดงรายการวิธีการทั้งหมดที่คุณต้องการมอบสิทธิ์เนื่องจากจะมอบหมายวิธีการทั้งหมดที่กำหนดไว้ในวัตถุเป้าหมายใด ๆ :

class Tee < DelegateToAllClass(IO)
end

$stdout = Tee.new(STDOUT, File.open("#{__FILE__}.log", "a"))

คุณควรจะสามารถใช้สิ่งนี้กับ Logger ได้เช่นกัน

delegate_to_all.rb จากที่นี่: https://gist.github.com/TylerRick/4990898



3

คำตอบของ @ jonas054 ข้างต้นนั้นยอดเยี่ยมมาก แต่ก็MultiDelegatorสร้างความเสียหายให้กับชั้นเรียนกับผู้ร่วมประชุมใหม่ทุกคน หากคุณใช้MultiDelegatorหลาย ๆ ครั้งระบบจะเพิ่มเมธอดให้กับคลาสซึ่งเป็นสิ่งที่ไม่พึงปรารถนา (ดูตัวอย่าง)

นี่คือการใช้งานแบบเดียวกัน แต่ใช้คลาสที่ไม่ระบุชื่อดังนั้นวิธีการนี้จึงไม่ก่อให้เกิดมลพิษต่อคลาส delegator

class BetterMultiDelegator

  def self.delegate(*methods)
    Class.new do
      def initialize(*targets)
        @targets = targets
      end

      methods.each do |m|
        define_method(m) do |*args|
          @targets.map { |t| t.send(m, *args) }
        end
      end

      class <<self
        alias to new
      end
    end # new class
  end # delegate

end

นี่คือตัวอย่างของมลพิษของวิธีการที่มีการใช้งานดั้งเดิมซึ่งแตกต่างจากการใช้งานที่แก้ไขแล้ว:

tee = MultiDelegator.delegate(:write).to(STDOUT)
tee.respond_to? :write
# => true
tee.respond_to? :size
# => false 

ทั้งหมดเป็นสิ่งที่ดีข้างต้น teeมีwriteวิธีการ แต่ไม่มีsizeวิธีการตามที่คาดไว้ ตอนนี้ให้พิจารณาเมื่อเราสร้างผู้รับมอบสิทธิ์อื่น:

tee2 = MultiDelegator.delegate(:size).to("bar")
tee2.respond_to? :size
# => true
tee2.respond_to? :write
# => true   !!!!! Bad
tee.respond_to? :size
# => true   !!!!! Bad

โอ้ไม่tee2ตอบsizeตามที่คาดไว้ แต่ก็ตอบสนองด้วยwriteเพราะผู้รับมอบสิทธิ์คนแรก แม้teeตอนนี้จะตอบสนองต่อsizeเนื่องจากมลพิษทางวิธี

ตรงกันข้ามกับโซลูชันคลาสที่ไม่ระบุชื่อทุกอย่างเป็นไปตามที่คาดไว้:

see = BetterMultiDelegator.delegate(:write).to(STDOUT)
see.respond_to? :write
# => true
see.respond_to? :size
# => false

see2 = BetterMultiDelegator.delegate(:size).to("bar")
see2.respond_to? :size
# => true
see2.respond_to? :write
# => false
see.respond_to? :size
# => false

2

คุณ จำกัด เฉพาะคนตัดไม้มาตรฐานหรือไม่?

หากไม่มีคุณสามารถใช้log4r :

require 'log4r' 

LOGGER = Log4r::Logger.new('mylog')
LOGGER.outputters << Log4r::StdoutOutputter.new('stdout')
LOGGER.outputters << Log4r::FileOutputter.new('file', :filename => 'test.log') #attach to existing log-file

LOGGER.info('aa') #Writs on STDOUT and sends to file

ข้อดีอย่างหนึ่ง: คุณสามารถกำหนดระดับการบันทึกที่แตกต่างกันสำหรับ stdout และไฟล์


1

ฉันใช้แนวคิดเดียวกันกับ "การมอบหมายวิธีการทั้งหมดให้กับองค์ประกอบย่อย" ที่คนอื่น ๆ ได้สำรวจไปแล้ว แต่กำลังส่งคืนค่าตอบแทนของการเรียกสุดท้ายของเมธอดให้แต่ละคน ถ้าฉันไม่ทำมันพังlogger-colorsซึ่งคาดหวังว่าIntegerและแผนที่กำลังส่งคืนArrayไฟล์.

class MultiIO
  def self.delegate_all
    IO.methods.each do |m|
      define_method(m) do |*args|
        ret = nil
        @targets.each { |t| ret = t.send(m, *args) }
        ret
      end
    end
  end

  def initialize(*targets)
    @targets = targets
    MultiIO.delegate_all
  end
end

สิ่งนี้จะกำหนดทุกวิธีใหม่ให้กับเป้าหมายทั้งหมดและส่งคืนเฉพาะค่าส่งคืนของการโทรครั้งสุดท้าย

นอกจากนี้หากคุณต้องการสีต้องใส่ STDOUT หรือ STDERR เนื่องจากควรมีเพียงสองสีเท่านั้นที่ควรได้รับการส่งออก แต่จากนั้นมันจะส่งออกสีไปยังไฟล์ของคุณด้วย

logger = Logger.new MultiIO.new(File.open("log/test.log", 'w'), STDOUT)
logger.error "Roses are red"
logger.unknown "Violets are blue"

1

ฉันได้เขียน RubyGem เล็กน้อยที่ช่วยให้คุณทำสิ่งต่างๆเหล่านี้ได้:

# Pipe calls to an instance of Ruby's logger class to $stdout
require 'teerb'

log_file = File.open("debug.log", "a")
logger = Logger.new(TeeRb::IODelegate.new(log_file, STDOUT))

logger.warn "warn"
$stderr.puts "stderr hello"
puts "stdout hello"

คุณสามารถค้นหารหัสได้ที่ github: teerb


1

อีกวิธีหนึ่ง หากคุณใช้การบันทึกแบบแท็กและต้องการแท็กในไฟล์บันทึกอื่นด้วยคุณสามารถทำได้ด้วยวิธีนี้

# backported from rails4
# config/initializers/active_support_logger.rb
module ActiveSupport
 class Logger < ::Logger

 # Broadcasts logs to multiple loggers. Returns a module to be
 # `extended`'ed into other logger instances.
 def self.broadcast(logger)
  Module.new do
    define_method(:add) do |*args, &block|
      logger.add(*args, &block)
      super(*args, &block)
    end

    define_method(:<<) do |x|
      logger << x
      super(x)
    end

    define_method(:close) do
      logger.close
      super()
    end

    define_method(:progname=) do |name|
      logger.progname = name
      super(name)
    end

    define_method(:formatter=) do |formatter|
      logger.formatter = formatter
      super(formatter)
    end

    define_method(:level=) do |level|
      logger.level = level
      super(level)
    end

   end # Module.new
 end # broadcast

 def initialize(*args)
   super
   @formatter = SimpleFormatter.new
 end

  # Simple formatter which only displays the message.
  class SimpleFormatter < ::Logger::Formatter
   # This method is invoked when a log event occurs
   def call(severity, time, progname, msg)
   element = caller[4] ? caller[4].split("/").last : "UNDEFINED"
    "#{Thread.current[:activesupport_tagged_logging_tags]||nil } # {time.to_s(:db)} #{severity} #{element} -- #{String === msg ? msg : msg.inspect}\n"
   end
  end

 end # class Logger
end # module ActiveSupport

custom_logger = ActiveSupport::Logger.new(Rails.root.join("log/alternative_#{Rails.env}.log"))
Rails.logger.extend(ActiveSupport::Logger.broadcast(custom_logger))

หลังจากนี้คุณจะได้รับแท็ก uuid ในคนตัดไม้ทางเลือก

["fbfea87d1d8cc101a4ff9d12461ae810"] 2015-03-12 16:54:04 INFO logger.rb:28:in `call_app' -- 
["fbfea87d1d8cc101a4ff9d12461ae810"] 2015-03-12 16:54:04 INFO   logger.rb:31:in `call_app' -- Started POST "/psp/entrypoint" for 192.168.56.1 at 2015-03-12 16:54:04 +0700

หวังว่าจะช่วยใครบางคน


เรียบง่ายเชื่อถือได้และทำงานได้อย่างยอดเยี่ยม ขอบคุณ! โปรดทราบว่าActiveSupport::Loggerการทำงานออกจากกล่องที่มีนี้ - คุณเพียงแค่ต้องใช้ด้วยRails.logger.extend ActiveSupport::Logger.broadcast(...)
XtraSimplicity

0

อีกหนึ่งทางเลือก ;-)

require 'logger'

class MultiDelegator
  def initialize(*targets)
    @targets = targets
  end

  def method_missing(method_sym, *arguments, &block)
    @targets.each do |target|
      target.send(method_sym, *arguments, &block) if target.respond_to?(method_sym)
    end
  end
end

log = MultiDelegator.new(Logger.new(STDOUT), Logger.new(File.open("debug.log", "a")))

log.info('Hello ...')

0

ฉันชอบแนวทางMultiIO มันทำงานได้ดีกับทับทิมLogger หากคุณใช้IO บริสุทธิ์มันจะหยุดทำงานเนื่องจากไม่มีวิธีการบางอย่างที่คาดว่าวัตถุ IO จะมี ท่อถูกกล่าวถึงมาก่อนที่นี่: ฉันจะมีเอาต์พุตบันทึกทับทิมไปยัง stdout และไฟล์ได้อย่างไร . นี่คือสิ่งที่ดีที่สุดสำหรับฉัน

def watch(cmd)
  output = StringIO.new
  IO.popen(cmd) do |fd|
    until fd.eof?
      bit = fd.getc
      output << bit
      $stdout.putc bit
    end
  end
  output.rewind
  [output.read, $?.success?]
ensure
  output.close
end

result, success = watch('./my/shell_command as a String')

หมายเหตุฉันรู้ว่านี่ไม่ได้ตอบคำถามโดยตรง แต่เกี่ยวข้องอย่างยิ่ง เมื่อใดก็ตามที่ฉันค้นหาเอาต์พุตไปยัง IO หลายตัวฉันก็เจอหัวข้อนี้ดังนั้นฉันหวังว่าคุณจะพบว่ามีประโยชน์เช่นกัน


0

นี่คือการทำให้โซลูชันของ @ rado ง่ายขึ้น

def delegator(*methods)
  Class.new do
    def initialize(*targets)
      @targets = targets
    end

    methods.each do |m|
      define_method(m) do |*args|
        @targets.map { |t| t.send(m, *args) }
      end
    end

    class << self
      alias for new
    end
  end # new class
end # delegate

มันมีประโยชน์เหมือนกับของเขาโดยไม่ต้องใช้กระดาษห่อหุ้มชั้นนอก ยูทิลิตี้ที่มีประโยชน์ที่จะมีในไฟล์ทับทิมแยกต่างหาก

ใช้มันเป็นซับเดียวเพื่อสร้างอินสแตนซ์ delegator ดังนี้:

IO_delegator_instance = delegator(:write, :read).for(STDOUT, STDERR)
IO_delegator_instance.write("blah")

หรือใช้เป็นโรงงานดังนี้:

logger_delegator_class = delegator(:log, :warn, :error)
secret_delegator = logger_delegator_class(main_logger, secret_logger)
secret_delegator.warn("secret")

general_delegator = logger_delegator_class(main_logger, debug_logger, other_logger) 
general_delegator.log("message")

0

คุณสามารถใช้Loog::Teeวัตถุจากloogอัญมณี:

require 'loog'
logger = Loog::Tee.new(first, second)

สิ่งที่คุณกำลังมองหา


0

หากคุณพอใจกับการใช้ActiveSupportงานฉันขอแนะนำอย่างยิ่งให้ตรวจสอบActiveSupport::Logger.broadcastซึ่งเป็นวิธีที่ยอดเยี่ยมและรัดกุมมากในการเพิ่มปลายทางบันทึกเพิ่มเติมให้กับคนตัดไม้

ในความเป็นจริงหากคุณใช้ Rails 4+ (ณ การกระทำนี้ ) คุณไม่จำเป็นต้องทำอะไรเพื่อให้ได้พฤติกรรมที่ต้องการ - อย่างน้อยถ้าคุณใช้ไฟล์rails console. เมื่อใดก็ตามที่คุณใช้rails consoleRails จะขยายโดยอัตโนมัติRails.loggerเพื่อให้เอาต์พุตทั้งสองไปยังปลายทางไฟล์ปกติ ( log/production.logตัวอย่างเช่น) และSTDERR:

    console do |app|unless ActiveSupport::Logger.logger_outputs_to?(Rails.logger, STDERR, STDOUT)
        console = ActiveSupport::Logger.new(STDERR)
        Rails.logger.extend ActiveSupport::Logger.broadcast console
      end
      ActiveRecord::Base.verbose_query_logs = false
    end

ด้วยเหตุผลบางประการที่ไม่ทราบสาเหตุวิธีนี้จึงไม่มีเอกสารแต่คุณสามารถอ้างถึงซอร์สโค้ดหรือบล็อกโพสต์เพื่อเรียนรู้วิธีการทำงานหรือดูตัวอย่าง

https://www.joshmcarthur.com/til/2018/08/16/logging-to-multiple-destinations-using-activesupport-4.htmlมีอีกตัวอย่าง:

require "active_support/logger"
console_logger = ActiveSupport::Logger.new(STDOUT)
file_logger = ActiveSupport::Logger.new("my_log.log")
combined_logger = console_logger.extend(ActiveSupport::Logger.broadcast(file_logger))

combined_logger.debug "Debug level"

0

เมื่อเร็ว ๆ นี้ฉันมีความต้องการเช่นกันดังนั้นฉันจึงติดตั้งไลบรารีที่ทำสิ่งนี้ ฉันเพิ่งค้นพบคำถามนี้ StackOverflow ดังนั้นฉันวางมันออกมีสำหรับทุกคนที่ต้องการมัน: https://github.com/agis/multi_io

เมื่อเทียบกับโซลูชันอื่น ๆ ที่กล่าวถึงที่นี่สิ่งนี้พยายามที่จะเป็นIOวัตถุของตัวเองดังนั้นจึงสามารถใช้แทนการดรอปอินสำหรับอ็อบเจ็กต์ IO ทั่วไปอื่น ๆ (ไฟล์ซ็อกเก็ต ฯลฯ )

ที่กล่าวว่าฉันยังไม่ได้ใช้เมธอด IO มาตรฐานทั้งหมด แต่เป็นไปตามความหมายของ IO (เช่น#writeส่งกลับผลรวมของจำนวนไบต์ที่เขียนไปยังเป้าหมาย IO ทั้งหมด)


-3

ฉันคิดว่า STDOUT ของคุณใช้สำหรับข้อมูลรันไทม์ที่สำคัญและเกิดข้อผิดพลาด

ดังนั้นฉันจึงใช้

  $log = Logger.new('process.log', 'daily')

เพื่อบันทึกการดีบักและการบันทึกปกติจากนั้นจึงเขียนบางส่วน

  puts "doing stuff..."

ที่ฉันต้องการดูข้อมูล STDOUT ที่สคริปต์ของฉันกำลังทำงานอยู่!

บาแค่ 10 เซ็นต์ของฉัน :-)

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.