Chaque fois que vous Loggerappelez putsvotre MultiIOobjet, il écrira à la fois dans STDOUTvotre fichier journal.
Edit: je suis allé de l'avant et j'ai compris le reste de l'interface. Un périphérique de journal doit répondre à writeet close(pas puts). Tant que MultiIOrépond à ceux-ci et les envoie par proxy aux objets d'E / S réels, cela devrait fonctionner.
si vous regardez le cteur de l'enregistreur, vous verrez que cela gâchera la rotation du journal. 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 le
3
Remarque dans Ruby 2.2, @targets.each(&:close)est amorti.
xis
A travaillé pour moi jusqu'à ce que je réalise que je devais appeler périodiquement: close sur log_file pour obtenir log_file pour mettre à jour ce que l'enregistreur avait enregistré (essentiellement une "sauvegarde"). STDOUT n'aimait pas: fermer être appelé dessus, en quelque sorte vaincre l'idée de MultoIO. Ajout d'un hack à ignorer: fermer sauf pour la classe File, mais j'aurais aimé avoir une solution plus élégante.
Kim Miller
48
La solution de @ David est très bonne. J'ai créé une classe de délégation générique pour plusieurs cibles en fonction de son code.
Pourriez-vous s'il vous plaît expliquer, en quoi c'est mieux ou quels sont les utilitaires améliorés de cette approche que celle suggérée par David
Manish Sapariya
5
C'est la séparation des préoccupations. MultiDelegator ne sait que déléguer des appels à plusieurs cibles. Le fait qu'un périphérique de journalisation nécessite une méthode d'écriture et de fermeture est implémenté dans l'appelant. Cela rend MultiDelegator utilisable dans d'autres situations que la journalisation.
jonas054
Belle solution. J'ai essayé de l'utiliser pour transférer la sortie de mes tâches de rake dans un fichier journal. Afin de le faire fonctionner avec les put (pour pouvoir appeler $ stdout.puts sans obtenir la "méthode privée` met 'appelée "), j'ai dû ajouter quelques méthodes supplémentaires: log_file = File.open (" tmp / rake.log "," a ") $ stdout = MultiDelegator.delegate (: write,: close,: met,: print) .to (STDOUT, log_file) Ce serait bien s'il était possible de créer une classe Tee héritée de MultiDelegator, comme vous pouvez le faire avec la classe Delegator dans stdlib ...
Tyler Rick
Je suis venu avec une implémentation de type Delegator de ce que j'ai appelé DelegatorToAll. De cette façon, vous n'avez pas à lister toutes les méthodes que vous souhaitez déléguer, car il déléguera toutes les méthodes qui sont définies dans la classe déléguée (IO): class Tee <DelegateToAllClass (IO) end $ stdout = Tee.new (STDOUT , File.open ("# { FILE } .log", "a")) Voir gist.github.com/TylerRick/4990898 pour plus de détails.
Tyler Rick
1
J'aime beaucoup votre solution, mais ce n'est pas bon en tant que délégant générique qui peut être utilisé plusieurs fois car chaque délégation pollue toutes les instances avec de nouvelles méthodes. J'ai posté une réponse ci-dessous ( stackoverflow.com/a/36659911/123376 ) qui résout ce problème. J'ai posté une réponse plutôt qu'une modification car il peut être éducatif de voir la différence entre les deux implémentations car j'ai également publié des exemples.
cela s'applique-t-il à l'extérieur des rails ou uniquement des rails?
Ed Sykes
Il est basé sur ActiveSupport, donc si vous avez déjà cette dépendance, vous pouvez extendn'importe quelle ActiveSupport::Loggerinstance comme indiqué ci-dessus.
phillbaker
Merci, c'était utile.
Lucas
Je pense que c'est la réponse la plus simple et la plus efficace, même si j'ai eu une certaine bizarrerie en utilisant la config.logger.extend()configuration de mon environnement. Au lieu de cela, je me mis config.loggerà STDOUTdans mon environnement, puis étendu l'enregistreur dans différentes initializers.
mattsch
14
Pour ceux qui aiment la simplicité:
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
J'utilise en fait cette technique pour imprimer dans un fichier journal, un service d'enregistrement dans le cloud (entrées de journaux) et s'il s'agit d'un environnement de développement, j'imprime également sur STDOUT.
"| tee test.log"remplacera les anciennes sorties, peut-être à la "| tee -a test.log"place
fangxing
13
Bien que j'aime assez les autres suggestions, j'ai trouvé que j'avais le même problème, mais je voulais pouvoir avoir des niveaux de journalisation différents pour STDERR et le fichier.
Je me suis retrouvé avec une stratégie de routage qui se multiplexe au niveau de l'enregistreur plutôt qu'au niveau des E / S, afin que chaque enregistreur puisse alors fonctionner à des niveaux de journalisation indépendants:
J'aime mieux cette solution car elle est (1) simple et (2) vous encourage à réutiliser vos classes Logger au lieu de supposer que tout va dans un fichier. Dans mon cas, j'aimerais me connecter à STDOUT et à un appender GELF pour Graylog. Avoir un MultiLoggercomme @dsz décrit est un excellent choix. Merci d'avoir partagé!
Eric Kramer
Ajout d'une section pour gérer les pseudovariables (setters / getters)
Eric Kramer
11
Vous pouvez également ajouter la fonctionnalité de journalisation de plusieurs appareils directement dans l'enregistreur:
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
Par exemple:
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')
Voici une autre implémentation, inspirée de la réponse de @ jonas054 .
Cela utilise un modèle similaire à Delegator. De cette façon, vous n'avez pas à répertorier toutes les méthodes que vous souhaitez déléguer, car cela déléguera toutes les méthodes définies dans l'un des objets cibles:
La réponse de @ jonas054 ci-dessus est excellente, mais elle pollue la MultiDelegatorclasse à chaque nouveau délégué. Si vous utilisez MultiDelegatorplusieurs fois, il continuera d'ajouter des méthodes à la classe, ce qui n'est pas souhaitable. (Voir ci-dessous par exemple)
Voici la même implémentation, mais en utilisant des classes anonymes pour que les méthodes ne polluent pas la classe de délégation.
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
Voici un exemple de la méthode pollution avec l'implémentation d'origine, par opposition à l'implémentation modifiée:
tee =MultiDelegator.delegate(:write).to(STDOUT)
tee.respond_to?:write
# => true
tee.respond_to?:size
# => false
Tout va bien dessus. teea une writeméthode, mais aucune sizeméthode comme prévu. Maintenant, considérez quand nous créons un autre délégué:
tee2 =MultiDelegator.delegate(:size).to("bar")
tee2.respond_to?:size
# => true
tee2.respond_to?:write
# => true !!!!! Bad
tee.respond_to?:size
# => true !!!!! Bad
Oh non, tee2répond sizecomme prévu, mais il répond aussi à writecause du premier délégué. Même teemaintenant répond à sizecause de la pollution de la méthode.
Comparez cela à la solution de classe anonyme, tout est comme prévu:
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
Un avantage: vous pouvez également définir différents niveaux de journalisation pour stdout et file.
Je suis allé à la même idée de "déléguer toutes les méthodes à des sous-éléments" que d'autres personnes ont déjà exploré, mais je retourne pour chacun d'eux la valeur de retour du dernier appel de la méthode. Si je ne le faisais pas, il se cassait logger-colorset attendait un Integeret la carte retournait 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
Cela redéléguera chaque méthode à toutes les cibles et ne retournera que la valeur de retour du dernier appel.
De plus, si vous voulez des couleurs, STDOUT ou STDERR doivent être mis en dernier, car ce sont les deux seuls où les couleurs sont censées être sorties. Mais alors, il produira également des couleurs dans votre fichier.
logger =Logger.new MultiIO.new(File.open("log/test.log",'w'), STDOUT)
logger.error "Roses are red"
logger.unknown "Violets are blue"
Encore une façon. Si vous utilisez la journalisation balisée et que vous avez également besoin de balises dans un autre fichier journal, vous pouvez le faire de cette manière
# 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))
Après cela, vous obtiendrez des balises uuid dans un enregistreur alternatif
["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, fiable et fonctionne avec brio. Merci! Notez que cela ActiveSupport::Loggerfonctionne hors de la boîte avec cela - il vous suffit de l'utiliser Rails.logger.extendavec 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')
Notez que je sais que cela ne répond pas directement à la question, mais c'est fortement lié. Chaque fois que je cherchais une sortie vers plusieurs E / S, je suis tombé sur ce fil, j'espère donc que vous le trouverez également utile.
Il s'agit d'une simplification de la solution 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
Il a tous les mêmes avantages que le sien sans avoir besoin de l'emballage de classe externe. C'est un utilitaire utile à avoir dans un fichier ruby séparé.
Utilisez-le comme une ligne unique pour générer des instances de délégant comme ceci:
Si vous êtes d'accord avec l'utilisation ActiveSupport, je vous recommande vivement de vérifier ActiveSupport::Logger.broadcast, ce qui est un moyen excellent et très concis d'ajouter des destinations de journal supplémentaires à un enregistreur.
En fait, si vous utilisez Rails 4+ ( à partir de ce commit ), vous n'avez pas besoin de faire quoi que ce soit pour obtenir le comportement souhaité - du moins si vous utilisez le rails console. Chaque fois que vous utilisez le rails console, Rails s'étend automatiquement deRails.logger telle sorte qu'il sort à la fois vers sa destination de fichier habituelle ( log/production.logpar exemple) et STDERR:
Pour une raison inconnue et malheureuse, cette méthode n'est pas documentée, mais vous pouvez vous référer au code source ou aux articles de blog pour savoir comment cela fonctionne ou voir des exemples.
J'ai également ce besoin récemment, j'ai donc implémenté une bibliothèque qui fait cela. Je viens de découvrir cette question StackOverflow, donc je la mets là-bas pour tous ceux qui en ont besoin: https://github.com/agis/multi_io .
Par rapport aux autres solutions mentionnées ici, cela s'efforce d'être un IOobjet à part entière, de sorte qu'il peut être utilisé comme un remplacement instantané pour d'autres objets IO réguliers (fichiers, sockets, etc.)
Cela dit, je n'ai pas encore implémenté toutes les méthodes IO standard, mais celles qui le sont suivent la sémantique IO (par exemple, #writeretourne la somme du nombre d'octets écrits sur toutes les cibles IO sous-jacentes).
| tee
avant le fichier a fonctionné pour moi, doncLogger.new("| tee test.log")
. Notez le tuyau. Cela provenaittee --append test.log
pour empêcher les écrasements.Réponses:
Vous pouvez écrire une pseudo
IO
classe qui écrira sur plusieursIO
objets. Quelque chose comme:Ensuite, définissez cela comme votre fichier journal:
Chaque fois que vous
Logger
appelezputs
votreMultiIO
objet, il écrira à la fois dansSTDOUT
votre fichier journal.Edit: je suis allé de l'avant et j'ai compris le reste de l'interface. Un périphérique de journal doit répondre à
write
etclose
(pasputs
). Tant queMultiIO
répond à ceux-ci et les envoie par proxy aux objets d'E / S réels, cela devrait fonctionner.la source
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 amorti.La solution de @ David est très bonne. J'ai créé une classe de délégation générique pour plusieurs cibles en fonction de son code.
la source
Si vous êtes dans Rails 3 ou 4, comme le souligne cet article de blog , Rails 4 intègre cette fonctionnalité . Vous pouvez donc faire:
Ou si vous êtes sur Rails 3, vous pouvez le rétroporter:
la source
extend
n'importe quelleActiveSupport::Logger
instance comme indiqué ci-dessus.config.logger.extend()
configuration de mon environnement. Au lieu de cela, je me misconfig.logger
àSTDOUT
dans mon environnement, puis étendu l'enregistreur dans différentes initializers.Pour ceux qui aiment la simplicité:
la source
Ou imprimez le message dans le formateur Logger:
J'utilise en fait cette technique pour imprimer dans un fichier journal, un service d'enregistrement dans le cloud (entrées de journaux) et s'il s'agit d'un environnement de développement, j'imprime également sur STDOUT.
la source
"| tee test.log"
remplacera les anciennes sorties, peut-être à la"| tee -a test.log"
placeBien que j'aime assez les autres suggestions, j'ai trouvé que j'avais le même problème, mais je voulais pouvoir avoir des niveaux de journalisation différents pour STDERR et le fichier.
Je me suis retrouvé avec une stratégie de routage qui se multiplexe au niveau de l'enregistreur plutôt qu'au niveau des E / S, afin que chaque enregistreur puisse alors fonctionner à des niveaux de journalisation indépendants:
la source
MultiLogger
comme @dsz décrit est un excellent choix. Merci d'avoir partagé!Vous pouvez également ajouter la fonctionnalité de journalisation de plusieurs appareils directement dans l'enregistreur:
Par exemple:
la source
Voici une autre implémentation, inspirée de la réponse de @ jonas054 .
Cela utilise un modèle similaire à
Delegator
. De cette façon, vous n'avez pas à répertorier toutes les méthodes que vous souhaitez déléguer, car cela déléguera toutes les méthodes définies dans l'un des objets cibles:Vous devriez également pouvoir l'utiliser avec Logger.
Delegate_to_all.rb est disponible ici: https://gist.github.com/TylerRick/4990898
la source
Rapide et sale (ref: https://coderwall.com/p/y_b3ra/log-to-stdout-and-a-file-at-the-same-time )
la source
La réponse de @ jonas054 ci-dessus est excellente, mais elle pollue la
MultiDelegator
classe à chaque nouveau délégué. Si vous utilisezMultiDelegator
plusieurs fois, il continuera d'ajouter des méthodes à la classe, ce qui n'est pas souhaitable. (Voir ci-dessous par exemple)Voici la même implémentation, mais en utilisant des classes anonymes pour que les méthodes ne polluent pas la classe de délégation.
Voici un exemple de la méthode pollution avec l'implémentation d'origine, par opposition à l'implémentation modifiée:
Tout va bien dessus.
tee
a unewrite
méthode, mais aucunesize
méthode comme prévu. Maintenant, considérez quand nous créons un autre délégué:Oh non,
tee2
répondsize
comme prévu, mais il répond aussi àwrite
cause du premier délégué. Mêmetee
maintenant répond àsize
cause de la pollution de la méthode.Comparez cela à la solution de classe anonyme, tout est comme prévu:
la source
Êtes-vous limité à l'enregistreur standard?
Sinon, vous pouvez utiliser log4r :
Un avantage: vous pouvez également définir différents niveaux de journalisation pour stdout et file.
la source
Je suis allé à la même idée de "déléguer toutes les méthodes à des sous-éléments" que d'autres personnes ont déjà exploré, mais je retourne pour chacun d'eux la valeur de retour du dernier appel de la méthode. Si je ne le faisais pas, il se cassait
logger-colors
et attendait unInteger
et la carte retournait unArray
.Cela redéléguera chaque méthode à toutes les cibles et ne retournera que la valeur de retour du dernier appel.
De plus, si vous voulez des couleurs, STDOUT ou STDERR doivent être mis en dernier, car ce sont les deux seuls où les couleurs sont censées être sorties. Mais alors, il produira également des couleurs dans votre fichier.
la source
J'ai écrit un petit RubyGem qui vous permet de faire plusieurs de ces choses:
Vous pouvez trouver le code sur github: teerb
la source
Encore une façon. Si vous utilisez la journalisation balisée et que vous avez également besoin de balises dans un autre fichier journal, vous pouvez le faire de cette manière
Après cela, vous obtiendrez des balises uuid dans un enregistreur alternatif
J'espère que cela aide quelqu'un.
la source
ActiveSupport::Logger
fonctionne hors de la boîte avec cela - il vous suffit de l'utiliserRails.logger.extend
avecActiveSupport::Logger.broadcast(...)
.Encore une option ;-)
la source
J'aime l' approche MultiIO . Cela fonctionne bien avec Ruby Logger . Si vous utilisez pure IO, il cesse de fonctionner car il manque certaines méthodes que les objets IO devraient avoir. Les tuyaux ont déjà été mentionnés ici: Comment puis-je avoir la sortie du journal de ruby logger sur stdout ainsi que sur un fichier? . Voici ce qui fonctionne le mieux pour moi.
Notez que je sais que cela ne répond pas directement à la question, mais c'est fortement lié. Chaque fois que je cherchais une sortie vers plusieurs E / S, je suis tombé sur ce fil, j'espère donc que vous le trouverez également utile.
la source
Il s'agit d'une simplification de la solution de @ rado.
Il a tous les mêmes avantages que le sien sans avoir besoin de l'emballage de classe externe. C'est un utilitaire utile à avoir dans un fichier ruby séparé.
Utilisez-le comme une ligne unique pour générer des instances de délégant comme ceci:
OU utilisez-le comme une usine comme ceci:
la source
Vous pouvez utiliser un
Loog::Tee
objet deloog
gem:Exactement ce que vous recherchez.
la source
Si vous êtes d'accord avec l'utilisation
ActiveSupport
, je vous recommande vivement de vérifierActiveSupport::Logger.broadcast
, ce qui est un moyen excellent et très concis d'ajouter des destinations de journal supplémentaires à un enregistreur.En fait, si vous utilisez Rails 4+ ( à partir de ce commit ), vous n'avez pas besoin de faire quoi que ce soit pour obtenir le comportement souhaité - du moins si vous utilisez le
rails console
. Chaque fois que vous utilisez lerails console
, Rails s'étend automatiquement deRails.logger
telle sorte qu'il sort à la fois vers sa destination de fichier habituelle (log/production.log
par exemple) etSTDERR
:Pour une raison inconnue et malheureuse, cette méthode n'est pas documentée, mais vous pouvez vous référer au code source ou aux articles de blog pour savoir comment cela fonctionne ou voir des exemples.
https://www.joshmcarthur.com/til/2018/08/16/logging-to-multiple-destinations-using-activesupport-4.html a un autre exemple:
la source
J'ai également ce besoin récemment, j'ai donc implémenté une bibliothèque qui fait cela. Je viens de découvrir cette question StackOverflow, donc je la mets là-bas pour tous ceux qui en ont besoin: https://github.com/agis/multi_io .
Par rapport aux autres solutions mentionnées ici, cela s'efforce d'être un
IO
objet à part entière, de sorte qu'il peut être utilisé comme un remplacement instantané pour d'autres objets IO réguliers (fichiers, sockets, etc.)Cela dit, je n'ai pas encore implémenté toutes les méthodes IO standard, mais celles qui le sont suivent la sémantique IO (par exemple,
#write
retourne la somme du nombre d'octets écrits sur toutes les cibles IO sous-jacentes).la source
Je pense que votre STDOUT est utilisé pour les informations d'exécution critiques et les erreurs soulevées.
Alors j'utilise
pour enregistrer le débogage et la journalisation régulière, puis a écrit quelques
où j'ai besoin de voir les informations STDOUT que mes scripts étaient en cours d'exécution!
Bah, juste mes 10 centimes :-)
la source