Module: Dash0::OpenTelemetry::SdkConfiguration

Defined in:
lib/dash0/opentelemetry/sdk_configuration.rb

Overview

Configures the OpenTelemetry SDK with Dash0 defaults. Leans on the Ruby SDK's env-driven configurator: once the endpoint/protocol defaults are set and the exporter gems are required, OpenTelemetry::SDK.configure wires up the OTLP exporters and processors from the environment. We own the resource. Traces, metrics, and logs are all exported over OTLP/HTTP-protobuf to the same collector base URL (/v1/traces, /v1/metrics, /v1/logs).

Metrics and logs support activates simply by requiring their SDK gems: they prepend ConfiguratorPatch modules that fill in the otherwise-empty metrics/logs configuration hooks the base SDK calls during configure.

Constant Summary collapse

DEFAULT_PROTOCOL =

OTLP is only supported over HTTP/protobuf in the Ruby SDK's default path.

'http/protobuf'
DEBUG_PRINT_SPANS_ENV =

DASH0_DEBUG_PRINT_SPANS=true additionally prints every span to stdout.

'DASH0_DEBUG_PRINT_SPANS'
ADDITIONAL_GEM_PATH_ENV =

When the operator/injector mounts a Bundler-less gem bundle, its path is exposed here and the distribution is responsible for putting the bundled gems on the load path itself.

'OTEL_RUBY_ADDITIONAL_GEM_PATH'
DISALLOWED_LIB_PATH_ENV =

Comma-separated allowlist gems to NOT put on the load path (see #disallowed_lib_names). Mirrors the upstream env var.

'DISALLOWED_LIB_PATH'
ADDITIONAL_LIB_GEM_ALLOWLIST =

Non-opentelemetry-* gems from the bundle that the SDK closure requires at load time and must therefore be on the load path: the OTLP exporter's protobuf deps, plus logger (required by opentelemetry-api; a default gem today, but a bundled gem from Ruby 4.0, at which point requiring it without this entry would fail). Kept minimal to avoid shadowing the app's own gems.

%w[googleapis-common-protos-types google-protobuf logger].freeze

Class Method Summary collapse

Class Method Details

.apply(base_url:) ⇒ Object

Parameters:

  • base_url (String)

    the Dash0 collector base URL. The generic OTEL_EXPORTER_OTLP_ENDPOINT is used (not a signal-specific variable), so each exporter appends its own /v1/{traces,metrics,logs} path.



46
47
48
49
50
51
52
53
54
55
56
# File 'lib/dash0/opentelemetry/sdk_configuration.rb', line 46

def apply(base_url:)
  configure_exporter_defaults(base_url)
  require_sdk_gems
  resource = build_resource

  ::OpenTelemetry::SDK.configure do |c|
    c.resource = resource
  end

  install_debug_span_printer if Environment.opted_in?(DEBUG_PRINT_SPANS_ENV)
end

.build_resourceObject

Builds the resource merged onto the SDK's default resource (process, SDK, and service-name-from-env attributes) by the configurator. Beyond the distro identity, this runs the upstream container detector plus the two custom Dash0 detectors. On key conflicts the later merge argument wins; distro attributes are applied last so they always take effect.



121
122
123
124
125
126
127
# File 'lib/dash0/opentelemetry/sdk_configuration.rb', line 121

def build_resource
  ::OpenTelemetry::SDK::Resources::Resource.create({})
                                           .merge(::OpenTelemetry::Resource::Detector::Container.detect)
                                           .merge(Resource::KubernetesPod.detect)
                                           .merge(Resource::ServiceNameFallback.detect)
                                           .merge(Resource::Distribution.detect)
end

.configure_exporter_defaults(base_url) ⇒ Object



58
59
60
61
# File 'lib/dash0/opentelemetry/sdk_configuration.rb', line 58

def configure_exporter_defaults(base_url)
  Environment.set_default('OTEL_EXPORTER_OTLP_ENDPOINT', base_url)
  Environment.set_default('OTEL_EXPORTER_OTLP_PROTOCOL', DEFAULT_PROTOCOL)
end

.disallowed_lib_namesObject

Gems opted out of via DISALLOWED_LIB_PATH (comma-separated).



94
95
96
# File 'lib/dash0/opentelemetry/sdk_configuration.rb', line 94

def disallowed_lib_names
  ENV.fetch(DISALLOWED_LIB_PATH_ENV, '').split(',').map(&:strip).reject(&:empty?)
end

.install_debug_span_printerObject

Adds a console span exporter after configure. It must not be added inside the configure block: the SDK configurator only builds the env-driven OTLP exporter when no span processor was registered manually, so adding one there would silently disable OTLP export. Appending to the already-built provider keeps OTLP and adds console output alongside it.



108
109
110
111
112
113
114
# File 'lib/dash0/opentelemetry/sdk_configuration.rb', line 108

def install_debug_span_printer
  ::OpenTelemetry.tracer_provider.add_span_processor(
    ::OpenTelemetry::SDK::Trace::Export::SimpleSpanProcessor.new(
      ::OpenTelemetry::SDK::Trace::Export::ConsoleSpanExporter.new
    )
  )
end

.on_load_path?(gem_dir_name, allowlist) ⇒ Boolean

Returns:

  • (Boolean)


98
99
100
101
# File 'lib/dash0/opentelemetry/sdk_configuration.rb', line 98

def on_load_path?(gem_dir_name, allowlist)
  gem_dir_name.start_with?('opentelemetry-') ||
    allowlist.any? { |name| gem_dir_name.start_with?("#{name}-") }
end

.require_sdk_gemsObject



63
64
65
66
67
68
69
70
71
72
73
74
# File 'lib/dash0/opentelemetry/sdk_configuration.rb', line 63

def require_sdk_gems
  wire_additional_gem_path
  require 'opentelemetry-sdk'
  require 'opentelemetry-metrics-sdk'
  require 'opentelemetry-logs-sdk'
  require 'opentelemetry-exporter-otlp'
  require 'opentelemetry-exporter-otlp-metrics'
  require 'opentelemetry-exporter-otlp-logs'
  require 'opentelemetry-resource-detector-container'
  # Populate the instrumentation registry so the installer can sweep it.
  require 'opentelemetry-instrumentation-all'
end

.wire_additional_gem_pathObject

In the injected (Bundler-less) environment the OpenTelemetry gems live only under OTEL_RUBY_ADDITIONAL_GEM_PATH; put their lib directories on the load path so the requires above resolve. A no-op when the variable is unset (e.g. running under Bundler, where the gems are already resolvable).



80
81
82
83
84
85
86
87
88
89
90
91
# File 'lib/dash0/opentelemetry/sdk_configuration.rb', line 80

def wire_additional_gem_path
  gem_path = ENV.fetch(ADDITIONAL_GEM_PATH_ENV, nil)
  return if gem_path.nil? || !Dir.exist?(gem_path)

  allowlist = ADDITIONAL_LIB_GEM_ALLOWLIST - disallowed_lib_names
  Dir.glob(File.join(gem_path, 'gems', '*')).each do |gem_dir|
    next unless on_load_path?(File.basename(gem_dir), allowlist)

    lib = File.join(gem_dir, 'lib')
    $LOAD_PATH.unshift(lib) if Dir.exist?(lib) && !$LOAD_PATH.include?(lib)
  end
end