Class: Datadog::Core::Configuration::Option

Inherits:
Object
  • Object
show all
Defined in:
lib/datadog/core/configuration/option.rb,
sig/datadog/core/configuration/option.rbs

Overview

Represents an instance of an integration configuration option

Constant Summary collapse

UNSET =

Anchor object that represents a value that is not set. This is necessary because nil is a valid value to be set.

Object.new

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(definition, context) ⇒ Option

Returns a new instance of Option.

Parameters:



66
67
68
69
70
71
72
73
74
75
76
77
78
# File 'lib/datadog/core/configuration/option.rb', line 66

def initialize(definition, context)
  @definition = definition
  @context = context
  @value = nil
  @is_set = false

  # One value is stored per precedence, to allow unsetting a higher
  # precedence value and falling back to a lower precedence one.
  @value_per_precedence = Hash.new(UNSET)

  # Lowest precedence, to allow for `#set` to always succeed for a brand new `Option` instance.
  @precedence_set = Precedence::DEFAULT
end

Instance Attribute Details

#definitionConfiguration::OptionDefinition (readonly)

The definition object that matches this option.



18
19
20
# File 'lib/datadog/core/configuration/option.rb', line 18

def definition
  @definition
end

#precedence_setObject (readonly)

Returns the value of attribute precedence_set.



18
# File 'lib/datadog/core/configuration/option.rb', line 18

attr_reader :definition, :precedence_set

Instance Method Details

#coerce_env_variable(value) ⇒ env_value

Parameters:

  • value (String)

Returns:

  • (env_value)


208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
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/datadog/core/configuration/option.rb', line 208

def coerce_env_variable(value)
  env_parser = @definition.env_parser
  return context_exec(value, &env_parser) if env_parser

  case @definition.type
  when :hash
    values = value.split(',') # By default we only want to support comma separated strings

    values.each_with_object({}) do |v, hash| # $ Hash[String, String]
      v.gsub!(/\A[\s,]*+|[\s,]*+\Z/, '')
      next if v.empty?

      pair = v.split(':', 2)
      hash[pair[0]] = pair[1]
    end
  when :int
    Integer(value, 10)
  when :float
    Float(value)
  when :array
    values = value.split(',')

    values.each_with_object([]) do |v, arr| # $ Array[String]
      v.gsub!(/\A[\s,]*+|[\s,]*+\Z/, '')
      next if v.empty?

      arr << v
    end
  when :bool
    string_value = value.strip
    string_value = string_value.downcase
    string_value == 'true' || string_value == '1'
  when :string, NilClass
    value
  else
    raise InvalidDefinitionError,
      "The option #{name_with_settings_path} is using an unsupported type option for env coercion `#{@definition.type}`"
  end
end

#context_eval(&block) ⇒ void

This method returns an undefined value.



326
327
328
# File 'lib/datadog/core/configuration/option.rb', line 326

def context_eval(&block)
  @context.instance_eval(&block)
end

#context_exec(*args, &block) ⇒ void

This method returns an undefined value.



322
323
324
# File 'lib/datadog/core/configuration/option.rb', line 322

def context_exec(*args, &block)
  @context.instance_exec(*args, &block)
end

#default_precedence?Boolean

Returns:

  • (Boolean)


187
188
189
# File 'lib/datadog/core/configuration/option.rb', line 187

def default_precedence?
  precedence_set == Precedence::DEFAULT
end

#default_valueOptionDefinition::generic_proc, OptionDefinition::option_type

Returns:

  • (OptionDefinition::generic_proc, OptionDefinition::option_type)


178
179
180
181
182
183
184
185
# File 'lib/datadog/core/configuration/option.rb', line 178

def default_value
  if definition.default.instance_of?(Proc)
    # Steep: https://github.com/soutaro/steep/issues/335
    context_eval(&definition.default) # steep:ignore BlockTypeMismatch
  else
    definition.default_proc || Core::Utils::SafeDup.frozen_or_dup(definition.default)
  end
end

#getObject?

Returns:

  • (Object, nil)


147
148
149
150
151
152
153
154
155
156
157
158
159
# File 'lib/datadog/core/configuration/option.rb', line 147

def get
  unless @is_set
    # Ensures that both the default value and the environment value are set.
    # This approach handles scenarios where an environment value is unset
    # by falling back to the default value consistently.
    set_default_value
    set_customer_stable_config_value
    set_env_value
    set_fleet_stable_config_value
  end

  @value
end

#get_value_from(source_env, source_name) ⇒ env_value

Parameters:

  • env_vars (env_vars)
  • source_name (String)

Returns:

  • (env_value)


370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
# File 'lib/datadog/core/configuration/option.rb', line 370

