คล้ายกับฟังก์ชันทีออฟในคนตัดไม้
คำตอบ:
คุณสามารถเขียน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 จริงสิ่งนี้ควรใช้งานได้
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
@targets.each(&:close)
มีค่าเสื่อมราคา
@ วิธีแก้ของเดวิดดีมาก ฉันได้สร้างคลาส 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)
หากคุณอยู่ใน 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))
extend
ใดก็ได้ActiveSupport::Logger
ตามที่แสดงด้านบน
config.logger.extend()
กำหนดค่าสภาพแวดล้อมภายในของฉัน แต่ฉันตั้งค่าconfig.logger
เป็นSTDOUT
ในสภาพแวดล้อมของฉันจากนั้นขยายตัวบันทึกในตัวเริ่มต้นอื่น
สำหรับผู้ที่ชอบความเรียบง่าย:
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 ด้วย
"| tee test.log"
จะเขียนทับเอาต์พุตเก่าอาจเป็น"| tee -a test.log"
แทน
แม้ว่าฉันจะชอบคำแนะนำอื่น ๆ แต่ฉันพบว่าฉันมีปัญหาเดียวกันนี้ แต่ต้องการความสามารถในการมีระดับการบันทึกที่แตกต่างกันสำหรับ 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)
MultiLogger
คำอธิบายที่เหมือน @dsz นั้นเหมาะสมอย่างยิ่ง ขอบคุณสำหรับการแบ่งปัน!
คุณยังสามารถเพิ่มฟังก์ชันการบันทึกหลายอุปกรณ์ลงใน 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')
นี่คือการนำไปใช้งานอื่นที่ได้รับแรงบันดาลใจจากคำตอบของ@ 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
รวดเร็วและสกปรก (อ้างอิง: https://coderwall.com/p/y_b3ra/log-to-stdout-and-a-file-at-the-same-time )
require 'logger'
ll=Logger.new('| tee script.log')
ll.info('test')
คำตอบของ @ 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
คุณ จำกัด เฉพาะคนตัดไม้มาตรฐานหรือไม่?
หากไม่มีคุณสามารถใช้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 และไฟล์
ฉันใช้แนวคิดเดียวกันกับ "การมอบหมายวิธีการทั้งหมดให้กับองค์ประกอบย่อย" ที่คนอื่น ๆ ได้สำรวจไปแล้ว แต่กำลังส่งคืนค่าตอบแทนของการเรียกสุดท้ายของเมธอดให้แต่ละคน ถ้าฉันไม่ทำมันพัง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"
ฉันได้เขียน 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
อีกวิธีหนึ่ง หากคุณใช้การบันทึกแบบแท็กและต้องการแท็กในไฟล์บันทึกอื่นด้วยคุณสามารถทำได้ด้วยวิธีนี้
# 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(...)
อีกหนึ่งทางเลือก ;-)
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 ...')
ฉันชอบแนวทาง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 หลายตัวฉันก็เจอหัวข้อนี้ดังนั้นฉันหวังว่าคุณจะพบว่ามีประโยชน์เช่นกัน
นี่คือการทำให้โซลูชันของ @ 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")
คุณสามารถใช้Loog::Tee
วัตถุจากloog
อัญมณี:
require 'loog'
logger = Loog::Tee.new(first, second)
สิ่งที่คุณกำลังมองหา
หากคุณพอใจกับการใช้ActiveSupport
งานฉันขอแนะนำอย่างยิ่งให้ตรวจสอบActiveSupport::Logger.broadcast
ซึ่งเป็นวิธีที่ยอดเยี่ยมและรัดกุมมากในการเพิ่มปลายทางบันทึกเพิ่มเติมให้กับคนตัดไม้
ในความเป็นจริงหากคุณใช้ Rails 4+ (ณ การกระทำนี้ ) คุณไม่จำเป็นต้องทำอะไรเพื่อให้ได้พฤติกรรมที่ต้องการ - อย่างน้อยถ้าคุณใช้ไฟล์rails console
. เมื่อใดก็ตามที่คุณใช้rails console
Rails จะขยายโดยอัตโนมัติ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"
…
เมื่อเร็ว ๆ นี้ฉันมีความต้องการเช่นกันดังนั้นฉันจึงติดตั้งไลบรารีที่ทำสิ่งนี้ ฉันเพิ่งค้นพบคำถามนี้ StackOverflow ดังนั้นฉันวางมันออกมีสำหรับทุกคนที่ต้องการมัน: https://github.com/agis/multi_io
เมื่อเทียบกับโซลูชันอื่น ๆ ที่กล่าวถึงที่นี่สิ่งนี้พยายามที่จะเป็นIO
วัตถุของตัวเองดังนั้นจึงสามารถใช้แทนการดรอปอินสำหรับอ็อบเจ็กต์ IO ทั่วไปอื่น ๆ (ไฟล์ซ็อกเก็ต ฯลฯ )
ที่กล่าวว่าฉันยังไม่ได้ใช้เมธอด IO มาตรฐานทั้งหมด แต่เป็นไปตามความหมายของ IO (เช่น#write
ส่งกลับผลรวมของจำนวนไบต์ที่เขียนไปยังเป้าหมาย IO ทั้งหมด)
ฉันคิดว่า STDOUT ของคุณใช้สำหรับข้อมูลรันไทม์ที่สำคัญและเกิดข้อผิดพลาด
ดังนั้นฉันจึงใช้
$log = Logger.new('process.log', 'daily')
เพื่อบันทึกการดีบักและการบันทึกปกติจากนั้นจึงเขียนบางส่วน
puts "doing stuff..."
ที่ฉันต้องการดูข้อมูล STDOUT ที่สคริปต์ของฉันกำลังทำงานอยู่!
บาแค่ 10 เซ็นต์ของฉัน :-)
| tee
สังเกตท่อ นี่คือเคล็ดลับในcoderwall.com/p/y_b3ra/…Logger.new("| tee test.log")