52

Web に出てさまざまなサービスをクロールする小さな Ruby スクリプトに取り組んでいます。内部にいくつかのクラスを含むモジュールがあります。

module Crawler
  class Runner
  class Options
  class Engine
end

これらのクラスのすべてのロガーで 1 つのロガーを共有したいと考えています。通常、これをモジュールの定数に入れて、次のように参照します。

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

問題は、出力先がわかるまでロガー インスタンスを作成できないことです。コマンド ラインからクローラーを開始し、その時点で、開発 (ログ出力は STDOUT に出力) または実稼働 (ログ出力はファイル、crawler.log に出力) で実行することを指定できます。

crawler --environment=production

Optionsコマンドラインから渡されたオプションを解析するクラスがあります。その時点で初めて、正しい出力場所でロガーをインスタンス化する方法を知ることができます。

だから、私の質問は、どのように/どこにロガーオブジェクトを配置して、すべてのクラスがそれにアクセスできるようにするのですか?

作成するすべてのクラス インスタンスの各呼び出しにロガー インスタンスを渡すこともできますnew()が、それを行うためのより優れた Ruby 的な方法が必要であることはわかっています。私は、class << self他の魔法と共有するモジュール上の奇妙なクラス変数を想像しています。:)

もう少し詳しく:Runnerコマンド ライン オプションをOptionsクラスに渡すことですべてを開始し、いくつかのインスタンス変数を持つオブジェクトを取得します。

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

ロガー オブジェクトにアクセスできるようにするためのコードが必要ですEngine(内部で初期化されるいくつかのクラスと共にEngine)。ヘルプ!

すでにインスタンス化されている Logger の出力場所を動的に変更できれば (ログ レベルを変更する方法と同様に)、このすべてを回避できます。私はそれを STDOUT にインスタンス化してから、本番環境であればファイルに切り替えます。Ruby の $stdout グローバル変数を変更して、出力を STDOUT 以外の場所にリダイレクトするという提案をどこかで見ましたが、これはかなりハッキリしているようです。

ありがとう!

4

9 に答える 9

107

クラスでメソッドを使用できるようにloggerしたいのですが、すべての初期化子に振りかけるのは好きではありません@logger = Logging.logger。通常、私はこれを行います:

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

次に、クラスで:

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

このLogging#loggerメソッドは、モジュールが混在するインスタンスにアクセスできるため、ロギング モジュールを拡張して、ログ メッセージでクラス名を記録するのは簡単です。

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

あなたWidgetは今、そのクラス名でメッセージをログに記録し、少し変更する必要はありませんでした:)

于 2011-07-20T20:34:00.843 に答える
22

あなたがレイアウトした設計では、最も簡単な解決策は、Crawler にモジュール ivar を返すモジュール メソッドを与えることです。

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

または、必要にclass <<self応じて「マジック」を使用できます。

module Crawler
  class <<self
    attr_accessor :logger
  end
end

それはまったく同じことをします。

于 2009-05-27T21:25:33.063 に答える
2

これは、それを回避できる奇妙なRubyの魔法かもしれませんが、奇妙なことを必要としないかなり単純な解決策があります。ロガーをモジュールに入れて、設定するメカニズムを使用して直接アクセスするだけです。それについてクールになりたい場合は、ロガーがまだあるかどうかを示すフラグを保持する「レイジーロガー」を定義し、ロガーが設定されるまでメッセージをサイレントにドロップするか、ロガーがログに記録される前に何かの例外をスローします設定するか、ログメッセージをリストに追加して、ロガーが定義されたらログに記録できるようにします。

于 2009-05-27T20:08:55.870 に答える
2

これがどのように機能するかを示すための小さなコード。object_id が呼び出し全体で同じままであることを確認できるように、新しい基本オブジェクトを作成しているだけです。

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}"

このコードの出力は次のようになります ( object_id4 の意味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

助けてくれてありがとう!

于 2009-05-27T23:20:24.530 に答える
1

ロガーをシングルトンにラップしてから、 MyLogger.instance を使用してアクセスできます

于 2009-10-16T10:58:58.603 に答える