Etwas wie eine Tee-Funktionalität im Logger.
tee --append test.log
, um Überschreibungen zu verhindern.
Etwas wie eine Tee-Funktionalität im Logger.
tee --append test.log
, um Überschreibungen zu verhindern.
Antworten:
Sie können eine Pseudoklasse IO
schreiben, die in mehrere IO
Objekte schreibt . Etwas wie:
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
Dann legen Sie das als Ihre Protokolldatei fest:
log_file = File.open("log/debug.log", "a")
Logger.new MultiIO.new(STDOUT, log_file)
Jedesmal , wenn Logger
Anrufe puts
auf Ihrem MultiIO
Objekt, wird es sowohl schreiben STDOUT
und Ihre Log - Datei.
Bearbeiten: Ich ging voran und fand den Rest der Oberfläche heraus. Ein Protokollgerät muss auf write
und close
(nicht puts
) antworten . Solange MultiIO
auf diese reagiert und sie auf die realen E / A-Objekte übertragen werden, sollte dies funktionieren.
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)
wird abgeschrieben.
@ Davids Lösung ist sehr gut. Ich habe eine generische Delegatorklasse für mehrere Ziele basierend auf seinem Code erstellt.
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)
Wenn Sie sich in Rails 3 oder 4 befinden, verfügt Rails 4, wie in diesem Blogbeitrag hervorgehoben , über diese integrierte Funktionalität . So können Sie tun:
# config/environment/production.rb
file_logger = Logger.new(Rails.root.join("log/alternative-output.log"))
config.logger.extend(ActiveSupport::Logger.broadcast(file_logger))
Oder wenn Sie auf Rails 3 sind, können Sie es zurückportieren:
# 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
jede ActiveSupport::Logger
Instanz wie oben gezeigt ausführen .
config.logger.extend()
Konfiguration in meiner Umgebung etwas seltsam war . Stattdessen setze ich config.logger
auf STDOUT
in meinem Umfeld, erweitert dann den Logger in verschiedenen initializers.
Für diejenigen, die es einfach mögen:
log = Logger.new("| tee test.log") # note the pipe ( '|' )
log.info "hi" # will log to both STDOUT and test.log
Oder drucken Sie die Nachricht im Logger-Formatierer:
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
Ich verwende diese Technik tatsächlich, um in eine Protokolldatei, einen Cloud-Logger-Dienst (Logentries) und in einer Entwicklungsumgebung zu drucken - auch in STDOUT zu drucken.
"| tee test.log"
wird die alten Ausgaben überschreiben, kann "| tee -a test.log"
stattdessen sein
Obwohl ich die anderen Vorschläge sehr mag, stellte ich fest, dass ich das gleiche Problem hatte, aber die Möglichkeit haben wollte, unterschiedliche Protokollierungsstufen für STDERR und die Datei zu haben.
Am Ende hatte ich eine Routing-Strategie, die auf Logger-Ebene und nicht auf E / A-Ebene multiplext, sodass jeder Logger dann auf unabhängigen Log-Ebenen arbeiten kann:
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
ähnliches wie @dsz zu haben, passt sehr gut. Danke für das Teilen!
Sie können dem Logger auch mehrere Geräteprotokollierungsfunktionen direkt hinzufügen:
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
Zum Beispiel:
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')
Hier ist eine weitere Implementierung, die von der Antwort von @ jonas054 inspiriert wurde .
Dies verwendet ein ähnliches Muster wie Delegator
. Auf diese Weise müssen Sie nicht alle Methoden auflisten, die Sie delegieren möchten, da alle Methoden delegiert werden, die in einem der Zielobjekte definiert sind:
class Tee < DelegateToAllClass(IO)
end
$stdout = Tee.new(STDOUT, File.open("#{__FILE__}.log", "a"))
Sie sollten dies auch mit Logger verwenden können.
delegate_to_all.rb ist hier verfügbar: https://gist.github.com/TylerRick/4990898
Schnell und schmutzig (siehe: 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')
Die Antwort von @ jonas054 oben ist großartig, aber sie verschmutzt die MultiDelegator
Klasse mit jedem neuen Delegierten. Wenn Sie MultiDelegator
mehrmals verwenden, werden der Klasse weiterhin Methoden hinzugefügt, was unerwünscht ist. (Siehe unten zum Beispiel)
Hier ist dieselbe Implementierung, jedoch mit anonymen Klassen, damit die Methoden die Delegatorklasse nicht verschmutzen.
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
Hier ist ein Beispiel für die Methodenverschmutzung mit der ursprünglichen Implementierung im Gegensatz zur modifizierten Implementierung:
tee = MultiDelegator.delegate(:write).to(STDOUT)
tee.respond_to? :write
# => true
tee.respond_to? :size
# => false
Alles ist gut oben. tee
hat eine write
Methode, aber keine size
Methode wie erwartet. Überlegen Sie nun, wann wir einen weiteren Delegaten erstellen:
tee2 = MultiDelegator.delegate(:size).to("bar")
tee2.respond_to? :size
# => true
tee2.respond_to? :write
# => true !!!!! Bad
tee.respond_to? :size
# => true !!!!! Bad
Oh nein, tee2
reagiert size
wie erwartet, aber es reagiert auch write
wegen des ersten Delegierten. Schon tee
jetzt reagiert size
aufgrund der Methode Verschmutzung.
Vergleichen Sie dies mit der anonymen Klassenlösung, alles ist wie erwartet:
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
Sind Sie auf den Standardlogger beschränkt?
Wenn nicht, können Sie log4r verwenden :
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
Ein Vorteil: Sie können auch verschiedene Protokollebenen für stdout und file definieren.
Ich bin zu der gleichen Idee übergegangen, "alle Methoden an Unterelemente zu delegieren", die andere Leute bereits untersucht haben, aber für jeden von ihnen den Rückgabewert des letzten Aufrufs der Methode zurückgeben. Wenn ich es nicht tat, brach es, logger-colors
was eine erwartete Integer
und die Karte eine zurückgab 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
Dadurch wird jede Methode auf alle Ziele neu verknüpft und nur der Rückgabewert des letzten Aufrufs zurückgegeben.
Wenn Sie Farben möchten, müssen STDOUT oder STDERR als letzte gesetzt werden, da dies die einzigen beiden Farben sind, in denen Farben ausgegeben werden sollen. Dann werden aber auch Farben in Ihre Datei ausgegeben.
logger = Logger.new MultiIO.new(File.open("log/test.log", 'w'), STDOUT)
logger.error "Roses are red"
logger.unknown "Violets are blue"
Ich habe ein kleines RubyGem geschrieben, mit dem Sie verschiedene Dinge tun können:
# 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"
Sie finden den Code auf github: teerb
Noch ein Weg. Wenn Sie die Tagged-Protokollierung verwenden und Tags auch in einer anderen Protokolldatei benötigen, können Sie dies auf diese Weise tun
# 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))
Danach erhalten Sie UUID-Tags im alternativen Logger
["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
Hoffe das hilft jemandem.
ActiveSupport::Logger
mit diesem der Box funktioniert - Sie müssen nur verwenden Rails.logger.extend
mit ActiveSupport::Logger.broadcast(...)
.
Noch eine Option ;-)
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 ...')
Ich mag den MultiIO- Ansatz. Es funktioniert gut mit Ruby Logger . Wenn Sie reines E / A verwenden , funktioniert es nicht mehr, da einige Methoden fehlen, über die E / A-Objekte erwartet werden. Pipes wurden hier bereits erwähnt: Wie kann ich eine Ruby-Logger-Protokollausgabe sowohl an stdout als auch an eine Datei senden? . Hier ist, was für mich am besten funktioniert.
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')
Hinweis Ich weiß, dass dies die Frage nicht direkt beantwortet, aber stark verwandt ist. Immer wenn ich nach einer Ausgabe für mehrere E / A gesucht habe, bin ich auf diesen Thread gestoßen. Ich hoffe, Sie finden dies auch nützlich.
Dies ist eine Vereinfachung der @ rado-Lösung.
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
Es hat alle die gleichen Vorteile wie sein, ohne dass der Wrapper der äußeren Klasse benötigt wird. Es ist ein nützliches Dienstprogramm in einer separaten Ruby-Datei.
Verwenden Sie es als Einzeiler, um Delegatorinstanzen wie folgt zu generieren:
IO_delegator_instance = delegator(:write, :read).for(STDOUT, STDERR)
IO_delegator_instance.write("blah")
ODER verwenden Sie es als Fabrik wie folgt:
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")
Wenn Sie mit der Verwendung einverstanden sind ActiveSupport
, würde ich das Auschecken sehr empfehlen. Dies ActiveSupport::Logger.broadcast
ist eine hervorragende und sehr präzise Möglichkeit, einem Protokollierer zusätzliche Protokollziele hinzuzufügen.
In der Tat, wenn Sie Rails verwenden 4+ (Stand dieser begehen ), brauchen Sie nicht zu tun , etwas , das gewünschte Verhalten zu bekommen - zumindest wenn Sie mit dem rails console
. Wenn Sie das verwenden rails console
, wird Rails automatisch so erweitert Rails.logger
, dass es sowohl an das übliche Dateiziel ( log/production.log
z. B.) als auch Folgendes ausgibt 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
Aus unbekannten und unglücklichen Gründen ist diese Methode nicht dokumentiert. Sie können sich jedoch auf den Quellcode oder die Blog-Beiträge beziehen, um zu erfahren, wie sie funktioniert, oder Beispiele anzeigen.
https://www.joshmcarthur.com/til/2018/08/16/logging-to-multiple-destinations-using-activesupport-4.html hat ein weiteres Beispiel:
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"
…
Ich habe dieses Bedürfnis auch kürzlich, also habe ich eine Bibliothek implementiert, die dies tut. Ich habe gerade diese StackOverflow-Frage entdeckt und stelle sie daher allen zur Verfügung, die sie benötigen: https://github.com/agis/multi_io .
Im Vergleich zu den anderen hier genannten Lösungen ist dies ein IO
eigenständiges Objekt, sodass es als Ersatz für andere reguläre E / A-Objekte (Dateien, Sockets usw.) verwendet werden kann.
Das heißt, ich habe noch nicht alle Standard-E / A-Methoden implementiert, aber diejenigen, die der E / A-Semantik folgen (z. B. #write
gibt die Summe der Anzahl der Bytes zurück, die in alle zugrunde liegenden E / A-Ziele geschrieben wurden).
Ich denke, Ihr STDOUT wird für kritische Laufzeitinformationen und Fehler verwendet.
Also benutze ich
$log = Logger.new('process.log', 'daily')
Debug und regelmäßige Protokollierung zu protokollieren, und schrieb dann ein paar
puts "doing stuff..."
wo ich STDOUT-Informationen sehen muss, dass meine Skripte überhaupt ausgeführt wurden!
Bah, nur meine 10 Cent :-)
| tee
bevor die Datei für mich funktioniert hat, alsoLogger.new("| tee test.log")
. Beachten Sie das Rohr. Dies war aus einem Tipp auf coderwall.com/p/y_b3ra/…