Một số như một chức năng phát bóng trong trình ghi nhật ký.
tee --append test.log
để ngăn ghi đè.
Một số như một chức năng phát bóng trong trình ghi nhật ký.
tee --append test.log
để ngăn ghi đè.
Câu trả lời:
Bạn có thể viết một IO
lớp giả sẽ ghi cho nhiều IO
đối tượng. 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 đó, đặt nó làm tệp nhật ký của bạn:
log_file = File.open("log/debug.log", "a")
Logger.new MultiIO.new(STDOUT, log_file)
Mỗi khi Logger
gọi đối tượng puts
của bạn MultiIO
, nó sẽ ghi vào cả hai STDOUT
và tệp nhật ký 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ị ghi nhật ký phải phản hồi write
và close
(không puts
). Miễn là MultiIO
phản hồi những thứ đó và proxy chúng với các đối tượng IO thực, điều này sẽ hoạt động.
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)
được khấu hao.
@ David's giải pháp rất tốt. Tôi đã tạo một lớp đại diện chung cho nhiều mục tiêu dựa trên mã của anh ta.
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)
Nếu bạn đang sử dụng Rails 3 hoặc 4, như bài đăng trên blog này đã chỉ ra, Rails 4 được tích hợp sẵn chức năng này . 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 sử dụng 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))
extend
bất kỳ ActiveSupport::Logger
trường hợp nào như được hiển thị ở trên.
config.logger.extend()
cấu hình môi trường bên trong của mình. Thay vào đó, tôi đặt config.logger
để STDOUT
trong môi trường của tôi, sau đó mở rộng các logger trong initializers khác nhau.
Đố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
Hoặc in thông báo trong trình đị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 thực sự đang sử dụng kỹ thuật này để in ra tệp nhật ký, dịch vụ ghi nhật ký đám mây (logentries) và nếu đó là môi trường nhà phát triển - cũng có thể in ra STDOUT.
"| tee test.log"
sẽ ghi đè lên các đầu ra cũ, có thể "| tee -a test.log"
thay
Trong khi tôi khá thích các đề xuất khác, tôi thấy mình gặp vấn đề tương tự nhưng muốn có khả năng có các mức ghi nhật ký khác nhau cho STDERR và tệp.
Tôi đã kết thúc với một chiến lược định tuyến ghép kênh ở cấp trình ghi thay vì ở cấp IO, để sau đó mỗi trình ghi nhật ký có thể hoạt động ở các cấp nhật ký độ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)
MultiLogger
mô tả như @dsz là một sự phù hợp tuyệt vời. Cám ơn vì đã chia sẻ!
Bạn cũng có thể thêm nhiều chức năng ghi nhật ký thiết bị trực tiếp vào Trình ghi nhật ký:
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')
Đây là một cách triển khai khác, lấy cảm hứng từ @ jonas054 câu trả lời .
Điều này sử dụng một mẫu tương tự như Delegator
. Bằng cách này, bạn không phải liệt kê tất cả các phương thức bạn muốn ủy quyền, vì nó sẽ ủy quyền tất cả các phương thức được xác định trong bất kỳ đối tượng đích nào:
class Tee < DelegateToAllClass(IO)
end
$stdout = Tee.new(STDOUT, File.open("#{__FILE__}.log", "a"))
Bạn cũng có thể sử dụng điều này với Logger.
Delegate_to_all.rb hiện có tại đây: https://gist.github.com/TylerRick/4990898
Nhanh và bẩn (tham khảo: 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')
Câu trả lời của @ jonas054 ở trên rất tuyệt, nhưng nó gây ô nhiễm cho MultiDelegator
lớp với mọi đại biểu mới. Nếu bạn dùngMultiDelegator
nhiều lần, nó sẽ tiếp tục thêm các phương thức vào lớp, điều này là không mong muốn. (Xem ví dụ dưới đây)
Đây là cách triển khai tương tự, nhưng sử dụng các lớp ẩn danh để các phương thức không gây ô nhiễm 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
Đây là một ví dụ về ô nhiễm phương pháp với triển khai ban đầu, trái ngược với triển khai đã sửa đổi:
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ó một write
phương pháp, nhưng không có size
phương pháp nào như mong đợi. Bây giờ, hãy xem xét khi chúng ta tạo một đại biểu khác:
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
phản hồi size
như mong đợi, nhưng nó cũng phản hồi write
vì đại biểu đầu tiên. Ngay cả tee
bây giờ cũng phản hồisize
vì ô nhiễm phương pháp.
Đối chiếu điều này với giải pháp lớp ẩn danh, mọi thứ đều 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
Bạn có bị giới hạn trong trình ghi nhật ký tiêu 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 các cấp độ nhật ký khác nhau cho stdout và tệp.
Tôi đã đi đến cùng một ý tưởng "Ủy quyền 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 trả về cho mỗi phương thức đó giá trị trả về của lần gọi cuối cùng của phương thức. Nếu tôi không làm như vậy, nó đã phá vỡ logger-colors
điều đang mong đợi một Integer
và bản đồ đang 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ẽ chuyển hướng mọi phương thức đến tất cả các mục tiêu và chỉ trả về giá trị trả về của lần gọi cuối cùng.
Ngoài ra, nếu bạn muốn có màu sắc, STDOUT hoặc STDERR phải được đặt cuối cùng, vì đó là hai màu duy nhất được cho là xuất ra. Nhưng sau đó, nó cũng sẽ xuất màu vào 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"
Tôi đã viết một chút RubyGem cho phép bạn thực hiện một số điều sau:
# 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 thấy mã trên github: teerb
Một cách nữa. Nếu bạn đang sử dụng ghi nhật ký được gắn thẻ và cũng cần các thẻ trong một tệp nhật ký khác, bạn có thể thực hiện 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 các thẻ uuid trong trình ghi 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
Hy vọng rằng sẽ giúp một ai đó.
ActiveSupport::Logger
hoạt động ngoài hộp với điều này - bạn chỉ cần sử dụng Rails.logger.extend
với ActiveSupport::Logger.broadcast(...)
.
Một lựa chọn nữa ;-)
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 ...')
Tôi thích cách 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ó sẽ ngừng hoạt động vì nó thiếu một số phương thức mà các đối tượng IO được mong đợi có. Pipes đã được đề cập trước đây ở đây: Làm cách nào để có thể xuất bản ghi nhật ký ruby logger thành stdout cũng như tệp? . Đây là những gì phù hợp nhất với 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')
Lưu ý 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 chặt chẽ. Bất cứ khi nào tôi tìm kiếm đầu ra cho nhiều IO, tôi đều bắt gặp chuỗi này. Vì vậy, tôi hy vọng bạn cũng thấy điều này hữu ích.
Đây là sự đơn giản hóa giải pháp của @ 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
Nó có tất cả các lợi ích giống như của mình mà không cần đến lớp bao bọc bên ngoài. Nó là một tiện ích hữu ích để có trong một tệp ruby riêng biệt.
Sử dụng nó như một lớp lót để tạo các phiên bản ủy quyền như sau:
IO_delegator_instance = delegator(:write, :read).for(STDOUT, STDERR)
IO_delegator_instance.write("blah")
HOẶC sử dụng nó như một nhà máy như vậy:
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")
Nếu bạn thấy ổn với việc sử dụng ActiveSupport
, thì tôi thực sự khuyên bạn nên kiểm traActiveSupport::Logger.broadcast
, đây là một cách tuyệt vời và rất ngắn gọn để thêm các điểm đến nhật ký bổ sung vào trình ghi nhật ký.
Trên thực tế, nếu bạn đang sử dụng Rails 4+ (kể từ bản cam kết này ), bạn không cần phải làm bất cứ điều gì để có được hành vi mong muốn - ít nhất là nếu bạn đang sử dụng rails console
. Bất cứ khi nào bạn sử dụng rails console
, Rails sẽ tự động mở rộng Rails.logger
để nó xuất cả đến đích tệp thông thường ( log/production.log
ví dụ:) và 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
Vì một số lý do không xác định và không may, phương pháp này không có tài liệu nhưng bạn có thể tham khảo mã nguồn hoặc các bài đăng trên blog để tìm hiểu cách hoạt động hoặc xem các ví dụ.
https://www.joshmcarthur.com/til/2018/08/16/logging-to-multiple-destination-using-activesupport-4.html có một ví dụ khác:
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"
…
Tôi cũng có nhu cầu này gần đây vì vậy tôi đã triển khai một thư viện thực hiện điều này. Tôi vừa phát hiện ra câu hỏi StackOverflow này, vì vậy tôi sẽ đưa nó ra cho bất kỳ ai cần nó: https://github.com/agis/multi_io .
So với các giải pháp khác được đề cập ở đây, điều này cố gắng trở thành một IO
đối tượng của riêng nó, vì vậy nó có thể được sử dụng như một sự thay thế thả vào cho các đối tượng IO thông thường khác (tệp, ổ cắm, v.v.)
Điều đó nói rằng, tôi vẫn chưa triển khai tất cả các phương thức IO tiêu chuẩn, nhưng những phương thức đó tuân theo ngữ nghĩa IO (ví dụ: #write
trả về tổng số byte được ghi cho tất cả các mục tiêu IO cơ bản).
Tôi nghĩ rằng STDOUT của bạn được sử dụng cho thông tin thời gian chạy quan trọng và các lỗi phát sinh.
Vì vậy, tôi sử dụng
$log = Logger.new('process.log', 'daily')
để ghi nhật ký gỡ lỗi và ghi nhật ký thông thường, sau đó viết một vài
puts "doing stuff..."
nơi tôi cần xem thông tin STDOUT rằng các tập lệnh của tôi đang chạy!
Bah, chỉ 10 xu của tôi :-)
| tee
trước khi tệp làm việc cho tôi, vì vậyLogger.new("| tee test.log")
. Lưu ý đường ống. Đây là từ một mẹo trên coderwall.com/p/y_b3ra/...