appmap 0.94.1 → 0.95.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: ca46315eb8aa31221cee8a1d656187a4e4355e63f6b2be1d591829dddb76f7e0
4
- data.tar.gz: d038130a2827f01f573bf69dbe32ab837d7ff68b28dce7a6076a80eee2f4550e
3
+ metadata.gz: a3f036b61b1750e2820f676694a15af9461404dc9b8f048add0b9b7291dd1d4a
4
+ data.tar.gz: f40f6587ecbf21c250952b1ecfc51349d6ad3cf53276d6cf30d9a97f859295b2
5
5
  SHA512:
6
- metadata.gz: 58a84ec34e17b22acf37fd0b9f964fb93f82531cb7f800721d33214b1bc434b81e47cbf7202f87ddeade990644d4cc512898125bc59851e82f035f9093c31eb2
7
- data.tar.gz: a8a0bfa9cf83dc59d6e3848228f602bf8030c01601755a853a55ee4b452a970e2de1c823649da1fc0b557b8b9b6ba24c142f324975a20a222aadc239d37958b1
6
+ metadata.gz: bd45853f98e82f90b6885b615931f73b125a5194041221c0bf9f21b41ab1f2285489ef3b7e1915905b41e434e65d607eebdf232d53a6bc1e1b75f516f73d2222
7
+ data.tar.gz: 3465e8521239b8eadc1145e06847bb454a1f6b43ef0adf6ed50f8d2fb6eccb63ac79a86b913628db7344b10c8b68980dd6c3aa5ac9741eac4bfef6b39d109a76
data/.gitignore CHANGED
@@ -2,12 +2,14 @@
2
2
  /html/
3
3
  .bundle
4
4
  /.yardoc
5
+ /.yarn
5
6
  /_yardoc/
6
7
  /coverage/
7
8
  /doc/
8
9
  /pkg/
9
10
  /spec/reports/
10
11
  tmp/
12
+ appmap_hook.log
11
13
  vendor
12
14
  node_modules
13
15
  Gemfile.lock
data/.nvmrc ADDED
@@ -0,0 +1 @@
1
+ 14
data/.ruby-version ADDED
@@ -0,0 +1 @@
1
+ 3.0.2
data/.rufo ADDED
@@ -0,0 +1,2 @@
1
+ # .rufo
2
+ quote_style :single
data/CHANGELOG.md CHANGED
@@ -1,3 +1,20 @@
1
+ # [0.95.0](https://github.com/getappmap/appmap-ruby/compare/v0.94.1...v0.95.0) (2022-12-15)
2
+
3
+
4
+ ### Bug Fixes
5
+
6
+ * Disable active_record hooks ([239986d](https://github.com/getappmap/appmap-ruby/commit/239986deded12e65384b441033180c30a3ffe698))
7
+ * Fix Array#pack hook ([2a8b2ed](https://github.com/getappmap/appmap-ruby/commit/2a8b2ed4d9fb047213e6a3fd427d846c5d312e2f))
8
+ * Fix name of rails7 test database ([6581b65](https://github.com/getappmap/appmap-ruby/commit/6581b65c088ee8415fd150d11c4ab6793173f0e2))
9
+ * Typo in label configuration ([999b25b](https://github.com/getappmap/appmap-ruby/commit/999b25b75057a7f6061cfbc9cdc04616f5aeb998))
10
+
11
+
12
+ ### Features
13
+
14
+ * Enable deserialize.safe labels ([62be78a](https://github.com/getappmap/appmap-ruby/commit/62be78a0502d83c9b5a2b7d97abd17d144bd41c5))
15
+ * Enhance the hook log a lot ([c93d91c](https://github.com/getappmap/appmap-ruby/commit/c93d91c5be86b9195e14ef40741600c633638b40))
16
+ * Enhancing hook logging and profiling ([01dbe4b](https://github.com/getappmap/appmap-ruby/commit/01dbe4bb973b71fea0885f55d17b80d6b6b8fb6e))
17
+
1
18
  ## [0.94.1](https://github.com/getappmap/appmap-ruby/compare/v0.94.0...v0.94.1) (2022-11-23)
2
19
 
3
20
 
@@ -19,6 +19,6 @@
19
19
  - method: ActiveSupport::MessageEncryptor#encrypt_and_sign
20
20
  require_name: active_support/message_encryptor
21
21
  force: true
22
- - method: ActiveSupport::MessageEncryptor#decrypt_and_verifdy
22
+ - method: ActiveSupport::MessageEncryptor#decrypt_and_verify
23
23
  require_name: active_support/message_encryptor
24
24
  force: true
@@ -7,9 +7,9 @@
7
7
  - method: Marshal#dump
8
8
  require_name: ruby
9
9
  label: serialize
10
- - method: String#pack
10
+ - method: Array#pack
11
11
  require_name: ruby
12
- label: string.pack
12
+ label: array.pack
13
13
  - methods:
14
14
  - String#unpack
15
15
  - String#unpack1
data/lib/appmap/config.rb CHANGED
@@ -8,6 +8,7 @@ require 'appmap/handler'
8
8
  require 'appmap/service/guesser'
9
9
  require 'appmap/swagger/configuration'
10
10
  require 'appmap/depends/configuration'
11
+ require_relative './hook_log'
11
12
 
12
13
  module AppMap
13
14
  class Config
@@ -499,7 +500,11 @@ module AppMap
499
500
 
500
501
  def never_hook?(cls, method)
501
502
  _, separator, = ::AppMap::Hook.qualify_method_name(method)
502
- return true if exclude.member?(cls.name) || exclude.member?([ cls.name, separator, method.name ].join)
503
+ if exclude.member?(cls.name) || exclude.member?([ cls.name, separator, method.name ].join)
504
+ HookLog.log "Hooking of #{method} disabled by configuration" if HookLog.enabled?
505
+ return true
506
+ end
507
+ false
503
508
  end
504
509
  end
505
510
  end
@@ -1,2 +1,2 @@
1
1
  - method: Rails::Application#config_for
2
- # label: deserialize.safe
2
+ label: deserialize.safe
@@ -1,2 +1,2 @@
1
1
  - method: Rails::Application::Configuration#database_configuration
2
- # label: deserialize.safe
2
+ label: deserialize.safe
@@ -38,13 +38,13 @@ module AppMap
38
38
  end
39
39
 
40
40
  def activate
41
- if Hook::LOG
41
+ if HookLog.enabled?
42
42
  msg = if method_display_name
43
- "#{method_display_name}"
44
- else
45
- "#{hook_method.name} (class resolution deferred)"
46
- end
47
- warn "AppMap: Hooking #{msg} at line #{(hook_method.source_location || []).join(':')}"
43
+ "#{method_display_name}"
44
+ else
45
+ "#{hook_method.name} (class resolution deferred)"
46
+ end
47
+ HookLog.log "Hooking #{msg} at line #{(hook_method.source_location || []).join(':')}"
48
48
  end
49
49
 
50
50
  hook_method_parameters = hook_method.parameters.dup.freeze
@@ -65,8 +65,7 @@ module AppMap
65
65
  protected
66
66
 
67
67
  def defining_class(hook_class)
68
- cls = \
69
- if RUBY_MAJOR_VERSION == 2 && RUBY_MINOR_VERSION <= 5
68
+ cls = if RUBY_MAJOR_VERSION == 2 && RUBY_MINOR_VERSION <= 5
70
69
  hook_class
71
70
  .ancestors
72
71
  .select { |cls| cls.method_defined?(hook_method.name) }
data/lib/appmap/hook.rb CHANGED
@@ -1,17 +1,15 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'English'
4
+ require_relative './hook_log'
4
5
 
5
6
  module AppMap
6
7
  class Hook
7
- LOG = (ENV['APPMAP_DEBUG'] == 'true' || ENV['DEBUG'] == 'true')
8
- LOG_HOOK = (ENV['DEBUG_HOOK'] == 'true')
9
-
10
8
  OBJECT_INSTANCE_METHODS = %i[! != !~ <=> == === =~ __id__ __send__ class clone define_singleton_method display dup
11
9
  enum_for eql? equal? extend freeze frozen? hash inspect instance_eval instance_exec instance_of? instance_variable_defined? instance_variable_get instance_variable_set instance_variables is_a? itself kind_of? method methods nil? object_id private_methods protected_methods public_method public_methods public_send remove_instance_variable respond_to? send singleton_class singleton_method singleton_methods taint tainted? tap then to_enum to_s to_h to_a trust untaint untrust untrusted? yield_self].freeze
12
10
  OBJECT_STATIC_METHODS = %i[! != !~ < <= <=> == === =~ > >= __id__ __send__ alias_method allocate ancestors attr
13
11
  attr_accessor attr_reader attr_writer autoload autoload? class class_eval class_exec class_variable_defined? class_variable_get class_variable_set class_variables clone const_defined? const_get const_missing const_set constants define_method define_singleton_method deprecate_constant display dup enum_for eql? equal? extend freeze frozen? hash include include? included_modules inspect instance_eval instance_exec instance_method instance_methods instance_of? instance_variable_defined? instance_variable_get instance_variable_set instance_variables is_a? itself kind_of? method method_defined? methods module_eval module_exec name new nil? object_id prepend private_class_method private_constant private_instance_methods private_method_defined? private_methods protected_instance_methods protected_method_defined? protected_methods public_class_method public_constant public_instance_method public_instance_methods public_method public_method_defined? public_methods public_send remove_class_variable remove_instance_variable remove_method respond_to? send singleton_class singleton_class? singleton_method singleton_methods superclass taint tainted? tap then to_enum to_s trust undef_method untaint untrust untrusted? yield_self].freeze
14
- SLOW_PACKAGE_THRESHOLD = 0.05
12
+ SLOW_PACKAGE_THRESHOLD = 0.001
15
13
 
16
14
  @unbound_method_arity = ::UnboundMethod.instance_method(:arity)
17
15
  @method_arity = ::Method.instance_method(:arity)
@@ -21,7 +19,7 @@ module AppMap
21
19
  Mutex.new.synchronize do
22
20
  @hook_builtins = true if @hook_builtins.nil?
23
21
 
24
- return false unless @hook_builtins
22
+ next false unless @hook_builtins
25
23
 
26
24
  @hook_builtins = false
27
25
  true
@@ -41,9 +39,9 @@ module AppMap
41
39
  def qualify_method_name(method)
42
40
  if method.owner.singleton_class?
43
41
  class_name = singleton_method_owner_name(method)
44
- [ class_name, '.', method.name ]
42
+ [class_name, '.', method.name]
45
43
  else
46
- [ method.owner.name, '#', method.name ]
44
+ [method.owner.name, '#', method.name]
47
45
  end
48
46
  end
49
47
  end
@@ -69,19 +67,23 @@ module AppMap
69
67
  @slow_packages = Set.new
70
68
 
71
69
  if ENV['APPMAP_PROFILE_HOOK'] == 'true'
70
+ dump_times = lambda do
71
+ @module_load_times
72
+ .keys
73
+ .select { |key| !@slow_packages.member?(key) }
74
+ .each do |key|
75
+ elapsed = @module_load_times[key]
76
+ if elapsed >= SLOW_PACKAGE_THRESHOLD
77
+ @slow_packages.add(key)
78
+ warn "AppMap: Package #{key} took #{@module_load_times[key]} seconds to hook"
79
+ end
80
+ end
81
+ end
82
+
83
+ at_exit &dump_times
72
84
  Thread.new do
73
- sleep 1
74
85
  while true
75
- @module_load_times
76
- .keys
77
- .select { |key| !@slow_packages.member?(key) }
78
- .each do |key|
79
- elapsed = @module_load_times[key]
80
- if elapsed >= SLOW_PACKAGE_THRESHOLD
81
- @slow_packages.add(key)
82
- warn "AppMap: Package #{key} took #{@module_load_times[key]} seconds to hook"
83
- end
84
- end
86
+ dump_times.call
85
87
  sleep 5
86
88
  end
87
89
  end
@@ -99,45 +101,51 @@ module AppMap
99
101
  hook_loaded_code = lambda do |hooks_by_class, builtin|
100
102
  hooks_by_class.each do |class_name, hooks|
101
103
  Array(hooks).each do |hook|
102
- if builtin && hook.package.require_name && hook.package.require_name != 'ruby'
103
- require hook.package.require_name
104
- end
105
-
106
- Array(hook.method_names).each do |method_name|
107
- method_name = method_name.to_sym
108
-
109
- warn "AppMap: Initiating hook for builtin #{class_name} #{method_name}" if LOG
110
-
104
+ HookLog.builtin class_name do
105
+ if builtin && hook.package.require_name && hook.package.require_name != 'ruby'
106
+ begin
107
+ require hook.package.require_name
108
+ rescue
109
+ HookLog.load_error hook.package.require_name, "Unable to require #{hook.package.require_name}: #{$!}" if HookLog.enabled?
110
+ next
111
+ end
112
+ end
113
+
111
114
  begin
112
115
  base_cls = Object.const_get class_name
113
116
  rescue NameError
117
+ HookLog.load_error class_name, "Class #{class_name} not found in global scope" if HookLog.enabled?
114
118
  next
115
119
  end
116
-
117
- hook_method = lambda do |entry|
118
- cls, method = entry
119
- next if config.never_hook?(cls, method)
120
-
121
- hook.package.handler_class.new(hook.package, cls, method).activate
122
- end
123
-
124
- methods = []
125
- # irb(main):001:0> Kernel.public_instance_method(:system)
126
- # (irb):1:in `public_instance_method': method `system' for module `Kernel' is private (NameError)
127
- if base_cls == Kernel
128
- methods << [ base_cls, base_cls.instance_method(method_name) ] rescue nil
129
- end
130
- methods << [ base_cls, base_cls.public_instance_method(method_name) ] rescue nil
131
- methods << [ base_cls, base_cls.protected_instance_method(method_name) ] rescue nil
132
- if base_cls.respond_to?(:singleton_class)
133
- methods << [ base_cls.singleton_class, base_cls.singleton_class.public_instance_method(method_name) ] rescue nil
134
- methods << [ base_cls.singleton_class, base_cls.singleton_class.protected_instance_method(method_name) ] rescue nil
135
- end
136
- methods.compact!
137
- if methods.empty?
138
- warn "Method #{method_name} not found on #{base_cls.name}" if LOG
139
- else
140
- methods.each(&hook_method)
120
+
121
+ Array(hook.method_names).each do |method_name|
122
+ method_name = method_name.to_sym
123
+
124
+ hook_method = lambda do |entry|
125
+ cls, method = entry
126
+ next if config.never_hook?(cls, method)
127
+
128
+ hook.package.handler_class.new(hook.package, cls, method).activate
129
+ end
130
+
131
+ methods = []
132
+ # irb(main):001:0> Kernel.public_instance_method(:system)
133
+ # (irb):1:in `public_instance_method': method `system' for module `Kernel' is private (NameError)
134
+ if base_cls == Kernel
135
+ methods << [base_cls, base_cls.instance_method(method_name)] rescue nil
136
+ end
137
+ methods << [base_cls, base_cls.public_instance_method(method_name)] rescue nil
138
+ methods << [base_cls, base_cls.protected_instance_method(method_name)] rescue nil
139
+ if base_cls.respond_to?(:singleton_class)
140
+ methods << [base_cls.singleton_class, base_cls.singleton_class.public_instance_method(method_name)] rescue nil
141
+ methods << [base_cls.singleton_class, base_cls.singleton_class.protected_instance_method(method_name)] rescue nil
142
+ end
143
+ methods.compact!
144
+ if methods.empty?
145
+ HookLog.load_error [ base_cls.name, method_name ].join('[#.]'), "Method #{method_name} not found on #{base_cls.name}" if HookLog.enabled?
146
+ else
147
+ methods.each(&hook_method)
148
+ end
141
149
  end
142
150
  end
143
151
  end
@@ -145,89 +153,93 @@ module AppMap
145
153
  end
146
154
 
147
155
  hook_loaded_code.(config.builtin_hooks, true)
148
- hook_loaded_code.(config.gem_hooks, false)
149
156
  end
150
157
 
151
158
  protected
152
159
 
153
160
  def trace_location(trace_point)
154
- [ trace_point.path, trace_point.lineno ].join(':')
161
+ [trace_point.path, trace_point.lineno].join(':')
155
162
  end
156
163
 
157
164
  def trace_end(trace_point)
158
165
  location = trace_location(trace_point)
159
- warn "Class or module ends at location #{location}" if Hook::LOG || Hook::LOG_HOOK
160
166
  return unless @trace_locations.add?(location)
161
-
162
- path = trace_point.path
163
- enabled = !@notrace_paths.member?(path) && config.path_enabled?(path)
164
- unless enabled
165
- warn 'Not hooking - path is not enabled' if Hook::LOG || Hook::LOG_HOOK
166
- @notrace_paths << path
167
- return
168
- end
169
-
170
- cls = trace_point.self
171
-
172
- instance_methods = cls.public_instance_methods(false) + cls.protected_instance_methods(false) - OBJECT_INSTANCE_METHODS
173
- # NoMethodError: private method `singleton_class' called for Rack::MiniProfiler:Class
174
- class_methods = begin
175
- if cls.respond_to?(:singleton_class)
176
- cls.singleton_class.public_instance_methods(false) + cls.singleton_class.protected_instance_methods(false) - instance_methods - OBJECT_STATIC_METHODS
177
- else
178
- []
167
+ HookLog.on_load location do
168
+ path = trace_point.path
169
+ enabled = !@notrace_paths.member?(path) && config.path_enabled?(path)
170
+ unless enabled
171
+ HookLog.log 'Not hooking - path is not enabled' if HookLog.enabled?
172
+ @notrace_paths << path
173
+ next
179
174
  end
180
- rescue NameError
181
- []
182
- end
183
175
 
184
- hook = lambda do |hook_cls|
185
- lambda do |method_id|
186
- method = \
187
- begin
188
- hook_cls.instance_method(method_id)
189
- rescue NameError
190
- warn "AppMap: Method #{hook_cls} #{fn} is not accessible: #{$!}" if LOG
191
- next
176
+ cls = trace_point.self
177
+
178
+ instance_methods = cls.public_instance_methods(false) + cls.protected_instance_methods(false) - OBJECT_INSTANCE_METHODS
179
+ # NoMethodError: private method `singleton_class' called for Rack::MiniProfiler:Class
180
+ class_methods = begin
181
+ if cls.respond_to?(:singleton_class)
182
+ cls.singleton_class.public_instance_methods(false) + cls.singleton_class.protected_instance_methods(false) - instance_methods - OBJECT_STATIC_METHODS
183
+ else
184
+ []
192
185
  end
186
+ rescue NameError
187
+ []
188
+ end
193
189
 
194
- package = config.lookup_package(hook_cls, method)
195
- # doing this check first returned early in 98.7% of cases in sample_app_6th_ed
196
- next unless package
190
+ hook = lambda do |hook_cls|
191
+ lambda do |method_id|
192
+ method = begin
193
+ hook_cls.instance_method(method_id)
194
+ rescue NameError
195
+ HookLog.load_error [ hook_cls, method_id ].join('[#.]'), "Method #{hook_cls} #{method_id} is not accessible: #{$!}" if HookLog.enabled?
196
+ next
197
+ end
197
198
 
198
- # Don't try and trace the AppMap methods or there will be
199
- # a stack overflow in the defined hook method.
200
- next if %w[Marshal AppMap ActiveSupport].member?((hook_cls&.name || '').split('::')[0])
199
+ package = config.lookup_package(hook_cls, method)
200
+ # doing this check first returned early in 98.7% of cases in sample_app_6th_ed
201
+ next unless package
201
202
 
202
- next if method_id == :call
203
+ # Don't try and trace the AppMap methods or there will be
204
+ # a stack overflow in the defined hook method.
205
+ next if %w[Marshal AppMap ActiveSupport].member?((hook_cls&.name || '').split('::')[0])
203
206
 
204
- next if self.class.already_hooked?(method)
207
+ next if method_id == :call
205
208
 
206
- warn "AppMap: Examining #{hook_cls} #{method.name}" if LOG
209
+ next if self.class.already_hooked?(method)
207
210
 
208
- disasm = RubyVM::InstructionSequence.disasm(method)
209
- # Skip methods that have no instruction sequence, as they are either have no body or they are or native.
210
- # TODO: Figure out how to tell the difference?
211
- next unless disasm
211
+ HookLog.log "Examining #{hook_cls} #{method.name}" if HookLog.enabled?
212
212
 
213
- package.handler_class.new(package, hook_cls, method).activate
213
+ disasm = RubyVM::InstructionSequence.disasm(method)
214
+ # Skip methods that have no instruction sequence, as they are either have no body or they are or native.
215
+ # TODO: Figure out how to tell the difference?
216
+ next unless disasm
217
+
218
+ package.handler_class.new(package, hook_cls, method).activate
219
+ end
214
220
  end
215
- end
216
221
 
217
- start = Time.now
218
- instance_methods.each(&hook.(cls))
219
- begin
220
- # NoMethodError: private method `singleton_class' called for Rack::MiniProfiler:Class
221
- class_methods.each(&hook.(cls.singleton_class)) if cls.respond_to?(:singleton_class)
222
- rescue NameError
223
- # NameError:
224
- # uninitialized constant Faraday::Connection
225
- warn "NameError in #{__FILE__}: #{$!.message}"
226
- end
227
- elapsed = Time.now - start
228
- if location.index(Bundler.bundle_path.to_s) == 0
229
- package_tokens = location[Bundler.bundle_path.to_s.length + 1..-1].split('/')
230
- @module_load_times[package_tokens[1]] += elapsed
222
+ start = Time.now
223
+ instance_methods.each(&hook.(cls))
224
+ begin
225
+ # NoMethodError: private method `singleton_class' called for Rack::MiniProfiler:Class
226
+ class_methods.each(&hook.(cls.singleton_class)) if cls.respond_to?(:singleton_class)
227
+ rescue NameError
228
+ # NameError:
229
+ # uninitialized constant Faraday::Connection
230
+ warn "NameError in #{__FILE__}: #{$!.message}"
231
+ end
232
+ elapsed = Time.now - start
233
+ if location.index(Bundler.bundle_path.to_s) == 0
234
+ package_tokens = location[Bundler.bundle_path.to_s.length + 1..-1].split('/')
235
+ @module_load_times[package_tokens[1]] += elapsed
236
+ else
237
+ file_path = location[Dir.pwd.length + 1..-1]
238
+ if file_path
239
+ location = file_path.split('/', 3)[0..1].join('/')
240
+ @module_load_times[location] += elapsed
241
+ end
242
+ end
231
243
  end
232
244
  end
233
245
  end
@@ -0,0 +1,99 @@
1
+ module AppMap
2
+ class HookLog
3
+ LOG = (ENV['APPMAP_DEBUG'] == 'true' || ENV['DEBUG'] == 'true')
4
+ LOG_HOOK = (ENV['DEBUG_HOOK'] == 'true' || ENV['APPMAP_LOG_HOOK'] == 'true')
5
+ LOG_HOOK_FILE = (ENV['APPMAP_LOG_HOOK_FILE'] || 'appmap_hook.log')
6
+
7
+ def initialize
8
+ @file_handle = self.class.send :open_log_file
9
+ @elapsed = Hash.new { |h, k| h[k] = [] }
10
+
11
+ at_exit do
12
+ @file_handle.puts 'Elapsed time:'
13
+ @elapsed.keys.each do |k|
14
+ @file_handle.puts "#{k}:\t#{@elapsed[k].sum}"
15
+ end
16
+ @file_handle.flush
17
+ @file_handle.close
18
+ end
19
+ end
20
+
21
+ def start_time(timer)
22
+ @elapsed[timer] << [Util.gettime]
23
+ end
24
+
25
+ def end_time(timer)
26
+ unless @elapsed[timer].last.is_a?(Array)
27
+ warn "AppMap: Unbalanced timing data in hook log"
28
+ @elapsed[timer].pop
29
+ return
30
+ end
31
+
32
+ @elapsed[timer][-1] = Util.gettime - @elapsed[timer].last[0]
33
+ end
34
+
35
+ class << self
36
+ def enabled?
37
+ LOG || LOG_HOOK
38
+ end
39
+
40
+ def builtin(class_name, &block)
41
+ return yield unless enabled?
42
+
43
+ begin
44
+ log "eager\tbegin\tInitiating eager hook for #{class_name}"
45
+ @hook_log.start_time :eager
46
+
47
+ yield
48
+ ensure
49
+ @hook_log.end_time :eager
50
+ log "eager\tend\tCompleted eager hook for #{class_name}"
51
+ end
52
+ end
53
+
54
+ def on_load(location, &block)
55
+ return yield unless enabled?
56
+
57
+ begin
58
+ log "on-load\tbegin\tInitiating on-load hook for class or module defined at location #{location}"
59
+ @hook_log.start_time :on_load
60
+
61
+ yield
62
+ ensure
63
+ @hook_log.end_time :on_load
64
+ log "on-load\tend\tCompleted on-load hook for location #{location}"
65
+ end
66
+ end
67
+
68
+ def load_error(name, msg)
69
+ log "load_error\t#{name}\t#{msg}"
70
+ end
71
+
72
+ def log(msg)
73
+ unless HookLog.enabled?
74
+ warn "AppMap: HookLog is not enabled. Disregarding message #{msg}"
75
+ return
76
+ end
77
+
78
+ @hook_log ||= HookLog.new
79
+ @hook_log.log msg
80
+ end
81
+
82
+ protected def open_log_file
83
+ if LOG_HOOK_FILE == 'stderr'
84
+ $stderr
85
+ else
86
+ File.open(LOG_HOOK_FILE, 'w')
87
+ end
88
+ end
89
+ end
90
+
91
+ def log(msg)
92
+ if LOG_HOOK_FILE == 'stderr'
93
+ msg = "AppMap: #{msg}"
94
+ end
95
+ msg = "#{Util.gettime}\t#{msg}"
96
+ @file_handle.puts(msg)
97
+ end
98
+ end
99
+ end
@@ -3,7 +3,7 @@
3
3
  module AppMap
4
4
  URL = 'https://github.com/applandinc/appmap-ruby'
5
5
 
6
- VERSION = '0.94.1'
6
+ VERSION = '0.95.0'
7
7
 
8
8
  APPMAP_FORMAT_VERSION = '1.10.0'
9
9