Class: AppMap::Hook
- Inherits:
-
Object
- Object
- AppMap::Hook
- Defined in:
- lib/appmap/hook.rb
Defined Under Namespace
Constant Summary collapse
- LOG =
false- HOOK_DISABLE_KEY =
'AppMap::Hook.disable'
Class Method Summary collapse
-
.hook(config = AppMap.configure) ⇒ Object
Observe class loading and hook all methods which match the config.
Class Method Details
.hook(config = AppMap.configure) ⇒ Object
Observe class loading and hook all methods which match the config.
51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 |
# File 'lib/appmap/hook.rb', line 51 def hook(config = AppMap.configure) package_include_paths = config.packages.map(&:path) package_exclude_paths = config.packages.map do |pkg| pkg.exclude.map do |exclude| File.join(pkg.path, exclude) end end.flatten before_hook = lambda do |defined_class, method, receiver, args| require 'appmap/event' call_event = AppMap::Event::MethodCall.build_from_invocation(defined_class, method, receiver, args) AppMap.tracing.record_event call_event, defined_class: defined_class, method: method [ call_event, Time.now ] end after_hook = lambda do |call_event, defined_class, method, start_time, return_value, exception| require 'appmap/event' elapsed = Time.now - start_time return_event = AppMap::Event::MethodReturn.build_from_invocation \ defined_class, method, call_event.id, elapsed, return_value, exception AppMap.tracing.record_event return_event end with_disabled_hook = lambda do |&fn| # Don't record functions, such as to_s and inspect, that might be called # by the fn. Otherwise there can be a stack oveflow. Thread.current[HOOK_DISABLE_KEY] = true begin fn.call ensure Thread.current[HOOK_DISABLE_KEY] = false end end TracePoint.trace(:end) do |tp| cls = tp.self instance_methods = cls.public_instance_methods(false) class_methods = cls.singleton_class.public_instance_methods(false) - instance_methods hook_method = lambda do |cls| lambda do |method_id| next if method_id.to_s =~ /_hooked_by_appmap$/ method = cls.public_instance_method(method_id) location = method.source_location location_file, = location next unless location_file location_file = location_file[Dir.pwd.length + 1..-1] if location_file.index(Dir.pwd) == 0 match = package_include_paths.find { |p| location_file.index(p) == 0 } match &&= !package_exclude_paths.find { |p| location_file.index(p) } next unless match disasm = RubyVM::InstructionSequence.disasm(method) # Skip methods that have no instruction sequence, as they are obviously trivial. next unless disasm defined_class, method_symbol = \ if method.owner.singleton_class? # Singleton class name is like: #<Class:<(.*)>> class_name = method.owner.to_s['#<Class:<'.length-1..-2] [ class_name, '.' ] else [ method.owner.name, '#' ] end method_display_name = "#{defined_class}#{method_symbol}#{method.name}" # Don't try and trace the tracing method or there will be a stack overflow # in the defined hook method. next if method_display_name == "AppMap.tracing" warn "AppMap: Hooking #{method_display_name}" if LOG cls.define_method method_id do |*args, &block| base_method = method.bind(self).to_proc hook_disabled = Thread.current[HOOK_DISABLE_KEY] enabled = true if !hook_disabled && AppMap.tracing.enabled? return base_method.call(*args, &block) unless enabled call_event, start_time = with_disabled_hook.call do before_hook.call(defined_class, method, self, args) end return_value = nil exception = nil begin return_value = base_method.call(*args, &block) rescue exception = $ERROR_INFO raise ensure with_disabled_hook.call do after_hook.call(call_event, defined_class, method, start_time, return_value, exception) end end end end end instance_methods.each(&hook_method.call(cls)) class_methods.each(&hook_method.call(cls.singleton_class)) end end |