class OpenTelemetry::Instrumentation::Base
The Base
class holds all metadata and configuration for an instrumentation. All instrumentation packages should include a subclass of Instrumentation::Base
that will register it with OpenTelemetry.instrumentation_registry
and make it available for discovery and installation by an SDK.
A typical subclass of Base
will provide an install block, a present block, and possibly a compatible block. Below is an example:
module OpenTelemetry
module Instrumentation module Sinatra class Instrumentation < OpenTelemetry::Instrumentation::Base install do |config| # install instrumentation, either by library hook or applying # a monkey patch end # determine if the target library is present present do defined?(::Sinatra) end # if the target library is present, is it compatible? compatible do Gem.loaded_specs['sinatra'].version > MIN_VERSION end end end end
end
The instrumentation name and version will be inferred from the namespace of the class. In this example, they'd be 'OpenTelemetry::Instrumentation::Sinatra' and OpenTelemetry::Instrumentation::Sinatra::VERSION, but can be explicitly set using the instrumentation_name
and instrumetation_version
methods if necessary.
All subclasses of OpenTelemetry::Instrumentation::Base
are automatically registered with OpenTelemetry.instrumentation_registry which is used by SDKs for instrumentation discovery and installation.
Instrumentation
libraries can use the instrumentation subclass to easily gain a reference to its named tracer. For example:
OpenTelemetry::Instrumentation::Sinatra.instance.tracer
The instrumention class establishes a convention for disabling an instrumentation by environment variable and local configuration. An instrumentation disabled by environment variable will take precedence over local config. The convention for environment variable name is the library name, upcased with '::' replaced by underscores, OPENTELEMETRY shortened to OTEL_{LANG}, and '_ENABLED' appended. For example: OTEL_RUBY_INSTRUMENTATION_SINATRA_ENABLED = false.
Constants
- NAME_REGEX
- VALIDATORS
Attributes
Public Class Methods
The compatible block for this instrumentation. This check will be run if the target library is present to determine if it's compatible. It's not required, but a common use case will be to check to target library version for compatibility.
@param [Callable] blk The compatibility block for this instrumentation
# File lib/opentelemetry/instrumentation/base.rb, line 136 def compatible(&blk) @compatible_blk = blk end
# File lib/opentelemetry/instrumentation/base.rb, line 77 def inherited(subclass) OpenTelemetry::Instrumentation.registry.register(subclass) end
The install block for this instrumentation. This will be where you install instrumentation, either by framework hook or applying a monkey patch.
@param [Callable] blk The install block for this instrumentation @yieldparam [Hash] config The instrumentation config will be yielded to the
install block
# File lib/opentelemetry/instrumentation/base.rb, line 116 def install(&blk) @install_blk = blk end
# File lib/opentelemetry/instrumentation/base.rb, line 156 def instance @instance ||= new(instrumentation_name, instrumentation_version, install_blk, present_blk, compatible_blk, options) end
Optionally set the name of this instrumentation. If not explicitly set, the name will default to the namespace of the class, or the class name if it does not have a namespace. If there is not a namespace, or a class name, it will default to 'unknown'.
@param [String] instrumentation_name
The full name of the instrumentation package
# File lib/opentelemetry/instrumentation/base.rb, line 87 def instrumentation_name(instrumentation_name = nil) if instrumentation_name @instrumentation_name = instrumentation_name else @instrumentation_name ||= infer_name || 'unknown' end end
Optionally set the version of this instrumentation. If not explicitly set, the version will default to the VERSION constant under namespace of the class, or the VERSION constant under the class name if it does not have a namespace. If a VERSION constant cannot be found, it defaults to '0.0.0'.
@param [String] instrumentation_version
The version of the instrumentation package
# File lib/opentelemetry/instrumentation/base.rb, line 102 def instrumentation_version(instrumentation_version = nil) if instrumentation_version @instrumentation_version = instrumentation_version else @instrumentation_version ||= infer_version || '0.0.0' end end
# File lib/opentelemetry/instrumentation/base.rb, line 187 def initialize(name, version, install_blk, present_blk, compatible_blk, options) @name = name @version = version @install_blk = install_blk @present_blk = present_blk @compatible_blk = compatible_blk @config = {} @installed = false @options = options @tracer = OpenTelemetry::Trace::Tracer.new end
The option method is used to define default configuration options for the instrumentation library. It requires a name, default value, and a validation callable to be provided. @param [String] name The name of the configuration option @param default The default value to be used, or to used if validation fails @param [Callable, Symbol] validate Accepts a callable or a symbol that matches a key in the VALIDATORS
hash. The supported keys are, :array, :boolean, :callable, :integer, :string.
# File lib/opentelemetry/instrumentation/base.rb, line 148 def option(name, default:, validate:) validate = VALIDATORS[validate] || validate raise ArgumentError, "validate must be #{VALIDATORS.keys.join(', ')}, or a callable" unless validate.respond_to?(:call) @options ||= [] @options << { name: name, default: default, validate: validate } end
The present block for this instrumentation. This block is used to detect if target library is present on the system. Typically this will involve checking to see if the target gem spec was loaded or if expected constants from the target library are present.
@param [Callable] blk The present block for this instrumentation
# File lib/opentelemetry/instrumentation/base.rb, line 126 def present(&blk) @present_blk = blk end
Private Class Methods
# File lib/opentelemetry/instrumentation/base.rb, line 165 def infer_name @inferred_name ||= if (md = name.match(NAME_REGEX)) # rubocop:disable Naming/MemoizedInstanceVariableName md['namespace'] || md['classname'] end end
# File lib/opentelemetry/instrumentation/base.rb, line 171 def infer_version return unless (inferred_name = infer_name) mod = inferred_name.split('::').map(&:to_sym).inject(Object) do |object, const| object.const_get(const) end mod.const_get(:VERSION) rescue NameError nil end
Public Instance Methods
Calls the compatible block of the Instrumentation
subclasses, if no block is provided it's assumed to be compatible
# File lib/opentelemetry/instrumentation/base.rb, line 234 def compatible? return true unless @compatible_blk instance_exec(&@compatible_blk) end
Whether this instrumentation is enabled. It first checks to see if it's enabled by an environment variable and will proceed to check if it's enabled by local config, if given.
@param [optional Hash] config The local config
# File lib/opentelemetry/instrumentation/base.rb, line 245 def enabled?(config = nil) return false unless enabled_by_env_var? return config[:enabled] if config&.key?(:enabled) true end
Install instrumentation with the given config. The present? and compatible? will be run first, and install will return false if either fail. Will return true if install was completed successfully.
@param [Hash] config The config for this instrumentation
# File lib/opentelemetry/instrumentation/base.rb, line 205 def install(config = {}) return true if installed? return false unless installable?(config) @config = config_options(config) instance_exec(@config, &@install_blk) @tracer = OpenTelemetry.tracer_provider.tracer(name, version) @installed = true end
Whether or not this instrumentation is installable in the current process. Will be true when the instrumentation defines an install block, is not disabled by environment or config, and the target library present and compatible.
@param [Hash] config The config for this instrumentation
# File lib/opentelemetry/instrumentation/base.rb, line 220 def installable?(config = {}) @install_blk && enabled?(config) && present? && compatible? end
Calls the present block of the Instrumentation
subclasses, if no block is provided it's assumed the instrumentation is not present
# File lib/opentelemetry/instrumentation/base.rb, line 226 def present? return false unless @present_blk instance_exec(&@present_blk) end
Private Instance Methods
The config_options
method is responsible for validating that the user supplied config hash is valid. Unknown configuration keys are not included in the final config hash. Invalid configuration values are logged, and replaced by the default.
@param [Hash] user_config The user supplied configuration hash
# File lib/opentelemetry/instrumentation/base.rb, line 260 def config_options(user_config) # rubocop:disable Metrics/AbcSize, Metrics/MethodLength @options ||= {} user_config ||= {} validated_config = @options.each_with_object({}) do |option, h| option_name = option[:name] config_value = user_config[option_name] value = if config_value.nil? option[:default] elsif option[:validate].call(config_value) config_value else OpenTelemetry.logger.warn( "Instrumentation #{name} configuration option #{option_name} value=#{config_value} " \ "failed validation, falling back to default value=#{option[:default]}" ) option[:default] end h[option_name] = value rescue StandardError => e OpenTelemetry.handle_error(exception: e, message: "Instrumentation #{name} unexpected configuration error") h[option_name] = option[:default] end dropped_config_keys = user_config.keys - validated_config.keys OpenTelemetry.logger.warn("Instrumentation #{name} ignored the following unknown configuration options #{dropped_config_keys}") unless dropped_config_keys.empty? validated_config end
Checks to see if this instrumentation is enabled by env var. By convention, the environment variable will be the instrumentation name upper cased, with '::' replaced by underscores, OPENTELEMETRY shortened to OTEL_{LANG} and _ENABLED appended. For example, the, environment variable name for OpenTelemetry::Instrumentation::Sinatra will be OTEL_RUBY_INSTRUMENTATION_SINATRA_ENABLED. A value of 'false' will disable the instrumentation, all other values will enable it.
# File lib/opentelemetry/instrumentation/base.rb, line 297 def enabled_by_env_var? var_name = name.dup.tap do |n| n.upcase! n.gsub!('::', '_') n.gsub!('OPENTELEMETRY_', 'OTEL_RUBY_') n << '_ENABLED' end ENV[var_name] != 'false' end