Что-то вроде функциональности тройника в регистраторе.
Что-то вроде функциональности тройника в регистраторе.
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")
Если вы согласны с использованием 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/…