def get_value_from(source_env, source_name)
  env = definition.env
  return unless env

  # An instance of ConfigHelper could be used with any Hash but this is the only place where
  # it's used with something else than ENV, let's keep it simple for now by overriding the source_env parameter.
  value = DATADOG_ENV.get_environment_variable(env, source_env: source_env)
  coerce_env_variable(value) unless value.nil?
rescue ArgumentError
  # This will be raised when the type is set to :int or :float but an invalid env var value is provided.
  raise ArgumentError,
    # ArgumentError will be thrown from coerce_env_variable, so we've already checked that env is not nil.
    # @type var env: String
    "Expected #{source_name} configuration file variable #{DATADOG_ENV.resolve_env(env, source_env: source_env)} " \
    "to be a #{definition.type}, but '#{value}' was provided."
end

#get_value_from_envenv_value

Returns:

  • (env_value)


355
356
357
358
359
360
361
362
363
364
365
366
367
368
# File 'lib/datadog/core/configuration/option.rb', line 355

def get_value_from_env
  env = definition.env
  return unless env

  value = DATADOG_ENV[env]
  coerce_env_variable(value) unless value.nil?
rescue ArgumentError
  # This will be raised when the type is set to :int or :float but an invalid env var value is provided.
  raise ArgumentError,
    # ArgumentError will be thrown from coerce_env_variable, so we've already checked that env is not nil.
    # @type var env: String
    "Expected environment variable #{DATADOG_ENV.resolve_env(env)} " \
    "to be a #{definition.type}, but '#{value}' was provided."
end

#internal_set(value, precedence) ⇒ Object

Directly manipulates the current value and currently set precedence.



309
310
311
312
313
314
315
316
317
318
319
320
# File 'lib/datadog/core/configuration/option.rb', line 309

def internal_set(value, precedence)
  old_value = @value
  (@value = context_exec(validate_type(value), old_value, &definition.setter)).tap do |v|
    @is_set = true
    @precedence_set = precedence
    # Store original value to ensure we can always safely call `#internal_set`
    # when restoring a value from `@value_per_precedence`, and we are only running `definition.setter`
    # on the original value, not on a value that has already been processed by `definition.setter`.
    @value_per_precedence[precedence] = value
    context_exec(v, old_value, precedence, &definition.after_set) if definition.after_set
  end
end

#name_with_settings_pathString

Computes the name of the option with the settings path. E.g. "tracing.rails.middleware_names" for the "middleware_names" option in the "tracing.rails" settings.

Returns:

  • (String)

    the name of the option with the settings path



83
84
85
86
87
88
# File 'lib/datadog/core/configuration/option.rb', line 83

def name_with_settings_path
  @name_with_settings_path ||= begin
    settings_path = @context.class.settings_path
    settings_path.nil? ? definition.name.to_s : "#{settings_path}.#{definition.name}"
  end
end

#resetvoid

This method returns an undefined value.



161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
# File 'lib/datadog/core/configuration/option.rb', line 161

def reset
  @value = if definition.resetter
    # Don't change @is_set to false; custom resetters are
    # responsible for changing @value back to a good state.
    # Setting @is_set = false would cause a default to be applied.
    context_exec(@value, &definition.resetter)
  else
    @is_set = false
    nil
  end

  # Reset back to the lowest precedence, to allow all `set`s to succeed right after a reset.
  @precedence_set = Precedence::DEFAULT
  # Reset all stored values
  @value_per_precedence = Hash.new(UNSET)
end

#set(value, precedence: Precedence::PROGRAMMATIC) ⇒ Object

Overrides the current value for this option if the precedence is equal or higher than the previously set value. The first call to #set will always store the value regardless of precedence.

Parameters:

  • value (Object)

    the new value to be associated with this option

  • precedence (Precedence) (defaults to: Precedence::PROGRAMMATIC)

    from what precedence order this new value comes from



96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
# File 'lib/datadog/core/configuration/option.rb', line 96

def set(value, precedence: Precedence::PROGRAMMATIC)
  # Is there a higher precedence value set?
  if @precedence_set > precedence
    # This should be uncommon, as higher precedence values tend to
    # happen later in the application lifecycle.
    Datadog.logger.info do
      "Option '#{name_with_settings_path}' not changed to '#{value}' (precedence: #{precedence.name}) because the higher " \
        "precedence value '#{@value}' (precedence: #{@precedence_set.name}) was already set."
    end

    # But if it happens, we have to store the lower precedence value `value`
    # because it's possible to revert to it by `#unset`ting
    # the existing, higher-precedence value.
    # Effectively, we always store one value pre precedence.
    @value_per_precedence[precedence] = value

    return @value
  end

  internal_set(value, precedence)
end

#set_customer_stable_config_valuevoid

This method returns an undefined value.



339
340
341
342
343
344
345
# File 'lib/datadog/core/configuration/option.rb', line 339

