Class: AppMap::Config

Inherits:
Object
  • Object
show all
Defined in:
lib/appmap/config.rb

Defined Under Namespace

Classes: LookupPackage, Package

Constant Summary collapse

METHOD_HOOKS =

Hook well-known functions. When a function configured here is available in the bundle, it will be hooked with the predefined labels specified here. If any of these hooks are not desired, they can be disabled in the exclude section of appmap.yml.

package_hooks('actionview',
    [
      method_hook('ActionView::Renderer', :render, %w[mvc.view]),
      method_hook('ActionView::TemplateRenderer', :render, %w[mvc.view]),
      method_hook('ActionView::PartialRenderer', :render, %w[mvc.view])
    ],
    handler_class: AppMap::Handler::Rails::Template::RenderHandler,
    package_name: 'action_view'
  ),
  package_hooks('actionview',
    [
      method_hook('ActionView::Resolver', i[find_all find_all_anywhere], %w[mvc.template.resolver])
    ],
    handler_class: AppMap::Handler::Rails::Template::ResolverHandler,
    package_name: 'action_view'
  ),
  package_hooks('actionpack',
    [
      method_hook('ActionDispatch::Request::Session', i[destroy [] dig values []= clear update delete fetch merge], %w[http.session]),
      method_hook('ActionDispatch::Cookies::CookieJar', i[[]= clear update delete recycle], %w[http.session]),
      method_hook('ActionDispatch::Cookies::EncryptedCookieJar', i[[]= clear update delete recycle], %w[http.cookie crypto.encrypt])
    ],
    package_name: 'action_dispatch'
  ),
  package_hooks('cancancan',
    [
      method_hook('CanCan::ControllerAdditions', i[authorize! can? cannot?], %w[security.authorization]),
      method_hook('CanCan::Ability', i[authorize?], %w[security.authorization])
    ]
  ),
  package_hooks('actionpack',
    [
      method_hook('ActionController::Instrumentation', i[process_action send_file send_data redirect_to], %w[mvc.controller])
    ],
    package_name: 'action_controller'
  )
].flatten.freeze
OPENSSL_PACKAGES =
->(labels) { Package.build_from_path('openssl', package_name: 'openssl', labels: labels) }
BUILTIN_HOOKS =

Hook functions which are builtin to Ruby. Because they are builtins, they may be loaded before appmap. Therefore, we can’t rely on TracePoint to report the loading of this code.

{
  'OpenSSL::PKey::PKey' => TargetMethods.new(:sign, OPENSSL_PACKAGES.(%w[crypto.pkey])),
  'OpenSSL::X509::Request' => TargetMethods.new(i[sign verify], OPENSSL_PACKAGES.(%w[crypto.x509])),
  'OpenSSL::PKCS5' => TargetMethods.new(i[pbkdf2_hmac_sha1 pbkdf2_hmac], OPENSSL_PACKAGES.(%w[crypto.pkcs5])),
  'OpenSSL::Cipher' => [
    TargetMethods.new(i[encrypt], OPENSSL_PACKAGES.(%w[crypto.encrypt])),
    TargetMethods.new(i[decrypt], OPENSSL_PACKAGES.(%w[crypto.decrypt]))
  ],
  'ActiveSupport::Callbacks::CallbackSequence' => [
    TargetMethods.new(:invoke_before, Package.build_from_gem('activesupport', force: true, package_name: 'active_support', labels: %w[mvc.before_action])),
    TargetMethods.new(:invoke_after, Package.build_from_gem('activesupport', force: true, package_name: 'active_support', labels: %w[mvc.after_action])),
  ],
  'ActiveSupport::SecurityUtils' => TargetMethods.new(:secure_compare, Package.build_from_gem('activesupport', force: true, package_name: 'active_support/security_utils', labels: %w[crypto.secure_compare])),
  'OpenSSL::X509::Certificate' => TargetMethods.new(:sign, OPENSSL_PACKAGES.(%w[crypto.x509])),
  'Net::HTTP' => TargetMethods.new(:request, Package.build_from_path('net/http', package_name: 'net/http', labels: %w[protocol.http]).tap do |package|
    package.handler_class = AppMap::Handler::NetHTTP
  end),
  'Net::SMTP' => TargetMethods.new(:send, Package.build_from_path('net/smtp', package_name: 'net/smtp', labels: %w[protocol.email.smtp])),
  'Net::POP3' => TargetMethods.new(:mails, Package.build_from_path('net/pop3', package_name: 'net/pop', labels: %w[protocol.email.pop])),
  # This is happening: Method send_command not found on Net::IMAP
  # 'Net::IMAP' => TargetMethods.new(:send_command, Package.build_from_path('net/imap', package_name: 'net/imap', labels: %w[protocol.email.imap])),
  # 'Marshal' => TargetMethods.new(%i[dump load], Package.build_from_path('marshal', labels: %w[format.marshal])),
  'Psych' => TargetMethods.new(i[dump dump_stream load load_stream parse parse_stream], Package.build_from_path('yaml', package_name: 'psych', labels: %w[format.yaml])),
  'JSON::Ext::Parser' => TargetMethods.new(:parse, Package.build_from_path('json', package_name: 'json', labels: %w[format.json])),
  'JSON::Ext::Generator::State' => TargetMethods.new(:generate, Package.build_from_path('json', package_name: 'json', labels: %w[format.json])),
}.freeze

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(name, packages, exclude: [], functions: []) ⇒ Config

