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

compatible_blk[R]
install_blk[R]
options[R]
present_blk[R]
config[R]
installed[R]
installed?[R]
name[R]
tracer[R]
version[R]

Public Class Methods

compatible(&blk) click to toggle source

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
inherited(subclass) click to toggle source
# File lib/opentelemetry/instrumentation/base.rb, line 77
def inherited(subclass)
  OpenTelemetry::Instrumentation.registry.register(subclass)
end
install(&blk) click to toggle source

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
instance() click to toggle source
# File lib/opentelemetry/instrumentation/base.rb, line 156
def instance
  @instance ||= new(instrumentation_name, instrumentation_version, install_blk,
                    present_blk, compatible_blk, options)
end
instrumentation_name(instrumentation_name = nil) click to toggle source

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
instrumentation_version(instrumentation_version = nil) click to toggle source

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
new(name, version, install_blk, present_blk, compatible_blk, options) click to toggle source
# 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
option(name, default:, validate:) click to toggle source

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
present(&blk) click to toggle source

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

infer_name() click to toggle source
# 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
infer_version() click to toggle source
# 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

compatible?() click to toggle source

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
enabled?(config = nil) click to toggle source

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(config = {}) click to toggle source

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
installable?(config = {}) click to toggle source

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
present?() click to toggle source

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

config_options(user_config) click to toggle source

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
enabled_by_env_var?() click to toggle source

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