2011-06-20 38 views

Trả lời

106

Bạn có thể viết một lớp giả IO sẽ ghi vào nhiều đối tượng IO. Một cái gì đó như:

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 

Sau đó thiết lập đó dưới dạng file log của bạn:

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

Mỗi lần Logger cuộc gọi puts trên đối tượng MultiIO của bạn, nó sẽ viết thư cho cả STDOUT và log file của bạn.

Chỉnh sửa: Tôi đã tiếp tục và tìm ra phần còn lại của giao diện. Thiết bị đăng nhập phải trả lời writeclose (không phải puts). Miễn là MultiIO phản hồi chúng và ủy quyền chúng tới các đối tượng IO thực, điều này sẽ hoạt động.

+0

nếu bạn xem các ctor của logger bạn sẽ thấy rằng điều này sẽ mess up đăng nhập xoay. 'def initialize (log = nil, opt = {}) @dev = @filename = @shift_age = @shift_size = nil @mutex = LogDeviceMutex.new nếu log.respond_to? (: Write) và log.respond_to? (: đóng) \t @dev = log else \t @dev = open_logfile (nhật ký) \t @dev.sync = true \t @filename = log \t @shift_age = opt [: shift_age] || 7 \t @shift_size = opt [: shift_size] || 1048576 kết thúc kết thúc' – JeffCharter

+1

Lưu ý trong Ruby 2.2, '@ targets.each (&: close)' được khấu hao. – xis

45

@ Giải pháp của David rất tốt. Tôi đã thực hiện một lớp đại biểu chung cho nhiều mục tiêu dựa trên mã của mình.

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) 
+0

Bạn có thể giải thích, làm thế nào điều này là tốt hơn hoặc những gì được tăng cường tiện ích của phương pháp này hơn so với đồng bằng một đề nghị của David –

+2

Đó là tách mối quan tâm. MultiDelegator chỉ biết về việc ủy ​​quyền các cuộc gọi đến nhiều mục tiêu. Thực tế là thiết bị ghi nhật ký cần viết và phương thức đóng được thực hiện trong trình gọi. Điều này làm cho MultiDelegator có thể sử dụng được trong các tình huống khác ngoài ghi nhật ký. – jonas054

+0

Giải pháp đẹp. – Lykos

10

Bạn cũng có thể thêm nhiều chức năng thiết bị khai thác gỗ trực tiếp vào 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 

Ví dụ:

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

Dưới đây là một thực hiện, lấy cảm hứng từ @ jonas054 's câu trả lời .

Điều này sử dụng mẫu tương tự như Delegator. Bằng cách này bạn không cần phải liệt kê tất cả những phương pháp bạn muốn giao phó, vì nó sẽ ủy thác tất cả các phương pháp được quy định tại một trong các đối tượng mục tiêu:

class Tee < DelegateToAllClass(IO) 
end 

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

Bạn sẽ có thể sử dụng điều này với Logger như tốt.

delegate_to_all.rb có sẵn từ đây: https://gist.github.com/TylerRick/4990898

+2

Điều này có vẻ giống như một cách siêu thanh lịch để giải quyết vấn đề. –

8

Trong khi tôi khá giống như những gợi ý khác, tôi thấy tôi có vấn đề này tương tự nhưng muốn khả năng có mức độ khai thác gỗ khác nhau cho thiết bị lỗi chuẩn và tập tin (như tôi có thể với các khung khai thác lớn hơn như NLog). Tôi đã kết thúc với một chiến lược định tuyến mà nhiều thành ở cấp logger chứ không phải ở cấp IO, để mỗi logger có thể sau đó hoạt động ở log-mức độc lập:

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) 
0

Tôi đã đi đến ý tưởng tương tự của "ủy thác tất cả các phương thức cho các phần tử con "mà những người khác đã khám phá, nhưng đang trả về cho mỗi giá trị trả về của cuộc gọi cuối cùng của phương thức. Nếu không, nó đã phá vỡ logger-colors đang mong đợi một Integer và bản đồ đã trả về một 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 

Điều này sẽ sắp xếp lại mọi phương thức cho tất cả các mục tiêu và chỉ trả về giá trị trả lại của cuộc gọi cuối cùng.

Ngoài ra, nếu bạn muốn màu sắc, STDOUT hoặc STDERR phải được đặt cuối cùng, vì đó là hai chỉ có màu sắc được cho là đầu ra. Nhưng sau đó, nó cũng sẽ xuất ra màu sắc cho tệp của bạn.

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