def set_customer_stable_config_value
  customer_config = StableConfig.configuration.dig(:local, :config)
  return if customer_config.nil?

  value = get_value_from(customer_config, 'local')
  set(value, precedence: Precedence::LOCAL_STABLE) unless value.nil?
end

#set_default_valuevoid

This method returns an undefined value.



330
331
332
# File 'lib/datadog/core/configuration/option.rb', line 330

def set_default_value
  set(default_value, precedence: Precedence::DEFAULT)
end

#set_env_valuevoid

This method returns an undefined value.



334
335
336
337
# File 'lib/datadog/core/configuration/option.rb', line 334

def set_env_value
  value = get_value_from_env
  set(value, precedence: Precedence::ENVIRONMENT) unless value.nil?
end

#set_fleet_stable_config_valuevoid

This method returns an undefined value.



347
348
349
350
351
352
353
# File 'lib/datadog/core/configuration/option.rb', line 347

def set_fleet_stable_config_value
  fleet_config = StableConfig.configuration.dig(:fleet, :config)
  return if fleet_config.nil?

  value = get_value_from(fleet_config, 'fleet')
  set(value, precedence: Precedence::FLEET_STABLE) unless value.nil?
end

#settings?Boolean

Returns:

  • (Boolean)


191
192
193
# File 'lib/datadog/core/configuration/option.rb', line 191

def settings?
  @definition.is_settings
end

#unset(precedence) ⇒ void

This method returns an undefined value.

Parameters:

  • precedence (Precedence::Value)


118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
# File 'lib/datadog/core/configuration/option.rb', line 118

def unset(precedence)
  @value_per_precedence[precedence] = UNSET

  # If we are unsetting the currently active value, we have to restore
  # a lower precedence one...
  if precedence == @precedence_set
    # Find a lower precedence value that is already set.
    Precedence::LIST.each do |p|
      # DEV: This search can be optimized, but the list is small, and unset is
      # DEV: only called from direct user interaction in the Datadog UI.
      next unless p < precedence

      # Look for value that is set.
      # The hash `@value_per_precedence` has a custom default value of `UNSET`.
      if (value = @value_per_precedence[p]) != UNSET
        internal_set(value, p)
        return nil
      end
    end

    # If no value is left to fall back on, reset this option
    reset
  end

  # ... otherwise, we are either unsetting a higher precedence value that is not
  # yet set, thus there's nothing to do; or we are unsetting a lower precedence
  # value, which also does not change the current value.
end

#validate(type, value) ⇒ Boolean

Parameters:

  • type (Symbol, nil)
  • value (option_value)

Returns:

  • (Boolean)


282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
# File 'lib/datadog/core/configuration/option.rb', line 282

def validate(type, value)
  case type
  when :string
    value.is_a?(String)
  when :int
    value.is_a?(Integer)
  when :float
    value.is_a?(Float) || value.is_a?(Integer) || value.is_a?(Rational)
  when :array
    value.is_a?(Array)
  when :hash
    value.is_a?(Hash)
  when :bool
    value.is_a?(TrueClass) || value.is_a?(FalseClass)
  when :proc
    value.is_a?(Proc)
  when :symbol
    value.is_a?(Symbol)
  when NilClass
    true # No validation is performed when option is typeless
  else
    raise InvalidDefinitionError,
      "The option #{name_with_settings_path} is using an unsupported type option `#{@definition.type}`"
  end
end

#validate_type(value) ⇒ option_value

Parameters:

  • value (option_value)

Returns:

  • (option_value)


252
253
254
255
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
# File 'lib/datadog/core/configuration/option.rb', line 252

def validate_type(value)
  raise_error = false

  valid_type = validate(@definition.type, value)

  unless valid_type
    raise_error = if @definition.type_options[:nilable]
      !value.is_a?(NilClass)
    else
      true
    end
  end

  if raise_error
    error_msg = if @definition.type_options[:nilable]
      "The setting `#{name_with_settings_path}` inside your app's `Datadog.configure` block expects a " \
      "#{@definition.type} or `nil`, but a `#{value.class}` was provided (#{value.inspect})." \
    else
      "The setting `#{name_with_settings_path}` inside your app's `Datadog.configure` block expects a " \
      "#{@definition.type}, but a `#{value.class}` was provided (#{value.inspect})." \
    end

    error_msg = "#{error_msg} Please update your `configure` block. "

    raise ArgumentError, error_msg
  end

  value
end

#values_per_precedence::Hash[Precedence::Value, option_value]

Returns:

  • (::Hash[Precedence::Value, option_value])


195
196
197
198
199
200
201
202
203
204
# File 'lib/datadog/core/configuration/option.rb', line 195

def values_per_precedence
  # value_per_precedence is only filled after we call `get` once.
  get unless @is_set

  @value_per_precedence.each_with_object({}) do |(precedence, value), result|
    next if value.equal?(UNSET)

    result[precedence] = value
  end
end