lib/peekaboo.rb in peekaboo-0.2.1 vs lib/peekaboo.rb in peekaboo-0.3.0

- old
+ new

@@ -1,73 +1,97 @@ require 'peekaboo/configuration' +require 'peekaboo/singleton_methods' -# The workhorse of this "Unobtrusive Tracing System". +# This system has been designed to provide you with an easy and unobtrusive way to trace: +# * _When_ certain methods are being called +# * _What_ values they are being supplied +# * _What_ values they return +# * _If_ they raise an exception +# +# Its API supports both class and instance method tracing inside any of your custom types. +# You can enable tracing for existing methods and/or pre-register method signatures for any of your types. +# The latter option gives you the ability to trace any methods that are defined _dynamically_ at runtime. +# +# ( see {SingletonMethods#enable_tracing_for} for details ) +# +# You can also setup *auto-inclusion*, which will allow you _dynamically_ include this module into any of +# your types at runtime. This alleviates the hassle of having to "+include Peekaboo+" inside all of the +# classes that you intend use it. +# +# ( see {Configuration#autoinclude_with} for details ) module Peekaboo class << self - # @return [Configuration] the current configuration + # @private def configuration @configuration ||= Configuration.new end - # Convenience method added to assist in configuring the system - # ( see {Configuration} for details on all options ). + # Use this to configure various aspects of tracing in your application. # - # @example Configuring the system inside your project - # Peekaboo.configure do |config| - # config.trace_with MyCustomerLogger.new - # config.autoinclude_with SomeBaseClass, AnotherSoloClass - # end + # See {Configuration} for option details. + # @yieldparam [Configuration] config current configuration def configure yield configuration end - # Callback used to hook tracing system into any class. - # - # @param [Class] klass including class + # @private def included klass - klass.const_set :PEEKABOO_METHOD_LIST, [] + klass.const_set :PEEKABOO_METHOD_MAP, { :singleton_methods => Set.new, :instance_methods => Set.new }.freeze klass.instance_variable_set :@_hooked_by_peekaboo, true klass.extend SingletonMethods def klass.method_added name - Peekaboo.wrap_method self, name if peek_list.include? name + Peekaboo.wrap self, name, :instance if traced_instance_methods.include? name end + + def klass.singleton_method_added name + Peekaboo.wrap self, name, :singleton if traced_singleton_methods.include? name + end end - # Modifies a class, and its child classes, to dynamically include module - # at runtime. This method is used by {Configuration#autoinclude_with}. - # - # @param [Class] klass class to modify + # @private def setup_autoinclusion klass + # @note changes made to this methods to support backwards + # compatibility with {#enable_tracing_on}. This will become + # much simpler when that method is removed. def klass.method_missing(method_name, *args, &block) - if method_name.to_s =~ /^enable_tracing_on$/ + if method_name.to_s =~ /^enable_tracing_(on|for)$/ instance_eval { include Peekaboo } - enable_tracing_on *args + __send__ method_name, *args else super end end end - # Takes a class object and method name, aliases the original method, - # and redefines the method with injected tracing. - # - # @note Should I add execution time to tracing? Configurable? - # - # @param [Class] klass method owner - # @param [Symbol] name method to trace - def wrap_method klass, name + # @private + def wrap klass, method_name, target return if @_adding_a_method - @_adding_a_method = true - - original_method = "original_#{name}" + begin + @_adding_a_method = true + original_method = "original_#{method_name}" + case target + when :singleton then wrap_singleton_method klass, method_name, original_method + when :instance then wrap_instance_method klass, method_name, original_method + else raise 'Only :class and :instance are valid targets' + end + rescue => exe + raise exe + ensure + @_adding_a_method = false + end + end + + private + + def wrap_instance_method klass, method_name, original_method_name method_wrapping = %{ - alias_method :#{original_method}, :#{name} - def #{name} *args, &block - trace = "\#{caller(1)[0]}\n\t( Invoking: #{klass}\##{name} with \#{args.inspect} " + alias_method :#{original_method_name}, :#{method_name} + def #{method_name} *args, &block + trace = "\#{caller(1)[0]}\n\t( Invoking: #{klass}\##{method_name} with \#{args.inspect} " begin - result = #{original_method} *args, &block + result = #{original_method_name} *args, &block trace << "==> Returning: \#{result.inspect} )" result rescue Exception => exe trace << "!!! Raising: \#{exe.message.inspect} )" raise exe @@ -75,52 +99,30 @@ Peekaboo.configuration.tracer.info trace end end } klass.class_eval method_wrapping - - @_adding_a_method = false end - end - - # Contains methods added to every class that includes the *Peekaboo* module, - # either through _direct_ or _auto_ inclusion. - module SingletonMethods - # @return [Array<Symbol>] - # a list of instance methods that are being traced inside calling class - def peek_list - self::PEEKABOO_METHOD_LIST - end - # Enables instance method tracing on calling class. - # - # @example Trace a couple of methods - # class SomeClass - # include Peekaboo - # - # def method1; end - # def method2; end - # def method3; end - # end - # - # # Tracing will be performed on method1(), method2(), but NOT method3() - # SomeClass.enable_tracing_on :method1, :method2 - # - # @param [*Symbol] method_names - # the list of methods that you want to trace - # @raise [RuntimeError] - # when attempting to add a method that is already being traced - def enable_tracing_on *method_names - include Peekaboo unless @_hooked_by_peekaboo - - method_names.each do |method_name| - unless peek_list.include? method_name - peek_list << method_name - method_list = self.instance_methods(false).map(&:to_sym) - Peekaboo.wrap_method self, method_name if method_list.include? method_name - else - raise "Already tracing `#{method_name}'" + def wrap_singleton_method klass, method_name, original_method_name + method_wrapping = %{ + class << self + alias_method :#{original_method_name}, :#{method_name} + def #{method_name} *args, &block + trace = "\#{caller(1)[0]}\n\t( Invoking: #{klass}.#{method_name} with \#{args.inspect} " + begin + result = #{original_method_name} *args, &block + trace << "==> Returning: \#{result.inspect} )" + result + rescue Exception => exe + trace << "!!! Raising: \#{exe.message.inspect} )" + raise exe + ensure + Peekaboo.configuration.tracer.info trace + end + end end - end + } + klass.instance_eval method_wrapping end end end