Tôi đã viết một RubyGem nhỏ mà cho phép bạn làm một số các điều:

# 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" 

Bạn có thể tìm mã trên github: teerb

-1

Tôi nghĩ STDOUT bạn được sử dụng cho thông tin thời gian chạy quan trọng và lỗi được nêu ra.

Vì vậy, tôi sử dụng

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

để đăng nhập debug và khai thác gỗ thông thường, và sau đó viết một vài

puts "doing stuff..." 

nơi mà tôi cần để xem thông tin STDOUT rằng kịch bản của tôi đã chạy ở tất cả!

Bah, chỉ cần tôi 10 cent :-)

23

Nếu bạn đang ở trong Rails 3 hoặc 4, như this blog post points out, Rails 4 has this functionality built in. Vì vậy, bạn có thể làm:

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

Hoặc nếu bạn đang on Rails 3, bạn có thể backport nó:

# 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)) 
+0

là điều này có thể áp dụng bên ngoài đường ray hay chỉ đường ray? –

+0

Nó dựa trên ActiveSupport, vì vậy nếu bạn đã có sự phụ thuộc đó, bạn có thể 'mở rộng' bất kỳ trường hợp' ActiveSupport :: Logger' nào như được hiển thị ở trên. – phillbaker

+0

Cảm ơn, nó rất hữu ích. – Lucas

0

Thêm một cách. Nếu bạn đang sử dụng tagged khai thác gỗ và cần thẻ trong logfile khác là tốt, bạn có thể làm điều đó theo cách này

# 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)) 

Sau đó bạn sẽ nhận được thẻ uuid trong logger thay thế

["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 

Hope giúp ai đó.

9

Đối với những người thích nó đơn giản:

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

source

Hoặc in thông điệp trong định dạng 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 

Tôi đang thực sự sử dụng kỹ thuật này để in ra một tệp nhật ký, dịch vụ nhật ký đám mây (logentries) và nếu môi trường dev của nó - cũng in tới STDOUT.

2

Bạn có bị hạn chế đối với bộ ghi chuẩn không?

Nếu không bạn có thể sử dụng 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 

Một ưu điểm: Bạn cũng có thể xác định log-cấp độ khác nhau cho stdout và tập tin.

3

câu trả lời của @ jonas054 ở trên rất tuyệt, nhưng nó gây ô nhiễm cho lớp MultiDelegator với mọi đại biểu mới. Nếu bạn sử dụng MultiDelegator nhiều lần, nó sẽ tiếp tục thêm phương thức vào lớp, điều này là không mong muốn. (Xem dưới đây ví dụ)

Đây là cùng một triển khai, nhưng sử dụng các lớp ẩn danh để các phương thức không gây ô nhiễm cho lớp người ủy nhiệm.

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 

Dưới đây là một ví dụ về tình trạng ô nhiễm phương pháp với việc thực hiện ban đầu, tương phản với việc thực hiện chỉnh sửa:

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

Tất cả là tốt ở trên. tee có phương thức write nhưng không có phương thức size như mong đợi. Bây giờ, hãy xem xét khi chúng ta tạo ra một đại biểu:

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

Ồ không, tee2 đáp ứng size như mong đợi, nhưng nó cũng đáp ứng write vì các đại biểu đầu tiên. Ngay cả tee giờ phản hồi lại size do ô nhiễm phương pháp.

Contrast này vào dung dịch lớp nặc danh, tất cả mọi thứ được như mong đợi:

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 
0

Thêm một lựa chọn ;-)

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

Tôi thích phương thức tiếp cận MultiIO. Nó hoạt động tốt với Ruby Logger. Nếu bạn sử dụng IO thuần túy nó ngừng hoạt động vì thiếu một số phương thức mà đối tượng IO được mong đợi có. Ống đã được đề cập trước đây: How can I have ruby logger log output to stdout as well as file?. Đây là những gì làm việc tốt nhất cho tôi.

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') 

Note Tôi biết điều này không trả lời câu hỏi trực tiếp nhưng nó có liên quan mạnh mẽ. Bất cứ khi nào tôi tìm kiếm đầu ra cho nhiều IO tôi đã đi qua thread.So này, tôi hy vọng bạn tìm thấy điều này hữu ích quá.

Các vấn đề liên quan