48 votes

Ruby - part enregistreur exemple parmi modules/classes

Travail sur un petit script Ruby qui va sortir pour le web et les analyses des différents services. J'ai un module avec plusieurs classes à l'intérieur:

module Crawler
  class Runner
  class Options
  class Engine
end

Je veux partager un enregistreur parmi tous ceux de ces classes. Normalement, je venais de mettre cela dans une constante dans le module et de le référencer comme suit:

Crawler::LOGGER.info("Hello, world")

Le problème est que je ne peux pas créer mon journal instance jusqu'à ce que je sais d'où la sortie est en cours. Vous démarrez le robot via la ligne de commande et à ce stade, vous pouvez dire ce que vous voulez exécuter dans le développement (journal de sortie vers STDOUT) ou de production (journal de sortie passe à un fichier, un crawler.log):

crawler --environment=production

J'ai une classe Options qui analyse les options passées au travers de la ligne de commande. C'est seulement à ce point que je ne sais comment instancier l'enregistreur, le bon emplacement de la sortie.

Donc, ma question est: comment/où j'ai mis mon enregistreur objet, de telle sorte que toutes mes classes ont accès?

J'ai pu passer mon journal instance pour chaque new() appel pour chaque instance de classe que j'ai créer, mais je sais qu'il y a un mieux, Rubyish façon de le faire. J'imagine un peu bizarre variable de classe dans le module partagé avec class << self ou une autre magie. :)

Un peu plus de détails: Runner commence tout en passant les options de ligne de commande à l' Options de la classe et obtient en retour un objet avec un couple de variables d'instance:

module Crawler
  class Runner
    def initialize(argv)
      @options = Options.new(argv)
      # feels like logger initialization should go here
      # @options.log_output => STDOUT or string (log file name)
      # @options.log_level => Logger::DEBUG or Logger::INFO
      @engine = Engine.new()
    end
    def run
      @engine.go
    end
  end
end

runner = Runner.new(ARGV)
runner.run

J'ai besoin du code en Engine à être en mesure d'accéder à l'objet logger (avec un couple de plusieurs classes qui sont initialisées à l'intérieur d' Engine). À l'aide!

Tout cela pourrait être évité si vous pouviez juste de modifier dynamiquement l'emplacement de la sortie d'un déjà-instancié Enregistreur (similaire à la façon dont vous modifiez le niveau de log). J'avais l'instancier à STDOUT et puis l'évolution vers un fichier si je suis dans la production. Je ne vois d'une suggestion, quelque part sur la modification de Rubis $stdout variable globale, qui permettrait de rediriger la sortie dans un endroit autre que STDOUT, mais cela semble assez hacky.

Merci!

99voto

Jacob Points 9117

J'aime avoir un logger méthode disponible dans mes cours, mais je n'aime pas l'aspersion @logger = Logging.logger dans tous mes initialiseurs. Habituellement, je fais ceci:

module Logging
  # This is the magical bit that gets mixed into your classes
  def logger
    Logging.logger
  end

  # Global, memoized, lazy initialized instance of a logger
  def self.logger
    @logger ||= Logger.new(STDOUT)
  end
end

Ensuite, dans vos classes:

class Widget
  # Mix in the ability to log stuff ...
  include Logging

  # ... and proceed to log with impunity:
  def discombobulate(whizbang)
    logger.warn "About to combobulate the whizbang"
    # commence discombobulation
  end
end

Parce que l' Logging#logger méthode peut accéder à l'instance que le module est mixte, il est trivial de prolonger votre module de journalisation pour enregistrer le nom de la classe avec des messages de log:

module Logging
  def logger
    @logger ||= Logging.logger_for(self.class.name)
  end

  # Use a hash class-ivar to cache a unique Logger per class:
  @loggers = {}

  class << self
    def logger_for(classname)
      @loggers[classname] ||= configure_logger_for(classname)
    end

    def configure_logger_for(classname)
      logger = Logger.new(STDOUT)
      logger.progname = classname
      logger
    end
  end
end

Votre Widget enregistre désormais des messages avec son nom de classe, et n'a pas besoin de changer un peu :)

22voto

Chuck Points 138930

Avec la conception que vous avez prévu, il ressemble à la solution la plus simple est de donner au Robot d'un module méthode qui retourne un module d'ivar.

module Crawler
  def self.logger
    @logger
  end
  def self.logger=(logger)
    @logger = logger
  end
end

Ou vous pourriez utiliser "class <<self magique" si tu voulais:

