Module: ObjectTracker

Defined in:
lib/object_tracker.rb,
lib/object_tracker/version.rb,
lib/object_tracker/log_formatter.rb,
lib/object_tracker/tracker_method.rb

Defined Under Namespace

Classes: LogFormatter, TrackerMethod

Constant Summary collapse

VERSION =
'2.1.1'.freeze

Class Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Class Attribute Details

.loggerObject



21
22
23
24
25
# File 'lib/object_tracker.rb', line 21

def logger
  @logger ||= Logger.new(STDOUT).tap do |config|
    config.formatter = LogFormatter.new
  end
end

Class Method Details

.build_tracker_methods(obj, method_names, except: []) ⇒ Object

Private



95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
# 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) ⇒ Object

Parameters:

  • trackers (Array<TrackerMethod>)
  • :mod (Hash)

    a customizable set of options

  • :before (Hash)

    a customizable set of options

  • :after (Hash)

    a customizable set of options



73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
# 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) ⇒ Object

Note:

Alias to .()

Utilities (not extended or mixed in)

Tracks method calls to the given object

Parameters:

  • obj (Object)

    class or instance to track

  • method_names (Array<Symbol>) (defaults to: [])

    method names to track

  • :except (Hash)

    a customizable set of options

  • :before (Hash)

    a customizable set of options

  • :after (Hash)

    a customizable set of options



47
48
49
50
51
52
53
54
55
56
57
58
59
# 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) ⇒ Object

Note:

If we don’t rescue, watch out for segfaults o.0



121
122
123
124
125
126
127
128
129
130
131
132
133
# 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) ⇒ Object



135
136
137
138
139
140
141
142
143
# 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) ⇒ Object



61
62
63
64
65
66
67
# 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) ⇒ Object



145
146
147
148
149
150
151
152
153
# 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

.reserved_tracker_methodsObject



27
28
29
30
31
32
33
# 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_hooksObject



155
156
157
# File 'lib/object_tracker.rb', line 155

def self.tracker_hooks
  @__tracker_hooks ||= Hash.new { |me, key| me[key] = [] }
end

.with_error_loggingObject



159
160
161
162
163
164
# 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

Instance Method Details

#track_all!(method_names = [], **options) ⇒ Object

Parameters:

  • method_names (Array<Symbol>) (defaults to: [])

    method names to track

  • :except (Hash)

    a customizable set of options

  • :before (Hash)

    a customizable set of options

  • :after (Hash)

    a customizable set of options



13
14
15
16
# File 'lib/object_tracker.rb', line 13

def track_all!(method_names = [], **options)
  ObjectTracker.(self, method_names, **options)
  self
end