Щось схоже на функціональність трійника в реєстраторі.
Щось схоже на функціональність трійника в реєстраторі.
tee --append test.log
для запобігання перезапису.
Відповіді:
Ви можете написати псевдо- 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
реагує на них і проксіє їх до реальних об'єктів вводу-виводу, це повинно працювати.
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)
знецінена.
@ Рішення Девіда дуже хороше. Я створив загальний клас делегатора для декількох цілей на основі його коду.
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
Я фактично використовую цю техніку для друку у файл журналу, службу хмарного реєстратора (журнали), а якщо це середовище розробника - також друкую в 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
кілька разів, це буде продовжувати додавати методи до класу, що небажано. (Див. Нижче, наприклад)
Ось така сама реалізація, але використання анонімних класів, щоб методи не забруднювали клас делегатора.
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 . Це добре працює з Ruby Logger . Якщо ви використовуєте чистий IO, він перестає працювати, оскільки йому бракує деяких методів, які, як очікується, мають об'єкти IO. Труби були згадані раніше тут: Як я можу отримати вихід журналу журналу ruby в 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')
Примітка. Я знаю, що це не відповідає безпосередньо на запитання, але воно сильно пов’язане. Щоразу, коли я шукав вихідні дані для декількох операцій вводу-виводу, я стикався з цією темою, тому, сподіваюся, це теж вам стане в нагоді.
Це спрощення рішення @ 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
Він має ті самі переваги, що і його, без необхідності обгортки зовнішнього класу. Його корисна утиліта мати в окремому файлі ruby.
Використовуйте його як однолінійний для створення екземплярів делегатора таким чином:
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")
Якщо у вас все гаразд із використанням 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
власним об'єктом, тому його можна використовувати як замінник для інших звичайних об'єктів вводу-виводу (файлів, сокетів тощо)
Тим не менш, я ще не реалізував усі стандартні методи вводу-виводу, але ті, що є, дотримуються семантики вводу-виводу (наприклад, #write
повертає суму кількості байтів, записаних до всіх основних цілей вводу-виводу).
Я думаю, що ваш STDOUT використовується для отримання критичної інформації про час роботи та виявлених помилок.
Тому я використовую
$log = Logger.new('process.log', 'daily')
реєструвати налагодження та регулярні журнали, а потім написав кілька
puts "doing stuff..."
де мені потрібно побачити інформацію STDOUT про те, що мої сценарії взагалі працювали!
Бах, лише мої 10 центів :-)
| tee
до того, як файл працював у мене, такLogger.new("| tee test.log")
. Зверніть увагу на трубу. Це було з підказки на coderwall.com/p/y_b3ra/…