module Crawler
  class <<self
    attr_accessor :logger
  end
end

Il fait exactement la même chose.

13voto

pedz Points 929

Comme Zenagray souligne, enregistrement à partir de méthodes de la classe a été laissé hors de Jacob réponse. Un petit ajout résout que:

require 'logger'

module Logging
  class << self
    def logger
      @logger ||= Logger.new($stdout)
    end

    def logger=(logger)
      @logger = logger
    end
  end

  # Addition
  def self.included(base)
    class << base
      def logger
        Logging.logger
      end
    end
  end

  def logger
    Logging.logger
  end
end

L'utilisation prévue est par l'intermédiaire de "comprendre":

class Dog
  include Logging

  def self.bark
    logger.debug "chirp"
    puts "#{logger.__id__}"
  end

  def bark
    logger.debug "grrr"
    puts "#{logger.__id__}"
  end
end

class Cat
  include Logging

  def self.bark
    logger.debug "chirp"
    puts "#{logger.__id__}"
  end

  def bark
    logger.debug "grrr"
    puts "#{logger.__id__}"
  end
end

Dog.new.bark
Dog.bark
Cat.new.bark
Cat.bark

Produit:

D, [2014-05-06T22:27:33.991454 #2735] DEBUG -- : grrr
70319381806200
D, [2014-05-06T22:27:33.991531 #2735] DEBUG -- : chirp
70319381806200
D, [2014-05-06T22:27:33.991562 #2735] DEBUG -- : grrr
70319381806200
D, [2014-05-06T22:27:33.991588 #2735] DEBUG -- : chirp
70319381806200

Notez l'id de l'enregistreur de données est la même dans tous les quatre cas. Si vous voulez un autre exemple pour chaque classe, puis ne pas utiliser Logging.logger, plutôt l'utilisation d' self.class.logger:

require 'logger'

module Logging
  def self.included(base)
    class << base
      def logger
        @logger ||= Logger.new($stdout)
      end

      def logger=(logger)
        @logger = logger
      end
    end
  end

  def logger
    self.class.logger
  end
end

Le même programme produit maintenant:

D, [2014-05-06T22:36:07.709645 #2822] DEBUG -- : grrr
70350390296120
D, [2014-05-06T22:36:07.709723 #2822] DEBUG -- : chirp
70350390296120
D, [2014-05-06T22:36:07.709763 #2822] DEBUG -- : grrr
70350390295100
D, [2014-05-06T22:36:07.709791 #2822] DEBUG -- : chirp
70350390295100

Notez que les deux premières id sont les mêmes, mais sont différents à partir de la 2e deux id en montrant que nous avons deux instances: un pour chaque classe.

2voto

Charlie Martin Points 62306

Peut être un peu bizarre Rubis magique qui pourrait vous permettre de l'éviter, mais il y a une solution plutôt simple qui n'a pas besoin de bizarre. Juste mettre l'enregistreur de données dans le module et y accéder directement, grâce à un mécanisme pour la définir. Si vous voulez être cool à ce sujet, de définir un "paresseux logger" qui tient un drapeau de dire si c'est un enregistreur encore, et soit silencieusement gouttes messages jusqu'à ce que l'enregistreur est réglé, déclenche une exception de quelque chose est connecté avant de l'enregistreur est réglé, ou ajoute le journal message à une liste de sorte qu'il peut être enregistré une fois que l'enregistreur de données est définie.

2voto

Rob Cameron Points 3456

Un petit morceau de code pour montrer comment cela fonctionne. Je suis tout simplement en créant un nouvel Objet de base de sorte que je peux observer que la object_id reste le même tout au long de l'appelle:

module M

  class << self
    attr_accessor :logger
  end

  @logger = nil

  class C
    def initialize
      puts "C.initialize, before setting M.logger: #{M.logger.object_id}"
      M.logger = Object.new
      puts "C.initialize, after setting M.logger: #{M.logger.object_id}"
      @base = D.new
    end
  end

  class D
    def initialize
      puts "D.initialize M.logger: #{M.logger.object_id}"
    end
  end
end

puts "M.logger (before C.new): #{M.logger.object_id}"
engine = M::C.new
puts "M.logger (after C.new): #{M.logger.object_id}"

La sortie de ce à quoi ressemble le code (un object_id de 4 signifie nil):

M.logger (before C.new): 4
C.initialize, before setting M.logger: 4
C.initialize, after setting M.logger: 59360
D.initialize M.logger: 59360
M.logger (after C.new): 59360

Merci pour l'aide les gars!

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X