require 'sync_attr'

# Logger class variable mix-in
#
#   Lazy initialize and a logger class variable with instance accessor
#
#   By including this mix-in into any class it will define a class level logger
#   and make it accessible via instance methods
#
# Example
#
#  require 'semantic_logger'
#  SemanticLogger.default_level = :debug
#  SemanticLogger.add_appender(STDOUT)
#
#  class ExternalSupplier
#    # Create class and instance logger methods
#    include SemanticLogger::Loggable
#
#    def call_supplier(amount, name)
#      logger.debug "Calculating with amount", { :amount => amount, :name => name }
#
#      # Measure and log on completion how long the call took to the external supplier
#      logger.benchmark_info "Calling external interface" do
#        # Code to call the external supplier ...
#      end
#    end
#  end
module SemanticLogger
  module Loggable

    def self.included(base)
      base.class_eval do
        # Thread safe class variable initialization
        include SyncAttr

        sync_cattr_reader :logger do
          SemanticLogger[self]
        end
      end
    end

    # Also make the logger available as an instance method MixIn
    # The class logger can be replaced using an instance specific #logger= below
    def logger
      @semantic_logger ||= self.class.logger
    end

    # Set instance specific logger
    #
    # By default instances of the class will use the class logger. Sometimes it
    # is useful to be able to add instance specific logging data to the class name.
    #
    # For example, server or host_name that the class instance is using.
    #
    # Example:
    #   require 'semantic_logger'
    #   SemanticLogger.default_level = :debug
    #   SemanticLogger.add_appender(STDOUT)
    #
    #   class MyClass
    #     include SemanticLogger::Loggable
    #
    #     def self.my_name=(my_name)
    #       # Use class level logger that only logs class name
    #       logger.info "My name is changed to #{my_name}"
    #
    #       @@my_name = my_name
    #     end
    #
    #     def initialize(host_name)
    #       # Add host_name to every log entry in this logging instance
    #       self.logger = SemanticLogger["#{self.class.name} [#{host_name}]"]
    #
    #       logger.info "Started server"
    #     end
    #
    #     def check
    #       logger.debug "Checking..."
    #     end
    #   end
    #
    #   MyClass.my_name = "Joe"
    #
    #   mine = MyClass.new('server.com')
    #   mine.check
    #
    # # Generates the following log output:
    #
    # 2013-04-02 15:08:42.368574 I [37279:70198560687720] MyClass -- My name is changed to Joe
    # 2013-04-02 15:08:42.369934 I [37279:70198560687720] MyClass [server.com] -- Started server
    # 2013-04-02 15:08:42.371171 D [37279:70198560687720] MyClass [server.com] -- Checking...
    #
    def logger=(logger)
      @semantic_logger = logger
    end

  end
end