module ObjectTracker

Constants

VERSION

Attributes

logger[W]

Public Class Methods

build_tracker_methods(obj, method_names, except: []) click to toggle source

Private

# File lib/object_tracker.rb, line 95
def self.build_tracker_methods(obj, method_names, except: [])
  class_methods, inst_methods = [], []
  reserved = obj.respond_to?(:reserved_tracker_methods) ? obj.reserved_tracker_methods : ObjectTracker.reserved_tracker_methods
  obj_instance = obj.respond_to?(:allocate) ? obj.allocate : obj
  if Array(method_names).any?
    Array(method_names).each do |method_name|
      if obj.methods.include?(method_name)
        class_methods << TrackerMethod.new(obj, method_name)
      elsif obj.respond_to?(:instance_method)
        inst_methods << TrackerMethod.new(obj_instance, method_name)
      end
    end
  else
    if obj.respond_to?(:instance_methods)
      (obj.instance_methods - reserved - Array(except)).each do |method_name|
        inst_methods << TrackerMethod.new(obj_instance, method_name)
      end
    end
    (obj.methods - reserved - Array(except)).each do |method_name|
      class_methods << TrackerMethod.new(obj, method_name)
    end
  end
  return class_methods, inst_methods
end
build_tracker_mod(trackers, mod: Module.new, before: nil, after: nil) click to toggle source

@param trackers [Array<TrackerMethod>] @option :mod [Module] module to add tracking to, will be mixed into self @option :before [Proc] proc to call before method execution (e.g. ->(name, context, args) {}) @option :after [Proc] proc to call after method execution (e.g. ->(name, context, args, duration) {})

# File lib/object_tracker.rb, line 73
  def self.build_tracker_mod(trackers, mod: Module.new, before: nil, after: nil)
    ObjectTracker.tracker_hooks[:before] << before if before
    ObjectTracker.tracker_hooks[:after] << after if after
    Array(trackers).each do |tracker|
      mod.module_eval <<-RUBY, __FILE__, __LINE__
        def #{tracker.name}(*args)
          ObjectTracker.call_tracker_hooks(:before, "#{tracker.display_name}", self, args)
          result, message, duration = ObjectTracker.call_with_tracking("#{tracker.display_name}", args, "#{tracker.source}") { super }
          ObjectTracker.logger.debug { message  + " (%.5f)" % duration }
          result
        ensure
          ObjectTracker.call_tracker_hooks(:after, "#{tracker.display_name}", self, args, duration)
        end
      RUBY
    end
    mod
  end
call(obj, method_names = [], except: [], **options) click to toggle source

Utilities (not extended or mixed in)

Tracks method calls to the given object

@note Alias to .()

@param obj [Object] class or instance to track
@param method_names [Array<Symbol>] method names to track
@option :except [Array<Symbol>] method names to NOT track
@option :before [Proc] proc to call before method execution (e.g. ->(name, context, args) {})
@option :after [Proc] proc to call after method execution (e.g. ->(name, context, args, duration) {})
# File lib/object_tracker.rb, line 47
def self.call(obj, method_names = [], except: [], **options)
  class_methods, inst_methods = ObjectTracker.build_tracker_methods(obj, method_names, except: except)
  name = obj.to_s
  obj.send :extend, ObjectTracker.define_tracker_mod(obj, :TrackerExt, ObjectTracker.build_tracker_mod(class_methods, **options))
  if inst_methods.any?
    # Silence all the noise about comparing class name and checking object behavior
    ObjectTracker.with_error_logging do
      obj.send :prepend, ObjectTracker.define_tracker_mod(obj, :InstanceTrackerExt, ObjectTracker.build_tracker_mod(inst_methods, **options))
    end
  end
  logger.info { "following #{name}" }
  obj
end
call_tracker_hooks(key, method_name, context, args, duration = nil) click to toggle source

@note If we don't rescue, watch out for segfaults o.0

# File lib/object_tracker.rb, line 121
def self.call_tracker_hooks(key, method_name, context, args, duration = nil)
  tracker_hooks[key].each do |hook|
    begin
      if duration
        hook.call(context, method_name, args, duration)
      else
        hook.call(context, method_name, args)
      end
    rescue Exception
      next
    end
  end
end
call_with_tracking(msg, args, source) { |rescue nil| ... } click to toggle source
# File lib/object_tracker.rb, line 135
def self.call_with_tracking(msg, args, source)
  result = nil
  msg += ObjectTracker.format_args(args) unless args.empty?
  msg += " [#{source}]"
  bm = Benchmark.measure do
    result = yield rescue nil
  end
  [result, msg, bm.real]
end
define_tracker_mod(context, name, mod) click to toggle source
# File lib/object_tracker.rb, line 61
def self.define_tracker_mod(context, name, mod)
  context = context.class unless context.respond_to?(:const_set)
  if context.const_defined?(name, false)
    context.send :remove_const, name
  end
  context.const_set name, mod
end
format_args(args) click to toggle source
# File lib/object_tracker.rb, line 145
def self.format_args(args)
  result = " with ["
  args.each do |arg|
    result << (arg ? arg.to_s : "nil")
    result << ", "
  end
  result.sub! /,\s\z/, ""
  result << "]"
end
logger() click to toggle source
# File lib/object_tracker.rb, line 21
def logger
  @logger ||= Logger.new(STDOUT).tap do |config|
    config.formatter = LogFormatter.new
  end
end
reserved_tracker_methods() click to toggle source
# File lib/object_tracker.rb, line 27
def reserved_tracker_methods
  @__reserved_methods ||= begin
    names = [:__send__]
    names.concat [:default_scope, :current_scope=] if defined?(Rails)
    names
  end
end
tracker_hooks() click to toggle source
# File lib/object_tracker.rb, line 155
def self.tracker_hooks
  @__tracker_hooks ||= Hash.new { |me, key| me[key] = [] }
end
with_error_logging() { || ... } click to toggle source
# File lib/object_tracker.rb, line 159
def self.with_error_logging
  old_log_level, logger.level = logger.level, Logger::ERROR
  yield
ensure
  logger.level = old_log_level if old_log_level
end

Public Instance Methods

track_all!(method_names = [], **options) click to toggle source

@param method_names [Array<Symbol>] method names to track @option :except [Array<Symbol>] method names to NOT track @option :before [Proc] proc to call before method execution (e.g. ->(name, context, args) {}) @option :after [Proc] proc to call after method execution (e.g. ->(name, context, args, duration) {})

# File lib/object_tracker.rb, line 13
def track_all!(method_names = [], **options)
  ObjectTracker.(self, method_names, **options)
  self
end