Добавление | teeдо того, как файл сработал для меня, поэтому Logger.new("| tee test.log"). Обратите внимание на трубу. Это было из подсказки на coderwall.com/p/y_b3ra/…
Майк В.
@mjwatts Используется tee --append test.logдля предотвращения перезаписи.
fangxing
Ответы:
124
Вы можете написать псевдокласс, IOкоторый будет писать в несколько IOобъектов. Что-то типа:
Каждый раз, когда Loggerвызывается putsваш MultiIOобъект, он будет писать как в STDOUTваш файл журнала, так и в него.
Изменить: я пошел дальше и разобрался с остальной частью интерфейса. Устройство журнала должно отвечать на writeи close(не puts). Пока он MultiIOотвечает на них и передает их реальным объектам ввода-вывода, это должно работать.
Если вы посмотрите на 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
Работал для меня, пока я не понял, что мне нужно периодически вызывать: close в log_file, чтобы получить log_file для обновления того, что журнал записал (по сути, «сохранение»). STDOUT не понравился: он был близок к тому, чтобы его использовали, что-то вроде поражения идеи MultoIO. Добавлен способ пропустить: закрыть, за исключением класса File, но хотелось бы иметь более элегантное решение.
Ким Миллер
48
@ Решение Дэвида очень хорошее. Я создал общий класс делегата для нескольких целей на основе его кода.
Не могли бы вы объяснить, чем это лучше или какие усовершенствованные возможности этого подхода, чем простой, предложенный Давидом
Маниш Сапария
5
Это разделение забот. MultiDelegator знает только о делегировании вызовов нескольким целям. Тот факт, что устройству регистрации требуется метод записи и закрытия, реализован в вызывающей программе. Это делает MultiDelegator пригодным для использования в других ситуациях, кроме ведения журнала.
jonas054
Хорошее решение. Я попытался использовать это, чтобы записать вывод моих задач с граблями в файл журнала. Однако для того, чтобы заставить его работать с путями (чтобы иметь возможность вызывать $ stdout.puts без "вызова частного метода" put '), мне пришлось добавить еще несколько методов: log_file = File.open ("tmp / rake.log "," a ") $ stdout = MultiDelegator.delegate (: write,: close,: put,: print) .to (STDOUT, log_file) Было бы неплохо, если бы можно было создать класс Tee, унаследованный от MultiDelegator, как вы можете сделать с классом Delegator в stdlib ...
Тайлер Рик
Я придумал реализацию этого типа Delegator, которую назвал DelegatorToAll. Таким образом, вам не нужно перечислять все методы, которые вы хотите делегировать, поскольку он делегирует все методы, определенные в классе делегата (IO): class Tee <DelegateToAllClass (IO) end $ stdout = Tee.new (STDOUT , File.open ("# { FILE } .log", "a")) Подробнее см. Gist.github.com/TylerRick/4990898 .
Тайлер Рик
1
Мне очень нравится ваше решение, но оно не подходит как универсальный делегатор, который можно использовать несколько раз, поскольку каждое делегирование загрязняет все экземпляры новыми методами. Я опубликовал ответ ниже ( stackoverflow.com/a/36659911/123376 ), который устраняет эту проблему. Я опубликовал ответ, а не редактирование, так как может быть полезно увидеть разницу между двумя реализациями, поскольку я также опубликовал примеры.
Или, если вы используете Rails 3, вы можете выполнить резервное копирование:
# config/initializers/alternative_output_log.rb# backported from rails4moduleActiveSupportclassLogger < ::Logger# Broadcasts logs to multiple loggers. Returns a module to be# `extended`'ed into other logger instances.defself.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)
endendendendend
file_logger = Logger.new(Rails.root.join("log/alternative-output.log"))
Rails.logger.extend(ActiveSupport::Logger.broadcast(file_logger))
Он основан на ActiveSupport, поэтому, если у вас уже есть эта зависимость, вы можете использовать extendлюбой ActiveSupport::Loggerэкземпляр, как показано выше.
phillbaker
Спасибо, это было полезно.
Лукас
Я думаю, что это самый простой и эффективный ответ, хотя у меня были некоторые странности при использовании config.logger.extend()внутренней конфигурации моей среды. Вместо этого, я поставил , config.loggerчтобы STDOUTв моем окружении, а затем расширил регистратор в различных инициализаторах.
mattsch 07
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
На самом деле я использую этот метод для печати в файл журнала, службу облачного журнала (logentries) и, если это среда разработки, также печать в STDOUT.
"| tee test.log"перезапишет старые выходные данные, может быть "| tee -a test.log"вместо этого
fangxing
13
Хотя мне очень нравятся другие предложения, я обнаружил, что у меня такая же проблема, но мне нужна возможность иметь разные уровни ведения журнала для STDERR и файла.
В итоге я выбрал стратегию маршрутизации, которая мультиплексируется на уровне регистратора, а не на уровне ввода-вывода, так что каждый регистратор может работать на независимых уровнях журнала:
Мне больше всего нравится это решение, так как оно (1) простое и (2) побуждает вас повторно использовать ваши классы Logger вместо того, чтобы предполагать, что все идет в файл. В моем случае я хотел бы войти в STDOUT и приложение GELF для Graylog. Имея MultiLoggerкак @dsz описывает это отлично подходит. Спасибо, что поделился!
Эрик Крамер
Добавлен раздел для обработки псевдопеременных (сеттеры / геттеры)
Эрик Крамер
11
Вы также можете добавить функцию регистрации нескольких устройств непосредственно в регистратор:
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. Таким образом, вам не нужно перечислять все методы, которые вы хотите делегировать, поскольку он делегирует все методы, определенные в любом из целевых объектов:
Ответ @ jonas054 выше отличный, но он загрязняет MultiDelegatorкласс каждым новым делегатом. Если вы используете MultiDelegatorнесколько раз, он продолжит добавлять методы в класс, что нежелательно. (См., Например, ниже)
Вот та же реализация, но с использованием анонимных классов, поэтому методы не загрязняют класс делегатора.
classBetterMultiDelegatordefself.delegate(*methods)
Class.new dodefinitialize(*targets)
@targets = targets
end
methods.each do|m|
define_method(m) do|*args|
@targets.map { |t| t.send(m, *args) }
endendclass <<selfalias to new
endend# new classend# delegateend
Вот пример загрязнения метода исходной реализацией в отличие от модифицированной реализации:
Я пошел к той же идее «Делегирование всех методов подэлементам», которую уже исследовали другие люди, но я возвращаю для каждого из них возвращаемое значение последнего вызова метода. Если я этого не сделал, он сломался, logger-colorsкоторый ожидал, Integerа карта возвращала Array.
classMultiIOdefself.delegate_all
IO.methods.each do|m|
define_method(m) do|*args|
ret = nil
@targets.each { |t| ret = t.send(m, *args) }
ret
endendenddefinitialize(*targets)
@targets = targets
MultiIO.delegate_all
endend
Это приведет к повторному делегированию каждого метода всем целям и возвратит только возвращаемое значение последнего вызова.
Кроме того, если вам нужны цвета, STDOUT или STDERR должны быть помещены в последнюю очередь, поскольку предполагается, что выводятся только два цвета. Но затем он также выведет цвета в ваш файл.
logger = Logger.new MultiIO.new(File.open("log/test.log", 'w'), STDOUT)
logger.error "Roses are red"
logger.unknown "Violets are blue"
Еще один способ. Если вы используете ведение журнала с тегами и вам нужны теги в другом файле журнала, вы можете сделать это таким образом
# backported from rails4# config/initializers/active_support_logger.rbmoduleActiveSupportclassLogger < ::Logger# Broadcasts logs to multiple loggers. Returns a module to be# `extended`'ed into other logger instances.defself.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)
endend# Module.newend# broadcastdefinitialize(*args)super
@formatter = SimpleFormatter.new
end# Simple formatter which only displays the message.classSimpleFormatter < ::Logger::Formatter# This method is invoked when a log event occursdefcall(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"endendend# class Loggerend# 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-1216: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(...).
Мне нравится подход MultiIO . Он хорошо работает с Ruby Logger . Если вы используете чистый ввод-вывод, он перестает работать, потому что ему не хватает некоторых методов, которые должны быть у объектов ввода-вывода. Каналы были упомянуты ранее здесь: Как я могу вывести журнал журнала Ruby на стандартный вывод, а также в файл? . Вот что мне больше всего подходит.
defwatch(cmd)
output = StringIO.new
IO.popen(cmd) do|fd|until fd.eof?
bit = fd.getc
output << bit
$stdout.putc bit
endend
output.rewind
[output.read, $?.success?]
ensure
output.close
end
result, success = watch('./my/shell_command as a String')
Обратите внимание: я знаю, что это не дает прямого ответа на вопрос, но это тесно связано. Всякий раз, когда я искал вывод для нескольких операций ввода-вывода, я сталкивался с этой веткой, так что надеюсь, вы тоже найдете это полезным.
defdelegator(*methods)
Class.new dodefinitialize(*targets)
@targets = targets
end
methods.each do|m|
define_method(m) do|*args|
@targets.map { |t| t.send(m, *args) }
endendclass << selfaliasfor new
endend# new classend# delegate
Он имеет все те же преимущества, что и его, без необходимости во внешней оболочке класса. Это полезная утилита, хранящаяся в отдельном рубиновом файле.
Используйте его как однострочник для создания экземпляров делегатора, например:
Если вы согласны с использованием ActiveSupport, я настоятельно рекомендую проверить ActiveSupport::Logger.broadcast, что является отличным и очень кратким способом добавить дополнительные места назначения журнала в регистратор.
Фактически, если вы используете Rails 4+ (начиная с этого коммита ), вам не нужно ничего делать для достижения желаемого поведения - по крайней мере, если вы используете rails console. Всякий раз, когда вы используете rails console, Rails автоматически расширяется Rails.logger, так что он выводит как в обычное место назначения файла ( log/production.logнапример), так и STDERR:
По какой-то неизвестной и досадной причине этот метод недокументирован, но вы можете обратиться к исходному коду или сообщениям в блоге, чтобы узнать, как он работает, или посмотреть примеры.
У меня тоже недавно возникла такая потребность, поэтому я реализовал библиотеку, которая делает это. Я только что обнаружил этот вопрос о StackOverflow, поэтому предлагаю его всем, кто в нем нуждается: https://github.com/agis/multi_io .
По сравнению с другими решениями, упомянутыми здесь, это стремление быть IOотдельным объектом, поэтому его можно использовать в качестве замены для других обычных объектов ввода-вывода (файлов, сокетов и т. Д.)
Тем не менее, я еще не реализовал все стандартные методы ввода-вывода, но те, которые есть, следуют семантике ввода-вывода (например, #writeвозвращают сумму количества байтов, записанных для всех основных целей ввода-вывода).
| tee
до того, как файл сработал для меня, поэтомуLogger.new("| tee test.log")
. Обратите внимание на трубу. Это было из подсказки на coderwall.com/p/y_b3ra/…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
На самом деле я использую этот метод для печати в файл журнала, службу облачного журнала (logentries) и, если это среда разработки, также печать в STDOUT.
источник
"| tee test.log"
перезапишет старые выходные данные, может быть"| tee -a test.log"
вместо этогоХотя мне очень нравятся другие предложения, я обнаружил, что у меня такая же проблема, но мне нужна возможность иметь разные уровни ведения журнала для STDERR и файла.
В итоге я выбрал стратегию маршрутизации, которая мультиплексируется на уровне регистратора, а не на уровне ввода-вывода, так что каждый регистратор может работать на независимых уровнях журнала:
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 описывает это отлично подходит. Спасибо, что поделился!Вы также можете добавить функцию регистрации нескольких устройств непосредственно в регистратор:
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
Одно преимущество: вы также можете определить разные уровни журнала для стандартного вывода и файла.
источник
Я пошел к той же идее «Делегирование всех методов подэлементам», которую уже исследовали другие люди, но я возвращаю для каждого из них возвращаемое значение последнего вызова метода. Если я этого не сделал, он сломался,
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 . Если вы используете чистый ввод-вывод, он перестает работать, потому что ему не хватает некоторых методов, которые должны быть у объектов ввода-вывода. Каналы были упомянуты ранее здесь: Как я могу вывести журнал журнала Ruby на стандартный вывод, а также в файл? . Вот что мне больше всего подходит.
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
Он имеет все те же преимущества, что и его, без необходимости во внешней оболочке класса. Это полезная утилита, хранящаяся в отдельном рубиновом файле.
Используйте его как однострочник для создания экземпляров делегатора, например:
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
отдельным объектом, поэтому его можно использовать в качестве замены для других обычных объектов ввода-вывода (файлов, сокетов и т. Д.)Тем не менее, я еще не реализовал все стандартные методы ввода-вывода, но те, которые есть, следуют семантике ввода-вывода (например,
#write
возвращают сумму количества байтов, записанных для всех основных целей ввода-вывода).источник
Я думаю, что ваш STDOUT используется для критической информации о времени выполнения и возникающих ошибок.
Поэтому я использую
$log = Logger.new('process.log', 'daily')
для регистрации отладки и регулярного ведения журнала, а затем написал несколько
puts "doing stuff..."
где мне нужно увидеть информацию STDOUT о том, что мои скрипты вообще выполнялись!
Ба, только мои 10 центов :-)
источник