Returns a new instance of Config.



223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
# File 'lib/appmap/config.rb', line 223

def initialize(name, packages, exclude: [], functions: [])
  @name = name
  @packages = packages
  @hook_paths = Set.new(packages.map(&:path))
  @exclude = exclude
  @builtin_hooks = BUILTIN_HOOKS
  @functions = functions

  @hooked_methods = METHOD_HOOKS.each_with_object(Hash.new { |h,k| h[k] = [] }) do |cls_target_methods, hooked_methods|
    hooked_methods[cls_target_methods.cls] << cls_target_methods.target_methods
  end

  functions.each do |func|
    package_options = {}
    package_options[:labels] = func.labels if func.labels
    @hooked_methods[func.cls] << TargetMethods.new(func.function_names, Package.build_from_path(func.package, package_options))
  end

  @hooked_methods.each_value do |hooks|
    Array(hooks).each do |hook|
      @hook_paths << hook.package.path
    end
  end
end

Instance Attribute Details

#builtin_hooksObject (readonly)

Returns the value of attribute builtin_hooks.



221
222
223
# File 'lib/appmap/config.rb', line 221

def builtin_hooks
  @builtin_hooks
end

#excludeObject (readonly)

Returns the value of attribute exclude.



221
222
223
# File 'lib/appmap/config.rb', line 221

def exclude
  @exclude
end

#hooked_methodsObject (readonly)

Returns the value of attribute hooked_methods.



221
222
223
# File 'lib/appmap/config.rb', line 221

def hooked_methods
  @hooked_methods
end

#nameObject (readonly)

Returns the value of attribute name.



221
222
223
# File 'lib/appmap/config.rb', line 221

def name
  @name
end

#packagesObject (readonly)

Returns the value of attribute packages.



221
222
223
# File 'lib/appmap/config.rb', line 221

def packages
  @packages
end

Class Method Details

.load(config_data) ⇒ Object

Loads configuration from a Hash.



256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
# File 'lib/appmap/config.rb', line 256

def load(config_data)
  functions = (config_data['functions'] || []).map do |function_data|
    package = function_data['package']
    cls = function_data['class']
    functions = function_data['function'] || function_data['functions']
    raise 'AppMap class configuration should specify package, class and function(s)' unless package && cls && functions
    functions = Array(functions).map(&:to_sym)
    labels = function_data['label'] || function_data['labels']
    labels = Array(labels).map(&:to_s) if labels
    Function.new(package, cls, labels, functions)
  end
  packages = (config_data['packages'] || []).map do |package|
    gem = package['gem']
    path = package['path']
    raise 'AppMap package configuration should specify gem or path, not both' if gem && path

    if gem
      shallow = package['shallow']
      # shallow is true by default for gems
      shallow = true if shallow.nil?
      Package.build_from_gem(gem, exclude: package['exclude'] || [], shallow: shallow)
    else
      Package.build_from_path(path, exclude: package['exclude'] || [], shallow: package['shallow'])
    end
  end.compact
  exclude = config_data['exclude'] || []
  Config.new config_data['name'], packages, exclude: exclude, functions: functions
end

.load_from_file(config_file_name) ⇒ Object

Loads configuration data from a file, specified by the file name.



250
251
252
253
# File 'lib/appmap/config.rb', line 250

def load_from_file(config_file_name)
  require 'yaml'
  load YAML.safe_load(::File.read(config_file_name))
end

.method_hook(cls, method_names, labels) ⇒ Object



143
144
145
# File 'lib/appmap/config.rb', line 143

def method_hook(cls, method_names, labels)
  MethodHook.new(cls, method_names, labels)
end

.package_hooks(gem_name, methods, handler_class: nil, package_name: nil) ⇒ Object



133
134
135
136
137
138
139
140
141
# File 'lib/appmap/config.rb', line 133

def package_hooks(gem_name, methods, handler_class: nil, package_name: nil)
  Array(methods).map do |method|
    package = Package.build_from_gem(gem_name, package_name: package_name, labels: method.labels, shallow: false, optional: true)
    next unless package

    package.handler_class = handler_class if handler_class
    ClassTargetMethods.new(method.cls, TargetMethods.new(Array(method.method_names), package))
  end.compact
end

Instance Method Details

#lookup_package(cls, method) ⇒ Object



336
337
338
# File 'lib/appmap/config.rb', line 336

def lookup_package(cls, method)
  LookupPackage.new(self, cls, method).package
end

#never_hook?(cls, method) ⇒ Boolean

Returns:

  • (Boolean)


340
341
342
343
# File 'lib/appmap/config.rb', line 340

def never_hook?(cls, method)
  _, separator, = ::AppMap::Hook.qualify_method_name(method)
  return true if exclude.member?(cls.name) || exclude.member?([ cls.name, separator, method.name ].join)
end

#path_enabled?(path) ⇒ Boolean

Determines if methods defined in a file path should possibly be hooked.

Returns:

  • (Boolean)


296
297
298
299
# File 'lib/appmap/config.rb', line 296

def path_enabled?(path)
  path = AppMap::Util.normalize_path(path)
  @hook_paths.find { |hook_path| path.index(hook_path) == 0 }
end

#to_hObject



286
287
288
289
290
291
292
293
# File 'lib/appmap/config.rb', line 286

def to_h
  {
    name: name,
    packages: packages.map(&:to_h),
    functions: @functions.map(&:to_h),
    exclude: exclude
  }
end