Сокет, как функция тройника в журнале.
Как я могу получить вывод журнала журнала рубинов на stdout, а также файл?
Ответ 1
Вы можете написать класс pseudo 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
(not puts
). Пока MultiIO
отвечает на эти и прокси-серверы на реальные объекты ввода-вывода, это должно работать.
Ответ 2
@Решение David очень хорошее. Я создал общий класс делегирования для нескольких целей на основе его кода.
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)
Ответ 3
Если вы находитесь в 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))
Ответ 4
Хотя мне и нравятся другие предложения, я обнаружил, что у меня возникла та же проблема, но мне хотелось иметь разные уровни ведения журнала для 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)
Ответ 5
Вы также можете добавить несколько функций регистрации устройств непосредственно в 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')
Ответ 6
Для тех, кому это нравится:
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) и, если он является средой dev, также печатается в STDOUT.
Ответ 7
Здесь другая реализация, вдохновленная ответом @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
Ответ 8
@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
Ответ 9
Вы ограничены стандартным регистратором?
Если вы не можете использовать 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 и файла.
Ответ 10
Быстрая и грязная (ссылка: 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')
Ответ 11
Я пошел к той же идее "Делегирование всех методов к подэлементам", которые другие исследователи уже изучили, но возвращаю для каждого из них возвращаемое значение последнего вызова метода.
Если я этого не сделал, он сломал 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"
Ответ 12
Я написал немного RubyGem, который позволяет вам сделать несколько из этих вещей:
# Pipe calls to an instance of Ruby 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
Ответ 13
Еще один способ. Если вы используете тегированные теги и нужны теги в другом файле журнала, вы можете сделать это таким образом.
# 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
Надеюсь, что это поможет кому-то.
Ответ 14
Еще один вариант: -)
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 ...')
Ответ 15
Мне нравится подход MultiIO. Он хорошо работает с Ruby Logger. Если вы используете чистый IO, он перестает работать, потому что ему не хватает некоторых методов, которые, как ожидается, будут иметь объекты IO. Трубы были упомянуты здесь: Как я могу получить вывод журнала журнала рубинов в 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')
Примечание Я знаю, что это не отвечает на вопрос напрямую, но это сильно связано. Всякий раз, когда я искал вывод для нескольких IO, я сталкивался с этой нитью. Так что, надеюсь, вы тоже найдете это полезным.
Ответ 16
Это упрощение решения @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")
Ответ 17
Вы можете использовать объект Loog::Tee
из loog
драгоценного камня:
require 'loog'
logger = Loog::Tee.new(first, second)
Именно то, что вы ищете.
Ответ 18
Я думаю, что ваш STDOUT используется для критической информации о запуске и ошибок.
Поэтому я использую
$log = Logger.new('process.log', 'daily')
для регистрации отладки и регулярного ведения журнала, а затем написал несколько
puts "doing stuff..."
где мне нужно увидеть информацию STDOUT о том, что мои скрипты запущены вообще!
Ба, только мои 10 центов: -)