# Copyright (c) 2016 SolarWinds, LLC. # All rights reserved. module AppOpticsAPM ## # Provides utility methods for use while in the business # of instrumenting code module Util class << self def contextual_name(cls) # Attempt to infer a contextual name if not indicated # # For example: # ::ActiveRecord::ConnectionAdapters::AbstractMysqlAdapter.to_s.split(/::/).last # => "AbstractMysqlAdapter" # cls.to_s.split(/::/).last rescue cls end ## # method_alias # # Centralized utility method to alias a method on an arbitrary # class or module. # def method_alias(cls, method, name = nil) name ||= contextual_name(cls) if cls.method_defined?(method.to_sym) || cls.private_method_defined?(method.to_sym) # Strip '!' or '?' from method if present safe_method_name = method.to_s.chop if method.to_s =~ /\?$|\!$/ safe_method_name ||= method without_appoptics = "#{safe_method_name}_without_appoptics" with_appoptics = "#{safe_method_name}_with_appoptics" # Only alias if we haven't done so already unless cls.method_defined?(without_appoptics.to_sym) || cls.private_method_defined?(without_appoptics.to_sym) cls.class_eval do alias_method without_appoptics, method.to_s alias_method method.to_s, with_appoptics end end else AppOpticsAPM.logger.warn "[appoptics_apm/loading] Couldn't properly instrument #{name}.#{method}. Partial traces may occur." end end ## # class_method_alias # # Centralized utility method to alias a class method on an arbitrary # class or module # def class_method_alias(cls, method, name = nil) name ||= contextual_name(cls) if cls.singleton_methods.include? method.to_sym # Strip '!' or '?' from method if present safe_method_name = method.to_s.chop if method.to_s =~ /\?$|\!$/ safe_method_name ||= method without_appoptics = "#{safe_method_name}_without_appoptics" with_appoptics = "#{safe_method_name}_with_appoptics" # Only alias if we haven't done so already unless cls.singleton_methods.include? without_appoptics.to_sym cls.singleton_class.send(:alias_method, without_appoptics, method.to_s) cls.singleton_class.send(:alias_method, method.to_s, with_appoptics) end else AppOpticsAPM.logger.warn "[appoptics_apm/loading] Couldn't properly instrument #{name}. Partial traces may occur." end end ## # send_extend # # Centralized utility method to send an extend call for an # arbitrary class def send_extend(target_cls, cls) target_cls.send(:extend, cls) if defined?(target_cls) end ## # send_include # # Centralized utility method to send a include call for an # arbitrary class def send_include(target_cls, cls) target_cls.send(:include, cls) if defined?(target_cls) end ## # static_asset? # # Given a path, this method determines whether it is a static asset or not (based # solely on filename) # def static_asset?(path) path =~ Regexp.new(AppOpticsAPM::Config[:dnt_regexp], AppOpticsAPM::Config[:dnt_opts]) end ## # prettify # # Even to my surprise, 'prettify' is a real word: # transitive v. To make pretty or prettier, especially in a superficial or insubstantial way. # from The American Heritage Dictionary of the English Language, 4th Edition # # This method makes things 'purty' for reporting. def prettify(x) if (x.to_s =~ /^# e # Also rescue ScriptError (aka SyntaxError) in case one of the expected # version defines don't exist platform_info['Error'] = "Error in legacy_build_init_report: #{e.message}" AppOpticsAPM.logger.warn "[appoptics_apm/warn] Error in legacy_build_init_report: #{e.message}" AppOpticsAPM.logger.debug e.backtrace end platform_info end # rubocop:enable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity, Metrics/AbcSize ## # build_init_report # # Internal: Build a hash of KVs that reports on the status of the # running environment. This is used on stack boot in __Init reporting # and for AppOpticsAPM.support_report. # # rubocop:disable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity, Metrics/AbcSize def build_init_report platform_info = { '__Init' => 1 } begin platform_info['Force'] = true platform_info['Ruby.Platform.Version'] = RUBY_PLATFORM platform_info['Ruby.Version'] = RUBY_VERSION platform_info['Ruby.AppOpticsAPM.Version'] = ::AppOpticsAPM::Version::STRING # Should this be the oboe version, separate from the Ruby library's version? platform_info['Ruby.Oboe.Version'] = ::AppOpticsAPM::Version::STRING platform_info['RubyHeroku.AppOpticsAPM.Version'] = ::AppOpticsAPMHeroku::Version::STRING if defined?(::AppOpticsAPMHeroku) platform_info['Ruby.TraceMode.Version'] = ::AppOpticsAPM::Config[:tracing_mode] # Collect up the loaded gems if defined?(Gem) && Gem.respond_to?(:loaded_specs) Gem.loaded_specs.each_pair { |k, v| platform_info["Ruby.#{k}.Version"] = v.version.to_s } else platform_info.merge!(legacy_build_init_report) end # Report the server in use (if possible) if defined?(::Unicorn::Const::UNICORN_VERSION) platform_info['Ruby.AppContainer.Version'] = "Unicorn-#{::Unicorn::Const::UNICORN_VERSION}" elsif defined?(::Puma::Const::PUMA_VERSION) platform_info['Ruby.AppContainer.Version'] = "Puma-#{::Puma::Const::PUMA_VERSION} (#{::Puma::Const::CODE_NAME})" elsif defined?(::PhusionPassenger::PACKAGE_NAME) platform_info['Ruby.AppContainer.Version'] = "#{::PhusionPassenger::PACKAGE_NAME}-#{::PhusionPassenger::VERSION_STRING}" elsif defined?(::Thin::VERSION::STRING) platform_info['Ruby.AppContainer.Version'] = "Thin-#{::Thin::VERSION::STRING} (#{::Thin::VERSION::CODENAME})" elsif defined?(::Mongrel::Const::MONGREL_VERSION) platform_info['Ruby.AppContainer.Version'] = "Mongrel-#{::Mongrel::Const::MONGREL_VERSION}" elsif defined?(::Mongrel2::VERSION) platform_info['Ruby.AppContainer.Version'] = "Mongrel2-#{::Mongrel2::VERSION}" elsif defined?(::Trinidad::VERSION) platform_info['Ruby.AppContainer.Version'] = "Trinidad-#{::Trinidad::VERSION}" elsif defined?(::WEBrick::VERSION) platform_info['Ruby.AppContainer.Version'] = "WEBrick-#{::WEBrick::VERSION}" else platform_info['Ruby.AppContainer.Version'] = File.basename($PROGRAM_NAME) end rescue StandardError, ScriptError => e # Also rescue ScriptError (aka SyntaxError) in case one of the expected # version defines don't exist platform_info['Error'] = "Error in build_report: #{e.message}" AppOpticsAPM.logger.warn "[appoptics_apm/warn] Error in build_init_report: #{e.message}" AppOpticsAPM.logger.debug e.backtrace end platform_info end # rubocop:enable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity, Metrics/AbcSize end end end