Adicionar | teeantes do arquivo funcionou para mim, então Logger.new("| tee test.log"). Observe o tubo. Isso foi de uma dica em coderwall.com/p/y_b3ra/…
Mike W
@mjwatts Use tee --append test.logpara evitar substituições.
fangxing
Respostas:
124
Você pode escrever uma pseudo IOclasse que gravará em vários IOobjetos. Algo como:
Cada vez que Loggerchama putsseu MultiIOobjeto, ele gravará em ambos STDOUTe em seu arquivo de log.
Edit: fui em frente e descobri o resto da interface. Um dispositivo de log deve responder writee close(não puts). Contanto que MultiIOresponda a esses e os atue como proxy para os objetos IO reais, isso deve funcionar.
se você olhar para o ctor do logger, verá que isso bagunçará a rotação do log. 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 em Ruby 2.2, @targets.each(&:close)é depreciado.
xis
Trabalhou para mim até que percebi que precisava chamar periodicamente: close em log_file para obter log_file para atualizar o logger registrado (essencialmente um "salvar"). STDOUT não gostou: quase não foi convocado, meio que derrotando a ideia do MultoIO. Adicionado um hack para pular: fechar, exceto para a classe Arquivo, mas gostaria de ter uma solução mais elegante.
Kim Miller de
48
A solução de @David é muito boa. Eu fiz uma classe delegadora genérica para vários destinos com base em seu código.
Você poderia explicar como isso é melhor ou quais são os utilitários aprimorados dessa abordagem do que a simples sugerida por David
Manish Sapariya
5
É separação de preocupações. MultiDelegator só sabe delegar chamadas a vários destinos. O fato de que um dispositivo de registro precisa de um método de gravação e fechamento é implementado no chamador. Isso torna o MultiDelegator utilizável em outras situações além do registro.
jonas054 de
Ótima solução. Tentei usar isso para colocar a saída de minhas tarefas de rake em um arquivo de log. Porém, para fazê-lo funcionar com puts (para poder chamar $ stdout.puts sem obter "método privado` puts 'chamado "), eu tive que adicionar mais alguns métodos: log_file = File.open (" tmp / rake.log "," a ") $ stdout = MultiDelegator.delegate (: escrever,: fechar,: puts,: imprimir) .to (STDOUT, log_file) Seria bom se fosse possível criar uma classe Tee herdada de MultiDelegator, como você pode fazer com a classe Delegator em stdlib ...
Tyler Rick
Eu vim com uma implementação do tipo Delegator que chamei de DelegatorToAll. Dessa forma, você não precisa listar todos os métodos que deseja delegar, pois ele delegará todos os métodos definidos na classe delegada (IO): class Tee <DelegateToAllClass (IO) end $ stdout = Tee.new (STDOUT , File.open ("# { FILE } .log", "a")) Consulte gist.github.com/TylerRick/4990898 para obter mais detalhes.
Tyler Rick
1
Eu realmente gosto da sua solução, mas não é boa como um delegador genérico que pode ser usado várias vezes, pois cada delegação polui todas as instâncias com novos métodos. Postei uma resposta abaixo ( stackoverflow.com/a/36659911/123376 ) que corrige esse problema. Publiquei uma resposta em vez de uma edição, pois pode ser educativo ver a diferença entre as duas implementações, pois também publiquei exemplos.
isso é aplicável fora dos trilhos ou somente trilhos?
Ed Sykes,
É baseado no ActiveSupport, então se você já tem essa dependência, você pode usar extendqualquer ActiveSupport::Loggerinstância como mostrado acima.
Phillbaker
Obrigado, foi útil.
Lucas
Acho que essa é a resposta mais simples e eficaz, embora tenha tido algumas estranhezas ao usar a config.logger.extend()configuração interna do meu ambiente. Em vez disso, configurei config.loggerpara STDOUTem meu ambiente e, em seguida, estendi o logger em inicializadores diferentes.
mattsch
14
Para quem gosta de coisas simples:
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
msgend
log.info "hi"# will log to both STDOUT and test.log
Na verdade, estou usando essa técnica para imprimir em um arquivo de log, um serviço de log em nuvem (logentries) e se for um ambiente de desenvolvimento - também imprimir em STDOUT.
"| tee test.log"vai substitui as antigas saídas, pode ser "| tee -a test.log"em vez
Fangxing
13
Embora eu goste bastante das outras sugestões, descobri que tinha o mesmo problema, mas queria a capacidade de ter diferentes níveis de registro para STDERR e o arquivo.
Acabei com uma estratégia de roteamento que multiplexa no nível do logger, em vez de no nível IO, para que cada logger pudesse operar em níveis de log independentes:
Eu gosto mais dessa solução porque é (1) simples e (2) incentiva você a reutilizar suas classes Logger em vez de assumir que tudo vai para um arquivo. No meu caso, gostaria de fazer logon em STDOUT e um appender GELF para Graylog. Ter um MultiLoggercomo @dsz descreve é uma ótima opção. Obrigado por compartilhar!
Eric Kramer
Seção adicionada para lidar com pseudovariáveis (setters / getters)
Eric Kramer
11
Você também pode adicionar a funcionalidade de registro de vários dispositivos diretamente no Logger:
require 'logger'classLogger# 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)endclassLogDevice# :nodoc:
attr_reader :devsdef attach(log)@devs||={}@devs[log]= open_logfile(log)enddef detach(log)@devs||={}@devs[log].close@devs.delete(log)end
alias_method :old_write,:writedef write(message)
old_write(message)@devs||={}@devs.each do|log, dev|
dev.write(message)endendendend
Por exemplo:
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')
Aqui está outra implementação, inspirada na resposta de @ jonas054 .
Isso usa um padrão semelhante a Delegator. Dessa forma, você não precisa listar todos os métodos que deseja delegar, uma vez que delegará todos os métodos definidos em qualquer um dos objetos de destino:
A resposta de @ jonas054 acima é ótima, mas polui a MultiDelegatorclasse a cada novo delegado. Se você usar MultiDelegatorvárias vezes, ele continuará adicionando métodos à classe, o que é indesejável. (Veja abaixo por exemplo)
Aqui está a mesma implementação, mas usando classes anônimas para que os métodos não poluam a classe 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
Aqui está um exemplo da poluição do método com a implementação original, em contraste com a implementação modificada:
tee =MultiDelegator.delegate(:write).to(STDOUT)
tee.respond_to?:write
# => true
tee.respond_to?:size
# => false
Tudo está bem acima. teetem um writemétodo, mas nenhum sizemétodo como o esperado. Agora, considere quando criamos outro delegado:
tee2 =MultiDelegator.delegate(:size).to("bar")
tee2.respond_to?:size
# => true
tee2.respond_to?:write
# => true !!!!! Bad
tee.respond_to?:size
# => true !!!!! Bad
Ah não, tee2responde sizeconforme o esperado, mas também responde por writecausa do primeiro delegado. Mesmo teeagora responde por sizecausa da poluição do método.
Compare isso com a solução de classe anônima, tudo está conforme o esperado:
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
Uma vantagem: você também pode definir diferentes níveis de log para stdout e arquivo.
Tive a mesma ideia de "Delegar todos os métodos a subelementos" que outras pessoas já exploraram, mas estou retornando para cada um deles o valor de retorno da última chamada do método. Se eu não fizesse isso, ele quebrou o logger-colorsque estava esperando um Integere o mapa estava retornando um 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
Isso redelegará todos os métodos a todos os destinos e retornará apenas o valor de retorno da última chamada.
Além disso, se você quiser cores, STDOUT ou STDERR deve ser colocado por último, uma vez que são as únicas duas onde as cores devem ser impressas. Mas então, ele também produzirá cores em seu arquivo.
logger =Logger.new MultiIO.new(File.open("log/test.log",'w'), STDOUT)
logger.error "Roses are red"
logger.unknown "Violets are blue"
Mais uma maneira. Se você estiver usando o registro com tags e também precisar de tags em outro arquivo de registro, poderá fazê-lo desta forma
# 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))
Depois disso, você obterá tags uuid no logger 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
Simples, confiável e funciona perfeitamente. Obrigado! Observe que ActiveSupport::Loggerfunciona fora da caixa com isso - você só precisa usar Rails.logger.extendcom ActiveSupport::Logger.broadcast(...).
Gosto da abordagem MultiIO . Funciona bem com Ruby Logger . Se você usar IO puro, ele para de funcionar porque não possui alguns métodos que os objetos IO devem ter. Pipes foram mencionados antes aqui: Como posso ter a saída do log do ruby logger para stdout, bem como arquivo? . Aqui está o que funciona melhor para mim.
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')
Nota: eu sei que isso não responde à pergunta diretamente, mas está fortemente relacionado. Sempre que eu procurava por saída para vários IOs, encontrei este tópico. Portanto, espero que você também ache isso útil.
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
Ele tem os mesmos benefícios que o dele, sem a necessidade do invólucro de classe externa. É um utilitário útil para ter em um arquivo ruby separado.
Use-o como uma linha única para gerar instâncias de delegador como:
Se você estiver bem com o uso ActiveSupport, eu recomendo altamente verificar ActiveSupport::Logger.broadcast, que é uma maneira excelente e muito concisa de adicionar destinos de log adicionais a um logger.
Na verdade, se você está usando Rails 4+ (a partir deste commit ), você não precisa fazer nada para obter o comportamento desejado - pelo menos se estiver usando o rails console. Sempre que você usa o rails console, o Rails se estende automaticamente deRails.logger forma que a saída seja para o destino de arquivo usual ( log/production.logpor exemplo) e STDERR:
Por algum motivo desconhecido e infeliz, esse método não é documentado, mas você pode consultar o código-fonte ou as postagens do blog para saber como funciona ou ver exemplos.
Eu também tenho essa necessidade recentemente, então implementei uma biblioteca que faz isso. Acabei de descobrir esta questão StackOverflow, então estou colocando-a lá para quem precisa: https://github.com/agis/multi_io .
Comparado com as outras soluções mencionadas aqui, este se esforça para ser um IOobjeto próprio, portanto, pode ser usado como um substituto para outros objetos IO regulares (arquivos, soquetes etc.)
Dito isso, ainda não implementei todos os métodos IO padrão, mas aqueles que o são, seguem a semântica IO (por exemplo, #writeretorna a soma do número de bytes gravados em todos os alvos IO subjacentes).
| tee
antes do arquivo funcionou para mim, entãoLogger.new("| tee test.log")
. Observe o tubo. Isso foi de uma dica em coderwall.com/p/y_b3ra/…tee --append test.log
para evitar substituições.Respostas:
Você pode escrever uma pseudo
IO
classe que gravará em váriosIO
objetos. Algo como:Em seguida, defina-o como seu arquivo de registro:
Cada vez que
Logger
chamaputs
seuMultiIO
objeto, ele gravará em ambosSTDOUT
e em seu arquivo de log.Edit: fui em frente e descobri o resto da interface. Um dispositivo de log deve responder
write
eclose
(nãoputs
). Contanto queMultiIO
responda a esses e os atue como proxy para os objetos IO reais, isso deve funcionar.fonte
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)
é depreciado.A solução de @David é muito boa. Eu fiz uma classe delegadora genérica para vários destinos com base em seu código.
fonte
Se você estiver no Rails 3 ou 4, como esta postagem no blog aponta, o Rails 4 tem essa funcionalidade embutida . Então você pode fazer:
Ou se você estiver no Rails 3, você pode fazer o backport:
fonte
extend
qualquerActiveSupport::Logger
instância como mostrado acima.config.logger.extend()
configuração interna do meu ambiente. Em vez disso, configureiconfig.logger
paraSTDOUT
em meu ambiente e, em seguida, estendi o logger em inicializadores diferentes.Para quem gosta de coisas simples:
fonte
Ou imprima a mensagem no formatador Logger:
Na verdade, estou usando essa técnica para imprimir em um arquivo de log, um serviço de log em nuvem (logentries) e se for um ambiente de desenvolvimento - também imprimir em STDOUT.
fonte
"| tee test.log"
vai substitui as antigas saídas, pode ser"| tee -a test.log"
em vezEmbora eu goste bastante das outras sugestões, descobri que tinha o mesmo problema, mas queria a capacidade de ter diferentes níveis de registro para STDERR e o arquivo.
Acabei com uma estratégia de roteamento que multiplexa no nível do logger, em vez de no nível IO, para que cada logger pudesse operar em níveis de log independentes:
fonte
MultiLogger
como @dsz descreve é uma ótima opção. Obrigado por compartilhar!Você também pode adicionar a funcionalidade de registro de vários dispositivos diretamente no Logger:
Por exemplo:
fonte
Aqui está outra implementação, inspirada na resposta de @ jonas054 .
Isso usa um padrão semelhante a
Delegator
. Dessa forma, você não precisa listar todos os métodos que deseja delegar, uma vez que delegará todos os métodos definidos em qualquer um dos objetos de destino:Você deve ser capaz de usar isso com o Logger também.
delegate_to_all.rb está disponível aqui: https://gist.github.com/TylerRick/4990898
fonte
Rápido e sujo (ref: https://coderwall.com/p/y_b3ra/log-to-stdout-and-a-file-at-the-same-time )
fonte
A resposta de @ jonas054 acima é ótima, mas polui a
MultiDelegator
classe a cada novo delegado. Se você usarMultiDelegator
várias vezes, ele continuará adicionando métodos à classe, o que é indesejável. (Veja abaixo por exemplo)Aqui está a mesma implementação, mas usando classes anônimas para que os métodos não poluam a classe delegadora.
Aqui está um exemplo da poluição do método com a implementação original, em contraste com a implementação modificada:
Tudo está bem acima.
tee
tem umwrite
método, mas nenhumsize
método como o esperado. Agora, considere quando criamos outro delegado:Ah não,
tee2
respondesize
conforme o esperado, mas também responde porwrite
causa do primeiro delegado. Mesmotee
agora responde porsize
causa da poluição do método.Compare isso com a solução de classe anônima, tudo está conforme o esperado:
fonte
Você está restrito ao logger padrão?
Caso contrário, você pode usar o log4r :
Uma vantagem: você também pode definir diferentes níveis de log para stdout e arquivo.
fonte
Tive a mesma ideia de "Delegar todos os métodos a subelementos" que outras pessoas já exploraram, mas estou retornando para cada um deles o valor de retorno da última chamada do método. Se eu não fizesse isso, ele quebrou o
logger-colors
que estava esperando umInteger
e o mapa estava retornando umArray
.Isso redelegará todos os métodos a todos os destinos e retornará apenas o valor de retorno da última chamada.
Além disso, se você quiser cores, STDOUT ou STDERR deve ser colocado por último, uma vez que são as únicas duas onde as cores devem ser impressas. Mas então, ele também produzirá cores em seu arquivo.
fonte
Eu escrevi um pequeno RubyGem que permite que você faça várias dessas coisas:
Você pode encontrar o código no github: teerb
fonte
Mais uma maneira. Se você estiver usando o registro com tags e também precisar de tags em outro arquivo de registro, poderá fazê-lo desta forma
Depois disso, você obterá tags uuid no logger alternativo
Espero que ajude alguém.
fonte
ActiveSupport::Logger
funciona fora da caixa com isso - você só precisa usarRails.logger.extend
comActiveSupport::Logger.broadcast(...)
.Mais uma opção ;-)
fonte
Gosto da abordagem MultiIO . Funciona bem com Ruby Logger . Se você usar IO puro, ele para de funcionar porque não possui alguns métodos que os objetos IO devem ter. Pipes foram mencionados antes aqui: Como posso ter a saída do log do ruby logger para stdout, bem como arquivo? . Aqui está o que funciona melhor para mim.
Nota: eu sei que isso não responde à pergunta diretamente, mas está fortemente relacionado. Sempre que eu procurava por saída para vários IOs, encontrei este tópico. Portanto, espero que você também ache isso útil.
fonte
Esta é uma simplificação da solução do @rado.
Ele tem os mesmos benefícios que o dele, sem a necessidade do invólucro de classe externa. É um utilitário útil para ter em um arquivo ruby separado.
Use-o como uma linha única para gerar instâncias de delegador como:
OU use-o como uma fábrica como:
fonte
Você pode usar o
Loog::Tee
objeto daloog
gema:Exatamente o que você está procurando.
fonte
Se você estiver bem com o uso
ActiveSupport
, eu recomendo altamente verificarActiveSupport::Logger.broadcast
, que é uma maneira excelente e muito concisa de adicionar destinos de log adicionais a um logger.Na verdade, se você está usando Rails 4+ (a partir deste commit ), você não precisa fazer nada para obter o comportamento desejado - pelo menos se estiver usando o
rails console
. Sempre que você usa orails console
, o Rails se estende automaticamente deRails.logger
forma que a saída seja para o destino de arquivo usual (log/production.log
por exemplo) eSTDERR
:Por algum motivo desconhecido e infeliz, esse método não é documentado, mas você pode consultar o código-fonte ou as postagens do blog para saber como funciona ou ver exemplos.
https://www.joshmcarthur.com/til/2018/08/16/logging-to-multiple-destinations-using-activesupport-4.html tem outro exemplo:
fonte
Eu também tenho essa necessidade recentemente, então implementei uma biblioteca que faz isso. Acabei de descobrir esta questão StackOverflow, então estou colocando-a lá para quem precisa: https://github.com/agis/multi_io .
Comparado com as outras soluções mencionadas aqui, este se esforça para ser um
IO
objeto próprio, portanto, pode ser usado como um substituto para outros objetos IO regulares (arquivos, soquetes etc.)Dito isso, ainda não implementei todos os métodos IO padrão, mas aqueles que o são, seguem a semântica IO (por exemplo,
#write
retorna a soma do número de bytes gravados em todos os alvos IO subjacentes).fonte
Acho que seu STDOUT é usado para informações críticas de tempo de execução e erros levantados.
Então eu uso
para registrar depuração e registro regular e, em seguida, escreveu alguns
onde eu preciso ver informações STDOUT de que meus scripts estavam em execução!
Bah, só meus 10 centavos :-)
fonte