Agregar | teeantes del archivo funcionó para mí, entonces Logger.new("| tee test.log"). Tenga en cuenta la tubería. Esto fue de un consejo en coderwall.com/p/y_b3ra/…
Mike W
@mjwatts Se utiliza tee --append test.logpara evitar sobrescrituras.
fangxing
Respuestas:
124
Puede escribir una pseudoclase IOque escribirá en varios IOobjetos. Algo como:
classMultiIOdef initialize(*targets)@targets= targets
enddef write(*args)@targets.each {|t| t.write(*args)}enddef close
@targets.each(&:close)endend
Cada vez que Loggerllame putsa su MultiIOobjeto, escribirá en ambos STDOUTy en su archivo de registro.
Editar: Seguí adelante y descubrí el resto de la interfaz. Un dispositivo de registro debe responder writey close(no puts). Siempre que MultiIOresponda a ellos y los transmita a los objetos IO reales, esto debería funcionar.
si observa el ctor del registrador, verá que esto estropeará la rotación del registro. 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
Nota en Ruby 2.2, @targets.each(&:close)está depreciado.
xis
Funcionó para mí hasta que me di cuenta de que necesitaba llamar periódicamente: close on log_file para obtener log_file para actualizar lo que el registrador había registrado (esencialmente un "guardar"). A STDOUT no le gustó: estar cerca de ser llamado, como derrotar la idea de MultoIO. Se agregó un truco para omitir: cerrar excepto para la clase Archivo, pero desearía tener una solución más elegante.
Kim Miller
48
La solución de @ David es muy buena. Hice una clase de delegador genérico para múltiples objetivos en función de su código.
require 'logger'classMultiDelegatordef initialize(*targets)@targets= targets
enddefself.delegate(*methods)
methods.each do|m|
define_method(m)do|*args|@targets.map {|t| t.send(m,*args)}endendselfendclass<<selfalias to new
endend
log_file =File.open("debug.log","a")
log =Logger.new MultiDelegator.delegate(:write,:close).to(STDOUT, log_file)
¿Podría explicar cómo es esto mejor o cuáles son las utilidades mejoradas de este enfoque que el simple sugerido por David?
Manish Sapariya
5
Es separación de preocupaciones. MultiDelegator solo sabe cómo delegar llamadas a múltiples objetivos. El hecho de que un dispositivo de registro necesite un método de escritura y cierre se implementa en la persona que llama. Esto hace que MultiDelegator se pueda utilizar en otras situaciones además del registro.
jonas054
Buena solucion. Traté de usar esto para enviar el resultado de mis tareas de rake a un archivo de registro. Sin embargo, para que funcione con put (para poder llamar a $ stdout.puts sin obtener el "método privado` put 'llamado "), tuve que agregar algunos métodos más: log_file = File.open (" tmp / rake.log "," a ") $ stdout = MultiDelegator.delegate (: write,: close,: puts,: print) .to (STDOUT, log_file) Sería bueno si fuera posible crear una clase Tee que heredara de MultiDelegator, como puedes hacer con la clase Delegator en stdlib ...
Tyler Rick
Se me ocurrió una implementación similar a Delegator de esto que llamé DelegatorToAll. De esta manera, no tiene que enumerar todos los métodos que desea delegar, ya que delegará todos los métodos que están definidos en la clase delegada (IO): class Tee <DelegateToAllClass (IO) end $ stdout = Tee.new (STDOUT , File.open ("# { FILE } .log", "a")) Consulte gist.github.com/TylerRick/4990898 para obtener más detalles.
Tyler Rick
1
Realmente me gusta su solución, pero no es buena como delegador genérico que se puede usar varias veces ya que cada delegación contamina todas las instancias con nuevos métodos. Publiqué una respuesta a continuación ( stackoverflow.com/a/36659911/123376 ) que soluciona este problema. Publiqué una respuesta en lugar de una edición, ya que puede ser educativo ver la diferencia entre las dos implementaciones, ya que también publiqué ejemplos.
¿Es esto aplicable fuera de los rieles o solo rieles?
Ed Sykes
Está basado en ActiveSupport, por lo que si ya tiene esa dependencia, puede usar extendcualquier ActiveSupport::Loggerinstancia como se muestra arriba.
phillbaker
Gracias, fue de gran ayuda.
Lucas
Creo que esta es la respuesta más simple y efectiva, aunque tuve algunas rarezas al usar la config.logger.extend()configuración interna de mi entorno. En su lugar, me puse config.loggera STDOUTen mi ambiente, luego se extendió el registrador en diferentes inicializadores.
mattsch
14
Para los que les gusta lo simple:
log =Logger.new("| tee test.log")# note the pipe ( '|' )
log.info "hi"# will log to both STDOUT and test.log
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
De hecho, estoy usando esta técnica para imprimir en un archivo de registro, un servicio de registro en la nube (logentries) y, si es un entorno de desarrollo, también imprimir en STDOUT.
"| tee test.log"se sobrescribe las salidas de edad, puede ser "| tee -a test.log"en su lugar
fangxing
13
Si bien me gustan bastante las otras sugerencias, descubrí que tenía el mismo problema pero quería tener la capacidad de tener diferentes niveles de registro para STDERR y el archivo.
Terminé con una estrategia de enrutamiento que se multiplexa en el nivel del registrador en lugar de en el nivel de IO, de modo que cada registrador pudiera operar a niveles de registro independientes:
Me gusta más esta solución porque es (1) simple y (2) te anima a reutilizar tus clases de Logger en lugar de asumir que todo va a un archivo. En mi caso, me gustaría iniciar sesión en STDOUT y un appender GELF para Graylog. Tener una MultiLoggerdescripción similar a la de @dsz encaja perfectamente. ¡Gracias por compartir!
Eric Kramer
Sección agregada para manejar pseudovariables (establecedores / captadores)
Eric Kramer
11
También puede agregar la funcionalidad de registro de varios dispositivos directamente en el registrador:
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')
Aquí hay otra implementación, inspirada en @ jonas054 la respuesta de .
Esto usa un patrón similar al Delegator. De esta manera, no tiene que enumerar todos los métodos que desea delegar, ya que delegará todos los métodos que estén definidos en cualquiera de los objetos de destino:
La respuesta de @ jonas054 anterior es excelente, pero contamina la MultiDelegatorclase con cada nuevo delegado. Si utilizaMultiDelegator varias veces, seguirá agregando métodos a la clase, lo cual no es deseable. (Ver abajo por ejemplo)
Aquí está la misma implementación, pero usando clases anónimas para que los métodos no contaminen la clase delegadora.
classBetterMultiDelegatordefself.delegate(*methods)Class.new dodef initialize(*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
A continuación se muestra un ejemplo del método de contaminación con la implementación original, en contraste con la implementación modificada:
tee =MultiDelegator.delegate(:write).to(STDOUT)
tee.respond_to?:write
# => true
tee.respond_to?:size
# => false
Todo está bien arriba. teetiene un writemétodo, pero ningún sizemétodo como se esperaba. Ahora, considere cuando creamos otro delegado:
tee2 =MultiDelegator.delegate(:size).to("bar")
tee2.respond_to?:size
# => true
tee2.respond_to?:write
# => true !!!!! Bad
tee.respond_to?:size
# => true !!!!! Bad
Oh no, tee2responde sizecomo se esperaba, pero también responde writepor el primer delegado. Incluso teeahora responde asize causa del método de contaminación.
Compare esto con la solución de clase anónima, todo es como se esperaba:
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
Una ventaja: también puede definir diferentes niveles de registro para stdout y file.
Fui a la misma idea de "Delegar todos los métodos a subelementos" que otras personas ya exploraron, pero estoy devolviendo para cada uno de ellos el valor de retorno de la última llamada del método. Si no lo hacía, se rompía, lo logger-colorsque esperaba un Integery el mapa devolvía un 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
endendenddef initialize(*targets)@targets= targets
MultiIO.delegate_all
endend
Esto volverá a delegar cada método a todos los objetivos y devolverá solo el valor de retorno de la última llamada.
Además, si desea colores, STDOUT o STDERR deben colocarse al final, ya que son los únicos dos donde se supone que se emiten los colores. Pero luego, también generará colores en su archivo.
logger =Logger.new MultiIO.new(File.open("log/test.log",'w'), STDOUT)
logger.error "Roses are red"
logger.unknown "Violets are blue"
Una forma más. Si está utilizando el registro etiquetado y también necesita etiquetas en otro archivo de registro, puede hacerlo de esta manera
# 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# broadcastdef initialize(*args)super@formatter=SimpleFormatter.new
end# Simple formatter which only displays the message.classSimpleFormatter<::Logger::Formatter# This method is invoked when a log event occursdef 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"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))
Después de esto, obtendrá etiquetas uuid en el registrador alternativo
["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
Simple, confiable y funciona de manera brillante. ¡Gracias! Tenga en cuenta que ActiveSupport::Loggerfunciona de inmediato con esto, solo necesita usar Rails.logger.extendcon ActiveSupport::Logger.broadcast(...).
def watch(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')
Tenga en cuenta que sé que esto no responde a la pregunta directamente, pero está fuertemente relacionado. Siempre que buscaba salida a varios IO, me encontraba con este hilo, así que espero que también lo encuentre útil.
Esta es una simplificación de la solución de @ rado.
def delegator(*methods)Class.new dodef initialize(*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
Tiene todos los mismos beneficios que el suyo sin la necesidad de la envoltura de clase externa. Es una utilidad útil para tener en un archivo ruby separado.
Úselo como una sola línea para generar instancias de delegador como este:
Si está de acuerdo con el uso ActiveSupport, le recomiendo encarecidamente que consulte ActiveSupport::Logger.broadcast, que es una forma excelente y muy concisa de agregar destinos de registro adicionales a un registrador.
De hecho, si está usando Rails 4+ (a partir de este compromiso ), no necesita hacer nada para obtener el comportamiento deseado, al menos si está usando rails console. Siempre que use rails console, Rails se extiende automáticamente deRails.logger manera que genera tanto en su destino de archivo habitual ( log/production.logpor ejemplo) como STDERR:
Por alguna razón desconocida y desafortunada, este método no está documentado, pero puede consultar el código fuente o las publicaciones del blog para aprender cómo funciona o ver ejemplos.
También tengo esta necesidad recientemente, así que implementé una biblioteca que hace esto. Acabo de descubrir esta pregunta de StackOverflow, así que la estoy publicando para cualquiera que la necesite: https://github.com/agis/multi_io .
En comparación con las otras soluciones mencionadas aquí, esto se esfuerza por ser un IOobjeto propio, por lo que puede usarse como un reemplazo directo de otros objetos IO regulares (archivos, sockets, etc.)
Dicho esto, todavía no he implementado todos los métodos estándar de E / S, pero los que sí lo son, siguen la semántica de E / S (por ejemplo, #write devuelve la suma del número de bytes escritos en todos los objetivos de E / S subyacentes).
| tee
antes del archivo funcionó para mí, entoncesLogger.new("| tee test.log")
. Tenga en cuenta la tubería. Esto fue de un consejo en coderwall.com/p/y_b3ra/…tee --append test.log
para evitar sobrescrituras.Respuestas:
Puede escribir una pseudoclase
IO
que escribirá en variosIO
objetos. Algo como:Luego configúrelo como su archivo de registro:
Cada vez que
Logger
llameputs
a suMultiIO
objeto, escribirá en ambosSTDOUT
y en su archivo de registro.Editar: Seguí adelante y descubrí el resto de la interfaz. Un dispositivo de registro debe responder
write
yclose
(noputs
). Siempre queMultiIO
responda a ellos y los transmita a los objetos IO reales, esto debería funcionar.fuente
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)
está depreciado.La solución de @ David es muy buena. Hice una clase de delegador genérico para múltiples objetivos en función de su código.
fuente
Si está en Rails 3 o 4, como señala esta publicación de blog , Rails 4 tiene esta funcionalidad incorporada . Entonces puedes hacer:
O si está en Rails 3, puede realizar una copia de respaldo:
fuente
extend
cualquierActiveSupport::Logger
instancia como se muestra arriba.config.logger.extend()
configuración interna de mi entorno. En su lugar, me puseconfig.logger
aSTDOUT
en mi ambiente, luego se extendió el registrador en diferentes inicializadores.Para los que les gusta lo simple:
fuente
O imprima el mensaje en el formateador de Logger:
De hecho, estoy usando esta técnica para imprimir en un archivo de registro, un servicio de registro en la nube (logentries) y, si es un entorno de desarrollo, también imprimir en STDOUT.
fuente
"| tee test.log"
se sobrescribe las salidas de edad, puede ser"| tee -a test.log"
en su lugarSi bien me gustan bastante las otras sugerencias, descubrí que tenía el mismo problema pero quería tener la capacidad de tener diferentes niveles de registro para STDERR y el archivo.
Terminé con una estrategia de enrutamiento que se multiplexa en el nivel del registrador en lugar de en el nivel de IO, de modo que cada registrador pudiera operar a niveles de registro independientes:
fuente
MultiLogger
descripción similar a la de @dsz encaja perfectamente. ¡Gracias por compartir!También puede agregar la funcionalidad de registro de varios dispositivos directamente en el registrador:
Por ejemplo:
fuente
Aquí hay otra implementación, inspirada en @ jonas054 la respuesta de .
Esto usa un patrón similar al
Delegator
. De esta manera, no tiene que enumerar todos los métodos que desea delegar, ya que delegará todos los métodos que estén definidos en cualquiera de los objetos de destino:También debería poder usar esto con Logger.
delegate_to_all.rb está disponible desde aquí: https://gist.github.com/TylerRick/4990898
fuente
Rápido y sucio (ref: https://coderwall.com/p/y_b3ra/log-to-stdout-and-a-file-at-the-same-time )
fuente
La respuesta de @ jonas054 anterior es excelente, pero contamina la
MultiDelegator
clase con cada nuevo delegado. Si utilizaMultiDelegator
varias veces, seguirá agregando métodos a la clase, lo cual no es deseable. (Ver abajo por ejemplo)Aquí está la misma implementación, pero usando clases anónimas para que los métodos no contaminen la clase delegadora.
A continuación se muestra un ejemplo del método de contaminación con la implementación original, en contraste con la implementación modificada:
Todo está bien arriba.
tee
tiene unwrite
método, pero ningúnsize
método como se esperaba. Ahora, considere cuando creamos otro delegado:Oh no,
tee2
respondesize
como se esperaba, pero también respondewrite
por el primer delegado. Inclusotee
ahora responde asize
causa del método de contaminación.Compare esto con la solución de clase anónima, todo es como se esperaba:
fuente
¿Está restringido al registrador estándar?
Si no, puede utilizar log4r :
Una ventaja: también puede definir diferentes niveles de registro para stdout y file.
fuente
Fui a la misma idea de "Delegar todos los métodos a subelementos" que otras personas ya exploraron, pero estoy devolviendo para cada uno de ellos el valor de retorno de la última llamada del método. Si no lo hacía, se rompía, lo
logger-colors
que esperaba unInteger
y el mapa devolvía unArray
.Esto volverá a delegar cada método a todos los objetivos y devolverá solo el valor de retorno de la última llamada.
Además, si desea colores, STDOUT o STDERR deben colocarse al final, ya que son los únicos dos donde se supone que se emiten los colores. Pero luego, también generará colores en su archivo.
fuente
He escrito un pequeño RubyGem que te permite hacer varias de estas cosas:
Puedes encontrar el código en github: teerb
fuente
Una forma más. Si está utilizando el registro etiquetado y también necesita etiquetas en otro archivo de registro, puede hacerlo de esta manera
Después de esto, obtendrá etiquetas uuid en el registrador alternativo
Espero que ayude a alguien.
fuente
ActiveSupport::Logger
funciona de inmediato con esto, solo necesita usarRails.logger.extend
conActiveSupport::Logger.broadcast(...)
.Una opción más ;-)
fuente
Me gusta el enfoque MultiIO . Funciona bien con Ruby Logger . Si usa IO puro , deja de funcionar porque carece de algunos métodos que se espera que tengan los objetos IO. Las tuberías se mencionaron antes aquí: ¿Cómo puedo tener la salida del registro de ruby logger en stdout y en el archivo? . Esto es lo que funciona mejor para mí.
Tenga en cuenta que sé que esto no responde a la pregunta directamente, pero está fuertemente relacionado. Siempre que buscaba salida a varios IO, me encontraba con este hilo, así que espero que también lo encuentre útil.
fuente
Esta es una simplificación de la solución de @ rado.
Tiene todos los mismos beneficios que el suyo sin la necesidad de la envoltura de clase externa. Es una utilidad útil para tener en un archivo ruby separado.
Úselo como una sola línea para generar instancias de delegador como este:
O úselo como una fábrica así:
fuente
Puedes usar el
Loog::Tee
objeto de laloog
gema:Exactamente lo que busca.
fuente
Si está de acuerdo con el uso
ActiveSupport
, le recomiendo encarecidamente que consulteActiveSupport::Logger.broadcast
, que es una forma excelente y muy concisa de agregar destinos de registro adicionales a un registrador.De hecho, si está usando Rails 4+ (a partir de este compromiso ), no necesita hacer nada para obtener el comportamiento deseado, al menos si está usando
rails console
. Siempre que userails console
, Rails se extiende automáticamente deRails.logger
manera que genera tanto en su destino de archivo habitual (log/production.log
por ejemplo) comoSTDERR
:Por alguna razón desconocida y desafortunada, este método no está documentado, pero puede consultar el código fuente o las publicaciones del blog para aprender cómo funciona o ver ejemplos.
https://www.joshmcarthur.com/til/2018/08/16/logging-to-multiple-destinations-using-activesupport-4.html tiene otro ejemplo:
fuente
También tengo esta necesidad recientemente, así que implementé una biblioteca que hace esto. Acabo de descubrir esta pregunta de StackOverflow, así que la estoy publicando para cualquiera que la necesite: https://github.com/agis/multi_io .
En comparación con las otras soluciones mencionadas aquí, esto se esfuerza por ser un
IO
objeto propio, por lo que puede usarse como un reemplazo directo de otros objetos IO regulares (archivos, sockets, etc.)Dicho esto, todavía no he implementado todos los métodos estándar de E / S, pero los que sí lo son, siguen la semántica de E / S (por ejemplo,
#write
devuelve la suma del número de bytes escritos en todos los objetivos de E / S subyacentes).fuente
Creo que su STDOUT se usa para información crítica de tiempo de ejecución y errores planteados.
Entonces uso
para registrar la depuración y el registro regular, y luego escribí algunos
donde necesito ver la información STDOUT de que mis scripts se estaban ejecutando.
Bah, solo mis 10 centavos :-)
fuente