[code] Using a common Logger object for extensions

Continuing the discussion from Creating log files for ruby code:

Here is a couple of examples of using a module to encapsulate a Logger object for use by other submodules.

For reference this is the class documenttion for the Logger class:


Simple edition:

# encoding: UTF-8

module AuthorTopLevelNamespace # outer namespace module

  require 'logger'

  module Log

    PATH  ||= File.join(ENV["HOME"],"Desktop")
    FILE  ||= "SketchUp_Plugins.log"
    @@log ||= Logger.new(File.join(PATH,FILE), 2, 1024*1024)

    # Redefine the method missing handler for this module to forward method
    # calls to a Logger instance (@@log) if it responds to the method name.
    # If this call is not meant for the Logger object, then just call super
    # so the NoMethodError is handled normally.
    def self.method_missing(methname,*args)
      @@log.respond_to?(methname) ? @@log.send(methname,*args) : super
    end

  end

  module SomePlugin # an inner extension submodule

    extend self

    # Within some method of this extension, a method call to the Log module
    # forwards to it's Logger object, some text to be logged in the logger file.
    def some_method
      Log.info("Sending my log some information.")
    end

  end

end # outer namespace module

Deluxe edition:

# encoding: UTF-8

module AuthorTopLevelNamespace # outer namespace module

  require 'logger'

  module Log

    PATH  ||= File.join(ENV["HOME"],"Desktop")
    FILE  ||= "SketchUp_Plugins.log"
    @@log ||= Logger.new(File.join(PATH,FILE), 2, 1024*1024)

    # Redefine the method missing handler for this module to forward method
    # calls to a Logger instance (@@log) if it responds to the method name.
    # If the first argument is a String and it has a %s replaceable parameter,
    # then replace that parameter with a Kernel#caller string.
    # If this call is not meant for the Logger object, then just call super
    # so the NoMethodError is handled normally.
    def self.method_missing(methname,*args)
      if @@log.respond_to?(methname)
        if args.first.is_a?(String) && args.first.include?('%s')
          args[0] = args.first % caller_locations(0)[2].to_s
        end
        @@log.send(methname,*args)
      else
        super # it's a real NoMethodError exception
      end
    end

  end

  module SomePlugin # an inner extension submodule

    extend self

    # This local extension method will prepend logger text with the
    # qualified module name and a replaceable %s parameter that will
    # be replaced by a caller inspection string.
    def log(text)
      Log.info("#{Module.nesting[0].name}:%s: #{text}")
    end

    # With some method of the extension, a call to the log() method
    # passes some text to be logged in the logger file.
    def some_method
      log("Sending my log some information.")
    end

  end

end # outer namespace module
2 Likes

Mixin Module edition

This example is a simple mixin module example. When you mixin a module containing constants, those constant objects become accessible from within the “mixee” modules and classes, as if they were local constants. But keep in mind that these are really proxy lookups. All modules or classes that mixin the module will share the objects that the constant point at.

# encoding: UTF-8

module AuthorTopLevelNamespace # outer namespace module

  require 'logger'

  module LogMixin

    LOGPATH  ||= File.join(ENV["HOME"],"Desktop")
    LOGFILE  ||= "SketchUp_Plugins.log"

    LOG ||= Logger.new(File.join(LOGPATH, LOGFILE), 2, 1024*1024)

  end

  module SomePlugin # an inner extension submodule

    extend self

    # Mixin the LogMixin module giving this module proxy access to LOG:
    include LogMixin

    # Within some method of this extension, a method call to send some text
    # to the shared LOG object, to be logged in the logger file.
    def some_method
      LOG.info("Sending my log some information.")
    end

    LOG.info("#{Module.nesting[0].name} was loaded.")
  end

end # outer namespace module

:bulb: