Ruby - share logger instance among module/classes

后端 未结 9 1716
爱一瞬间的悲伤
爱一瞬间的悲伤 2020-12-04 06:45

Working on a little Ruby script that goes out to the web and crawls various services. I\'ve got a module with several classes inside:

module Crawler
  class          


        
相关标签:
9条回答
  • 2020-12-04 07:08

    Inspired by this thread I created the easy_logging gem.

    It combines all the features discussed such as:

    • Adds logging functionality anywhere with one, short, self-descriptive command
    • Logger works in both class and instance methods
    • Logger is specific to class and contains class name

    Installation:

    gem install 'easy_logging
    

    Usage:

    require 'easy_logging'
    
    class YourClass
      include EasyLogging
    
      def do_something
        # ...
        logger.info 'something happened'
      end
    end
    
    class YourOtherClass
      include EasyLogging
    
      def self.do_something
        # ...
        logger.info 'something happened'
      end
    end
    
    YourClass.new.do_something
    YourOtherClass.do_something
    

    Output

    I, [2017-06-03T21:59:25.160686 #5900]  INFO -- YourClass: something happened
    I, [2017-06-03T21:59:25.160686 #5900]  INFO -- YourOtherClass: something happened
    

    More details on GitHub.

    0 讨论(0)
  • 2020-12-04 07:08

    The may be some weird Ruby magic that could let you avoid it, but there's a fairly simple solution that doesn't need weird. Just put the logger into the module and access it directly, with a mechanism to set it. If you want to be cool about it, define a "lazy logger" that keeps a flag to say if it has a logger yet, and either silently drops messages until the logger is set, throws an exception of something is logged before the logger is set, or adds the log message to a list so it can be logged once the logger is defined.

    0 讨论(0)
  • 2020-12-04 07:10

    With the design you've laid out, it looks like the easiest solution is to give Crawler a module method that returns a module ivar.

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

    Or you could use "class <<self magic" if you wanted:

    module Crawler
      class <<self
        attr_accessor :logger
      end
    end
    

    It does the exact same thing.

    0 讨论(0)
  • 2020-12-04 07:10

    As Zenagray points out, logging from class methods was left out of Jacob's answer. A small addition solves that:

    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
    

    The intended use is via "include":

    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
    

    Produces:

    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
    

    Note the id of the logger is the same in all four cases. If you want a different instance for each class, then don't use Logging.logger, rather use 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
    

    The same program now produces:

    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
    

    Note that the first two id's are the same but are different from the 2nd two ids showing that we have two instances -- one for each class.

    0 讨论(0)
  • 2020-12-04 07:11

    A little chunk of code to demonstrate how this works. I'm simply creating a new basic Object so that I can observe that the object_id remains the same throughout the calls:

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

    The output of this code looks like (an object_id of 4 means 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
    

    Thanks for the help guys!

    0 讨论(0)
  • 2020-12-04 07:14

    Although an old question, I thought it worthwhile to document a different approach.

    Building on Jacob's answer, I would suggest a module that you can add in as and when needed.

    My version is this:

    # saved into lib/my_log.rb
    
    require 'logger'
    
    module MyLog
    
      def self.logger
        if @logger.nil?
          @logger = Logger.new( STDERR)
          @logger.datetime_format = "%H:%M:%S "
        end
        @logger
      end
    
      def self.logger=( logger)
        @logger = logger
      end
    
      levels = %w(debug info warn error fatal)
      levels.each do |level|
        define_method( "#{level.to_sym}") do |msg|
          self.logger.send( level, msg)
        end
      end
    end
    
    include MyLog
    

    I have this saved into a library of handy modules, and I would use it like this:

    #! /usr/bin/env ruby
    #
    
    require_relative '../lib/my_log.rb'
    
    MyLog.debug "hi"
    # => D, [19:19:32 #31112] DEBUG -- : hi
    
    MyLog.warn "ho"
    # => W, [19:20:14 #31112]  WARN -- : ho
    
    MyLog.logger.level = Logger::INFO
    
    MyLog.logger = Logger.new( 'logfile.log')
    
    MyLog.debug 'huh'
    # => no output, sent to logfile.log instead
    

    I find this a lot easier and more versatile than other options I've looked at so far, so I hope it helps you with yours.

    0 讨论(0)
提交回复
热议问题