datadog 2.30.0 → 2.31.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (137) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +44 -1
  3. data/ext/datadog_profiling_native_extension/datadog_ruby_common.c +18 -0
  4. data/ext/datadog_profiling_native_extension/datadog_ruby_common.h +10 -0
  5. data/ext/datadog_profiling_native_extension/extconf.rb +2 -0
  6. data/ext/libdatadog_api/crashtracker.c +5 -8
  7. data/ext/libdatadog_api/datadog_ruby_common.c +18 -0
  8. data/ext/libdatadog_api/datadog_ruby_common.h +10 -0
  9. data/ext/libdatadog_api/di.c +79 -0
  10. data/ext/libdatadog_api/extconf.rb +2 -0
  11. data/ext/libdatadog_api/init.c +5 -2
  12. data/ext/libdatadog_extconf_helpers.rb +9 -1
  13. data/lib/datadog/ai_guard/component.rb +2 -0
  14. data/lib/datadog/ai_guard/contrib/ruby_llm/chat_instrumentation.rb +41 -3
  15. data/lib/datadog/ai_guard/evaluation/content_builder.rb +31 -0
  16. data/lib/datadog/ai_guard/evaluation/content_part.rb +36 -0
  17. data/lib/datadog/ai_guard/evaluation/no_op_result.rb +3 -1
  18. data/lib/datadog/ai_guard/evaluation/request.rb +14 -9
  19. data/lib/datadog/ai_guard/evaluation/result.rb +3 -1
  20. data/lib/datadog/ai_guard/evaluation.rb +36 -7
  21. data/lib/datadog/ai_guard.rb +26 -8
  22. data/lib/datadog/appsec/autoload.rb +1 -1
  23. data/lib/datadog/appsec/component.rb +11 -7
  24. data/lib/datadog/appsec/contrib/rack/gateway/request.rb +1 -1
  25. data/lib/datadog/appsec/contrib/rack/gateway/watcher.rb +6 -7
  26. data/lib/datadog/appsec/contrib/rails/patcher.rb +2 -2
  27. data/lib/datadog/appsec/instrumentation/gateway.rb +0 -13
  28. data/lib/datadog/appsec/monitor/gateway/watcher.rb +2 -0
  29. data/lib/datadog/appsec/utils/http/media_type.rb +1 -2
  30. data/lib/datadog/appsec/utils/http/url_encoded.rb +2 -2
  31. data/lib/datadog/appsec.rb +5 -9
  32. data/lib/datadog/core/configuration/base.rb +17 -5
  33. data/lib/datadog/core/configuration/components.rb +21 -8
  34. data/lib/datadog/core/configuration/config_helper.rb +9 -0
  35. data/lib/datadog/core/configuration/option.rb +30 -5
  36. data/lib/datadog/core/configuration/option_definition.rb +38 -12
  37. data/lib/datadog/core/configuration/options.rb +40 -6
  38. data/lib/datadog/core/configuration/settings.rb +15 -0
  39. data/lib/datadog/core/configuration/supported_configurations.rb +1 -0
  40. data/lib/datadog/core/contrib/rails/railtie.rb +32 -0
  41. data/lib/datadog/core/contrib/rails/utils.rb +7 -3
  42. data/lib/datadog/core/crashtracking/component.rb +3 -3
  43. data/lib/datadog/core/environment/container.rb +2 -2
  44. data/lib/datadog/core/environment/ext.rb +1 -0
  45. data/lib/datadog/core/environment/identity.rb +25 -3
  46. data/lib/datadog/core/environment/process.rb +12 -0
  47. data/lib/datadog/core/metrics/client.rb +5 -5
  48. data/lib/datadog/core/remote/component.rb +38 -21
  49. data/lib/datadog/core/runtime/metrics.rb +1 -1
  50. data/lib/datadog/core/telemetry/component.rb +3 -0
  51. data/lib/datadog/core/telemetry/event/app_client_configuration_change.rb +2 -3
  52. data/lib/datadog/core/telemetry/event/app_extended_heartbeat.rb +32 -0
  53. data/lib/datadog/core/telemetry/event/app_started.rb +151 -169
  54. data/lib/datadog/core/telemetry/event.rb +1 -7
  55. data/lib/datadog/core/telemetry/ext.rb +1 -0
  56. data/lib/datadog/core/telemetry/transport/http/telemetry.rb +5 -0
  57. data/lib/datadog/core/telemetry/worker.rb +20 -0
  58. data/lib/datadog/core/utils/only_once.rb +1 -1
  59. data/lib/datadog/core/utils/spawn_monkey_patch.rb +36 -0
  60. data/lib/datadog/core/workers/async.rb +1 -1
  61. data/lib/datadog/core.rb +0 -1
  62. data/lib/datadog/data_streams/pathway_context.rb +1 -1
  63. data/lib/datadog/di/boot.rb +2 -4
  64. data/lib/datadog/di/component.rb +4 -0
  65. data/lib/datadog/di/instrumenter.rb +10 -4
  66. data/lib/datadog/di/probe_notification_builder.rb +109 -1
  67. data/lib/datadog/di/serializer.rb +1 -1
  68. data/lib/datadog/di.rb +81 -0
  69. data/lib/datadog/kit/enable_core_dumps.rb +1 -1
  70. data/lib/datadog/open_feature/evaluation_engine.rb +1 -1
  71. data/lib/datadog/open_feature/exposures/reporter.rb +1 -1
  72. data/lib/datadog/open_feature/exposures/worker.rb +1 -1
  73. data/lib/datadog/open_feature/remote.rb +1 -1
  74. data/lib/datadog/open_feature/transport.rb +1 -1
  75. data/lib/datadog/opentelemetry/configuration/settings.rb +2 -0
  76. data/lib/datadog/profiling/collectors/code_provenance.rb +2 -3
  77. data/lib/datadog/profiling/collectors/cpu_and_wall_time_worker.rb +1 -1
  78. data/lib/datadog/profiling/collectors/idle_sampling_helper.rb +1 -1
  79. data/lib/datadog/profiling/component.rb +11 -1
  80. data/lib/datadog/profiling/load_native_extension.rb +1 -1
  81. data/lib/datadog/profiling/profiler.rb +0 -4
  82. data/lib/datadog/profiling/scheduler.rb +2 -2
  83. data/lib/datadog/profiling/tasks/exec.rb +2 -2
  84. data/lib/datadog/profiling/tasks/setup.rb +2 -2
  85. data/lib/datadog/profiling.rb +1 -2
  86. data/lib/datadog/single_step_instrument.rb +1 -1
  87. data/lib/datadog/tracing/buffer.rb +3 -3
  88. data/lib/datadog/tracing/component.rb +11 -0
  89. data/lib/datadog/tracing/configuration/settings.rb +2 -1
  90. data/lib/datadog/tracing/contrib/action_pack/action_controller/instrumentation.rb +2 -2
  91. data/lib/datadog/tracing/contrib/action_pack/action_dispatch/instrumentation.rb +20 -0
  92. data/lib/datadog/tracing/contrib/action_pack/action_dispatch/patcher.rb +3 -1
  93. data/lib/datadog/tracing/contrib/action_view/events/render_template.rb +1 -1
  94. data/lib/datadog/tracing/contrib/active_job/events/discard.rb +1 -1
  95. data/lib/datadog/tracing/contrib/active_job/events/enqueue.rb +1 -1
  96. data/lib/datadog/tracing/contrib/active_job/events/enqueue_at.rb +1 -1
  97. data/lib/datadog/tracing/contrib/active_job/events/enqueue_retry.rb +1 -1
  98. data/lib/datadog/tracing/contrib/active_job/events/perform.rb +1 -1
  99. data/lib/datadog/tracing/contrib/active_job/events/retry_stopped.rb +1 -1
  100. data/lib/datadog/tracing/contrib/active_model_serializers/events/render.rb +1 -1
  101. data/lib/datadog/tracing/contrib/active_model_serializers/events/serialize.rb +1 -1
  102. data/lib/datadog/tracing/contrib/active_record/events/instantiation.rb +1 -1
  103. data/lib/datadog/tracing/contrib/active_record/events/sql.rb +1 -1
  104. data/lib/datadog/tracing/contrib/active_record/utils.rb +1 -1
  105. data/lib/datadog/tracing/contrib/active_support/cache/events/cache.rb +1 -1
  106. data/lib/datadog/tracing/contrib/active_support/notifications/subscription.rb +2 -2
  107. data/lib/datadog/tracing/contrib/aws/instrumentation.rb +1 -1
  108. data/lib/datadog/tracing/contrib/configurable.rb +18 -3
  109. data/lib/datadog/tracing/contrib/elasticsearch/patcher.rb +1 -1
  110. data/lib/datadog/tracing/contrib/excon/middleware.rb +2 -2
  111. data/lib/datadog/tracing/contrib/faraday/middleware.rb +2 -2
  112. data/lib/datadog/tracing/contrib/grape/endpoint.rb +5 -5
  113. data/lib/datadog/tracing/contrib/http/instrumentation.rb +1 -1
  114. data/lib/datadog/tracing/contrib/httprb/instrumentation.rb +1 -1
  115. data/lib/datadog/tracing/contrib/opensearch/patcher.rb +1 -1
  116. data/lib/datadog/tracing/contrib/rails/log_injection.rb +1 -1
  117. data/lib/datadog/tracing/contrib/rails/patcher.rb +0 -1
  118. data/lib/datadog/tracing/contrib/rails/runner.rb +1 -1
  119. data/lib/datadog/tracing/contrib/rake/instrumentation.rb +2 -2
  120. data/lib/datadog/tracing/contrib/redis/tags.rb +1 -1
  121. data/lib/datadog/tracing/contrib/status_range_matcher.rb +4 -0
  122. data/lib/datadog/tracing/contrib/stripe/request.rb +1 -1
  123. data/lib/datadog/tracing/distributed/datadog.rb +4 -2
  124. data/lib/datadog/tracing/event.rb +1 -1
  125. data/lib/datadog/tracing/remote.rb +1 -1
  126. data/lib/datadog/tracing/sampling/ext.rb +2 -0
  127. data/lib/datadog/tracing/sampling/priority_sampler.rb +13 -0
  128. data/lib/datadog/tracing/sampling/rule.rb +1 -1
  129. data/lib/datadog/tracing/sampling/rule_sampler.rb +54 -25
  130. data/lib/datadog/tracing/sampling/span/rule_parser.rb +1 -1
  131. data/lib/datadog/tracing/span_operation.rb +1 -1
  132. data/lib/datadog/tracing/trace_operation.rb +50 -6
  133. data/lib/datadog/tracing/tracer.rb +25 -0
  134. data/lib/datadog/tracing/transport/io/client.rb +1 -1
  135. data/lib/datadog/tracing/transport/trace_formatter.rb +1 -1
  136. data/lib/datadog/version.rb +1 -1
  137. metadata +13 -7
@@ -10,7 +10,7 @@ module Datadog
10
10
  module AIGuard
11
11
  Core::Configuration::Settings.extend(Configuration::Settings)
12
12
 
13
- # This error is raised when user passes `allow_raise: true` to Evaluation.perform
13
+ # This error is raised when `allow_raise` is set to true (the default) in Evaluation.perform
14
14
  # and AI Guard considers the messages not safe. Intended to be rescued by the user.
15
15
  #
16
16
  # WARNING: This name must not change, since front-end is using it.
@@ -68,13 +68,14 @@ module Datadog
68
68
  # One or more message objects to be evaluated.
69
69
  # @param allow_raise [Boolean]
70
70
  # Whether this method may raise an exception when evaluation result is not ALLOW.
71
+ # Defaults to true.
71
72
  #
72
73
  # @return [Datadog::AIGuard::Evaluation::Result]
73
74
  # The result of AI Guard evaluation.
74
75
  # @raise [Datadog::AIGuard::AIGuardAbortError]
75
76
  # If the evaluation results in DENY or ABORT action and `allow_raise` is set to true
76
77
  # @public_api
77
- def evaluate(*messages, allow_raise: false)
78
+ def evaluate(*messages, allow_raise: true)
78
79
  if enabled?
79
80
  Evaluation.perform(messages, allow_raise: allow_raise)
80
81
  else
@@ -84,25 +85,42 @@ module Datadog
84
85
 
85
86
  # Builds a generic evaluation message.
86
87
  #
87
- # Example:
88
+ # Accepts either a string content or a block for multi-modal content parts:
88
89
  #
89
90
  # ```
91
+ # # String content:
90
92
  # Datadog::AIGuard.message(role: :user, content: "Hello, assistant")
93
+ #
94
+ # # Multi-modal content with block:
95
+ # Datadog::AIGuard.message(role: :user) do |m|
96
+ # m.text("What's in this image?")
97
+ # m.image_url("https://example.com/img.png")
98
+ # end
91
99
  # ```
92
100
  #
93
101
  # @param role [Symbol]
94
102
  # The role associated with the message.
95
103
  # Must be one of `:assistant`, `:tool`, `:system`, `:developer`, or `:user`.
96
- # @param content [String]
97
- # The textual content of the message.
104
+ # @param content [String, nil]
105
+ # The textual content of the message. Cannot be combined with a block.
106
+ # @yield [builder] A block for building multi-modal content parts.
107
+ # @yieldparam builder [Datadog::AIGuard::Evaluation::ContentBuilder]
98
108
  #
99
109
  # @return [Datadog::AIGuard::Evaluation::Message]
100
110
  # A new message instance with the given role and content.
101
111
  # @raise [ArgumentError]
102
- # If an invalid role is provided.
112
+ # If both content and a block are provided, or if an invalid role is provided.
103
113
  # @public_api
104
- def message(role:, content:)
105
- Evaluation::Message.new(role: role, content: content)
114
+ def message(role:, content: nil)
115
+ if block_given?
116
+ raise ArgumentError, "Cannot pass both content and a block" if content
117
+
118
+ builder = Evaluation::ContentBuilder.new
119
+ yield builder
120
+ Evaluation::Message.new(role: role, content: builder.parts)
121
+ else
122
+ Evaluation::Message.new(role: role, content: content)
123
+ end
106
124
  end
107
125
 
108
126
  # Builds an assistant message representing a tool call initiated by the model.
@@ -7,7 +7,7 @@ if %w[1 true].include?((Datadog::DATADOG_ENV['DD_APPSEC_ENABLED'] || '').downcas
7
7
  rescue => e
8
8
  Kernel.warn(
9
9
  '[datadog] AppSec failed to instrument. No security check will be performed. error: ' \
10
- " #{e.class.name} #{e.message}"
10
+ " #{e.class}: #{e}"
11
11
  )
12
12
  end
13
13
  end
@@ -45,10 +45,15 @@ module Datadog
45
45
  settings.appsec.instrument(:devise) unless devise_integration.patcher.patched?
46
46
 
47
47
  security_engine = SecurityEngine::Engine.new(appsec_settings: settings.appsec, telemetry: telemetry)
48
- new(security_engine: security_engine, telemetry: telemetry)
49
- rescue
50
- Datadog.logger.warn('AppSec is disabled, see logged errors above')
51
-
48
+ new(security_engine: security_engine)
49
+ rescue => e
50
+ Datadog.logger.warn("AppSec is disabled: #{e.class}: #{e}; there may be additional logged errors above")
51
+
52
+ # Not reporting to telemetry here because some of the rescued exceptions
53
+ # have already been reported by the code that raised them
54
+ # (e.g. SecurityEngine::Engine.new reports WAF init failures).
55
+ # TODO: reconsider whether telemetry reporting belongs here
56
+ # (single catch-all) or in the downstream code (as it is now).
52
57
  nil
53
58
  end
54
59
 
@@ -70,11 +75,10 @@ module Datadog
70
75
  end
71
76
  end
72
77
 
73
- attr_reader :security_engine, :telemetry
78
+ attr_reader :security_engine
74
79
 
75
- def initialize(security_engine:, telemetry:)
80
+ def initialize(security_engine:)
76
81
  @security_engine = security_engine
77
- @telemetry = telemetry
78
82
  end
79
83
 
80
84
  def reconfigure!
@@ -25,7 +25,7 @@ module Datadog
25
25
  def query
26
26
  ::Rack::Utils.parse_query(request.query_string)
27
27
  rescue => e
28
- Datadog.logger.debug { "AppSec: Failed to parse request query string: #{e.class}: #{e.message}" }
28
+ Datadog.logger.debug { "AppSec: Failed to parse request query string: #{e.class}: #{e}" }
29
29
  AppSec.telemetry.report(e, description: 'AppSec: Failed to parse request query string')
30
30
 
31
31
  {}
@@ -116,17 +116,16 @@ module Datadog
116
116
  gateway.watch('rack.request.finish') do |stack, gateway_request|
117
117
  context = gateway_request.env[AppSec::Ext::CONTEXT_KEY]
118
118
 
119
- if context.span.nil? || !gateway.pushed?('appsec.events.user_lifecycle')
120
- next stack.call(gateway_request.request)
121
- end
119
+ next stack.call(gateway_request.request) if context.span.nil?
122
120
 
123
121
  gateway_request.headers.each do |name, value|
124
- if !Ext::COLLECTABLE_REQUEST_HEADERS.include?(name) &&
125
- !Ext::IDENTITY_COLLECTABLE_REQUEST_HEADERS.include?(name)
126
- next
122
+ if Ext::COLLECTABLE_REQUEST_HEADERS.include?(name)
123
+ context.span["http.request.headers.#{name}"] ||= value
127
124
  end
128
125
 
129
- context.span["http.request.headers.#{name}"] ||= value
126
+ if context.state[:has_identity_event] && Ext::IDENTITY_COLLECTABLE_REQUEST_HEADERS.include?(name)
127
+ context.span["http.request.headers.#{name}"] ||= value
128
+ end
130
129
  end
131
130
 
132
131
  stack.call(gateway_request.request)
@@ -138,7 +138,7 @@ module Datadog
138
138
  end
139
139
  rescue => e
140
140
  error_message = 'Failed to get application routes'
141
- Datadog.logger.error("#{error_message}, #{e.class}: #{e.message}")
141
+ Datadog.logger.error("#{error_message}, #{e.class}: #{e}")
142
142
  AppSec.telemetry.report(e, description: error_message)
143
143
  end
144
144
  end
@@ -148,7 +148,7 @@ module Datadog
148
148
  Datadog::AppSec::Contrib::Rails::Patcher.report_routes_via_telemetry(::Rails.application.routes.routes)
149
149
  rescue => e
150
150
  error_message = 'Failed to get application routes'
151
- Datadog.logger.error("#{error_message}, #{e.class}: #{e.message}")
151
+ Datadog.logger.error("#{error_message}, #{e.class}: #{e}")
152
152
  AppSec.telemetry.report(e, description: error_message)
153
153
  end
154
154
  end
@@ -10,18 +10,9 @@ module Datadog
10
10
  class Gateway
11
11
  def initialize
12
12
  @middlewares = Hash.new { |h, k| h[k] = [] }
13
- @pushed_events = {}
14
13
  end
15
14
 
16
- # NOTE: Be careful with pushed names because every pushed event name
17
- # is recorded in order to provide an ability to any subscriber
18
- # to check wether an arbitrary event had happened.
19
- #
20
- # WARNING: If we start pushing generated names we should consider
21
- # limiting the storage of pushed names.
22
15
  def push(name, env, &block)
23
- @pushed_events[name] = true
24
-
25
16
  block ||= -> {}
26
17
  middlewares_for_name = @middlewares[name]
27
18
 
@@ -44,10 +35,6 @@ module Datadog
44
35
  def watch(name, &block)
45
36
  @middlewares[name] << Middleware.new(&block)
46
37
  end
47
-
48
- def pushed?(name)
49
- @pushed_events.key?(name)
50
- end
51
38
  end
52
39
 
53
40
  # NOTE: This left as-is and will be depricated soon.
@@ -26,6 +26,7 @@ module Datadog
26
26
  def watch_user_id(gateway = Instrumentation.gateway)
27
27
  gateway.watch('identity.set_user') do |stack, user|
28
28
  context = AppSec.active_context
29
+ context.state[:has_identity_event] = true
29
30
 
30
31
  if user.id.nil? && user.login.nil? && user.session_id.nil?
31
32
  Datadog.logger.debug { 'AppSec: skipping WAF check because no user information was provided' }
@@ -57,6 +58,7 @@ module Datadog
57
58
  def watch_user_login(gateway = Instrumentation.gateway)
58
59
  gateway.watch('appsec.events.user_lifecycle') do |stack, kind|
59
60
  context = AppSec.active_context
61
+ context.state[:has_identity_event] = true
60
62
 
61
63
  next stack.call(kind) unless WATCHED_LOGIN_EVENTS.include?(kind)
62
64
 
@@ -68,8 +68,7 @@ module Datadog
68
68
  name.downcase! # steep:ignore NoMethod
69
69
  value.downcase!
70
70
 
71
- # See https://github.com/soutaro/steep/issues/2051
72
- parameters[name] = value # steep:ignore ArgumentTypeMismatch
71
+ parameters[name] = value
73
72
  end
74
73
  end
75
74
 
@@ -30,9 +30,9 @@ module Datadog
30
30
  #
31
31
  # @type var key: ::String
32
32
  # @type var value: ::String
33
- key, value = pair.split('=', 2).map! do |value| #: ::String
33
+ key, value = pair.split('=', 2).map! do |value|
34
34
  CGI.unescape(value)
35
- end
35
+ end #: [::String, ::String]
36
36
 
37
37
  if (stored = memo[key])
38
38
  if stored.is_a?(Array)
@@ -11,25 +11,21 @@ module Datadog
11
11
  module AppSec
12
12
  class << self
13
13
  def enabled?
14
- Datadog.configuration.appsec.enabled
14
+ !!components.appsec
15
15
  end
16
16
 
17
17
  def rasp_enabled?
18
- Datadog.configuration.appsec.rasp_enabled
18
+ # TODO this should take rasp_enabled flag from the settings in
19
+ # the appsec component rather than reading global configuration.
20
+ enabled? && Datadog.configuration.appsec.rasp_enabled
19
21
  end
20
22
 
21
23
  def active_context
22
24
  Datadog::AppSec::Context.active
23
25
  end
24
26
 
25
- # NOTE: This is a temporary workaround for type checking.
26
- #
27
- # We want to move from possible nil-component to the disabled-component
28
- # on an initialization error. Technically, telemetry will be never
29
- # used if AppSec was not able to initialize, so it's safe to assume
30
- # that telemetry will never be used and will be nil at the same time.
31
27
  def telemetry
32
- components.appsec&.telemetry || components.telemetry
28
+ components.telemetry
33
29
  end
34
30
 
35
31
  def security_engine
@@ -24,9 +24,21 @@ module Datadog
24
24
  # e.g. `settings :foo { option :bar }` --> `config.foo.bar`
25
25
  # @param [Symbol] name option name. Methods will be created based on this name.
26
26
  def settings(name, &block)
27
- settings_class = new_settings_class(name, &block)
28
-
29
- option(name) do |o|
27
+ nested_settings_path = settings_path ? "#{settings_path}.#{name}" : name.to_s
28
+ settings_class = new_settings_class(name, nested_settings_path, &block)
29
+ # Record the child settings class on the owning class so
30
+ # Options::ClassMethods#settings_path= can later propagate any path
31
+ # changes down to nested settings classes.
32
+ #
33
+ # Example:
34
+ # settings :cache_key { option :enabled }
35
+ # creates a child class whose initial settings_path is "cache_key".
36
+ # If a contrib integration later assigns the parent path to
37
+ # "tracing.active_support", settings_children lets us update the
38
+ # nested class to "tracing.active_support.cache_key" as well.
39
+ settings_children[name] = settings_class
40
+
41
+ option(name, is_settings: true) do |o|
30
42
  o.default { settings_class.new }
31
43
 
32
44
  o.resetter do |value|
@@ -40,9 +52,9 @@ module Datadog
40
52
 
41
53
  private
42
54
 
43
- def new_settings_class(name, &block)
55
+ def new_settings_class(name, settings_path, &block)
44
56
  Class.new { include Configuration::Base }.tap do |klass|
45
- klass.instance_variable_set(:@settings_name, name)
57
+ klass.instance_variable_set(:@settings_path, settings_path)
46
58
  klass.instance_eval(&block) if block
47
59
  end
48
60
  end
@@ -12,6 +12,7 @@ require_relative '../telemetry/component'
12
12
  require_relative '../workers/runtime_metrics'
13
13
  require_relative '../remote/component'
14
14
  require_relative '../utils/at_fork_monkey_patch'
15
+ require_relative '../utils/spawn_monkey_patch'
15
16
  require_relative '../utils/only_once'
16
17
  require_relative '../../tracing/component'
17
18
  require_relative '../../profiling/component'
@@ -22,6 +23,7 @@ require_relative '../../open_feature/component'
22
23
  require_relative '../../error_tracking/component'
23
24
  require_relative '../crashtracking/component'
24
25
  require_relative '../environment/agent_info'
26
+ require_relative '../environment/identity'
25
27
  require_relative '../process_discovery'
26
28
  require_relative '../../data_streams/processor'
27
29
 
@@ -31,7 +33,7 @@ module Datadog
31
33
  # Global components for the trace library.
32
34
  class Components
33
35
  # Class-level constant to ensure fork patch is applied only once
34
- AT_FORK_ONLY_ONCE = Utils::OnlyOnce.new
36
+ PATCH_ONLY_ONCE = Utils::OnlyOnce.new
35
37
 
36
38
  class << self
37
39
  def build_health_metrics(settings, logger, telemetry)
@@ -128,8 +130,11 @@ module Datadog
128
130
  Deprecations.log_deprecations_from_all_sources(@logger)
129
131
 
130
132
  # Register fork handling once globally
131
- self.class::AT_FORK_ONLY_ONCE.run do
133
+ self.class::PATCH_ONLY_ONCE.run do
132
134
  Utils::AtForkMonkeyPatch.apply!
135
+ Utils::SpawnMonkeyPatch.apply!(
136
+ lineage_envs_provider: Core::Environment::Identity.method(:runtime_propagation_envs),
137
+ )
133
138
 
134
139
  # Register callback that calls Components.after_fork
135
140
  Utils::AtForkMonkeyPatch.at_fork(:child) do
@@ -172,6 +177,11 @@ module Datadog
172
177
 
173
178
  # Configure non-privileged components.
174
179
  Datadog::Tracing::Contrib::Component.configure(settings)
180
+
181
+ # Load the core Rails Railtie when Rails is present so all products benefit from Rails-specific setup.
182
+ if defined?(::Rails::Railtie)
183
+ require_relative '../contrib/rails/railtie'
184
+ end
175
185
  end
176
186
 
177
187
  # Called when a fork is detected
@@ -204,14 +214,13 @@ module Datadog
204
214
  end
205
215
  end
206
216
 
207
- if settings.remote.enabled && old_state&.remote_started?
217
+ if remote && old_state&.remote_started?
208
218
  # The library was reconfigured and previously it already started
209
219
  # the remote component (i.e., it received at least one request
210
220
  # through the installed Rack middleware which started the remote).
211
221
  # If the new configuration also has remote enabled, start the
212
222
  # new remote right away.
213
- # remote should always be not nil here but steep doesn't know this.
214
- remote&.start
223
+ remote.start
215
224
  end
216
225
 
217
226
  # This should stay here, not in initialize. During reconfiguration, the order of the calls is:
@@ -279,11 +288,15 @@ module Datadog
279
288
  unused_statsd = (old_statsd - (old_statsd & new_statsd))
280
289
  unused_statsd.each(&:close)
281
290
 
282
- # enqueue closing event before stopping telemetry so it will be sent out on shutdown
291
+ Core::ProcessDiscovery.shutdown!
292
+
293
+ # Shut down telemetry last so that all other components may
294
+ # report shutdown errors.
295
+ #
296
+ # Enqueue closing event before stopping telemetry so it will be
297
+ # sent out on shutdown.
283
298
  telemetry.emit_closing! unless replacement&.telemetry&.enabled
284
299
  telemetry.shutdown!
285
-
286
- Core::ProcessDiscovery.shutdown!
287
300
  end
288
301
 
289
302
  # Returns the current state of various components.
@@ -40,6 +40,12 @@ module Datadog
40
40
  !get_environment_variable(name).nil?
41
41
  end
42
42
 
43
+ # Returns the source environment as a Hash. Used when the full environment
44
+ # must be passed through (e.g. Process.spawn child env).
45
+ def to_h
46
+ @source_env.to_h
47
+ end
48
+
43
49
  alias_method :has_key?, :key?
44
50
  alias_method :include?, :key?
45
51
  alias_method :member?, :key?
@@ -97,4 +103,7 @@ module Datadog
97
103
  end
98
104
  end
99
105
  end
106
+ unless const_defined?(:DATADOG_ENV, false)
107
+ DATADOG_ENV = Core::Configuration::ConfigHelper.new
108
+ end
100
109
  end
@@ -77,6 +77,16 @@ module Datadog
77
77
  @precedence_set = Precedence::DEFAULT
78
78
  end
79
79
 
80
+ # Computes the name of the option with the settings path.
81
+ # E.g. "tracing.rails.middleware_names" for the "middleware_names" option in the "tracing.rails" settings.
82
+ # @return [String] the name of the option with the settings path
83
+ def name_with_settings_path
84
+ @name_with_settings_path ||= begin
85
+ settings_path = @context.class.settings_path
86
+ settings_path.nil? ? definition.name.to_s : "#{settings_path}.#{definition.name}"
87
+ end
88
+ end
89
+
80
90
  # Overrides the current value for this option if the `precedence` is equal or higher than
81
91
  # the previously set value.
82
92
  # The first call to `#set` will always store the value regardless of precedence.
@@ -89,7 +99,7 @@ module Datadog
89
99
  # This should be uncommon, as higher precedence values tend to
90
100
  # happen later in the application lifecycle.
91
101
  Datadog.logger.info do
92
- "Option '#{definition.name}' not changed to '#{value}' (precedence: #{precedence.name}) because the higher " \
102
+ "Option '#{name_with_settings_path}' not changed to '#{value}' (precedence: #{precedence.name}) because the higher " \
93
103
  "precedence value '#{@value}' (precedence: #{@precedence_set.name}) was already set."
94
104
  end
95
105
 
@@ -178,6 +188,21 @@ module Datadog
178
188
  precedence_set == Precedence::DEFAULT
179
189
  end
180
190
 
191
+ def settings?
192
+ @definition.is_settings
193
+ end
194
+
195
+ def values_per_precedence
196
+ # value_per_precedence is only filled after we call `get` once.
197
+ get unless @is_set
198
+
199
+ @value_per_precedence.each_with_object({}) do |(precedence, value), result|
200
+ next if value.equal?(UNSET)
201
+
202
+ result[precedence] = value
203
+ end
204
+ end
205
+
181
206
  private
182
207
 
183
208
  def coerce_env_variable(value)
@@ -216,7 +241,7 @@ module Datadog
216
241
  value
217
242
  else
218
243
  raise InvalidDefinitionError,
219
- "The option #{@definition.name} is using an unsupported type option for env coercion `#{@definition.type}`"
244
+ "The option #{name_with_settings_path} is using an unsupported type option for env coercion `#{@definition.type}`"
220
245
  end
221
246
  end
222
247
 
@@ -239,10 +264,10 @@ module Datadog
239
264
 
240
265
  if raise_error
241
266
  error_msg = if @definition.type_options[:nilable]
242
- "The setting `#{@definition.name}` inside your app's `Datadog.configure` block expects a " \
267
+ "The setting `#{name_with_settings_path}` inside your app's `Datadog.configure` block expects a " \
243
268
  "#{@definition.type} or `nil`, but a `#{value.class}` was provided (#{value.inspect})." \
244
269
  else
245
- "The setting `#{@definition.name}` inside your app's `Datadog.configure` block expects a " \
270
+ "The setting `#{name_with_settings_path}` inside your app's `Datadog.configure` block expects a " \
246
271
  "#{@definition.type}, but a `#{value.class}` was provided (#{value.inspect})." \
247
272
  end
248
273
 
@@ -276,7 +301,7 @@ module Datadog
276
301
  true # No validation is performed when option is typeless
277
302
  else
278
303
  raise InvalidDefinitionError,
279
- "The option #{@definition.name} is using an unsupported type option `#{@definition.type}`"
304
+ "The option #{name_with_settings_path} is using an unsupported type option `#{@definition.type}`"
280
305
  end
281
306
  end
282
307
 
@@ -10,28 +10,36 @@ module Datadog
10
10
  IDENTITY = ->(new_value, _old_value) { new_value }
11
11
 
12
12
  attr_reader \
13
+ :is_settings,
13
14
  :default,
14
15
  :default_proc,
15
16
  :env,
16
17
  :env_parser,
17
18
  :name,
18
19
  :after_set,
20
+ :skip_telemetry,
19
21
  :resetter,
20
22
  :setter,
21
23
  :type,
22
24
  :type_options
23
25
 
24
- def initialize(name, meta, &block)
25
- @default = meta[:default]
26
- @default_proc = meta[:default_proc]
27
- @env = meta[:env]
28
- @env_parser = meta[:env_parser]
26
+ def initialize(name, attributes, &block)
27
+ # When a settings is defined using the config DSL, an option is also created.
28
+ # See Datadog::Core::Configuration::Base::ClassMethods#settings for more details.
29
+ # This flag is used to indicate that the option is a settings option.
30
+ @is_settings = attributes[:is_settings]
31
+
32
+ @default = attributes[:default]
33
+ @default_proc = attributes[:default_proc]
34
+ @env = attributes[:env]
35
+ @env_parser = attributes[:env_parser]
29
36
  @name = name.to_sym
30
- @after_set = meta[:after_set]
31
- @resetter = meta[:resetter]
32
- @setter = meta[:setter] || block || IDENTITY
33
- @type = meta[:type]
34
- @type_options = meta[:type_options]
37
+ @after_set = attributes[:after_set]
38
+ @skip_telemetry = attributes[:skip_telemetry]
39
+ @resetter = attributes[:resetter]
40
+ @setter = attributes[:setter] || block || IDENTITY
41
+ @type = attributes[:type]
42
+ @type_options = attributes[:type_options]
35
43
  end
36
44
 
37
45
  # Creates a new Option, bound to the context provided.
@@ -49,6 +57,8 @@ module Datadog
49
57
  :helpers
50
58
 
51
59
  def initialize(name, options = {})
60
+ @is_settings = options.fetch(:is_settings, false)
61
+
52
62
  @env = nil
53
63
  @env_parser = nil
54
64
  @default = nil
@@ -56,6 +66,7 @@ module Datadog
56
66
  @helpers = {}
57
67
  @name = name.to_sym
58
68
  @after_set = nil
69
+ @skip_telemetry = false
59
70
  @resetter = nil
60
71
  @setter = OptionDefinition::IDENTITY
61
72
  @type = nil
@@ -95,6 +106,16 @@ module Datadog
95
106
  @after_set = block
96
107
  end
97
108
 
109
+ # This should only be set to true for options that are manually modified
110
+ # before being added to the telemetry payload in app_started.rb.
111
+ # E.g. telemetry is skipped for `tracing.writer_options`, but in app_started.rb,
112
+ # we send two separate entries for the buffer_size and flush_interval values.
113
+ # Standard: We want to keep this method as skip_telemetry(value),
114
+ # not skip_telemetry = value, so attr_writer is not used.
115
+ def skip_telemetry(value) # standard:disable Style/TrivialAccessors
116
+ @skip_telemetry = value
117
+ end
118
+
98
119
  def resetter(&block)
99
120
  @resetter = block
100
121
  end
@@ -119,6 +140,8 @@ module Datadog
119
140
  env(options[:env]) if options.key?(:env)
120
141
  env_parser(&options[:env_parser]) if options.key?(:env_parser)
121
142
  after_set(&options[:after_set]) if options.key?(:after_set)
143
+ # Steep: https://github.com/soutaro/steep/issues/1979
144
+ skip_telemetry(options[:skip_telemetry]) if options.key?(:skip_telemetry) # steep:ignore ArgumentTypeMismatch
122
145
  resetter(&options[:resetter]) if options.key?(:resetter)
123
146
  # Steep: https://github.com/soutaro/steep/issues/1979
124
147
  setter(&options[:setter]) if options.key?(:setter) # steep:ignore BlockTypeMismatch
@@ -126,16 +149,19 @@ module Datadog
126
149
  end
127
150
 
128
151
  def to_definition
129
- OptionDefinition.new(@name, meta)
152
+ OptionDefinition.new(@name, attributes)
130
153
  end
131
154
 
132
- def meta
155
+ def attributes
133
156
  {
157
+ is_settings: @is_settings,
158
+
134
159
  default: @default,
135
160
  default_proc: @default_proc,
136
161
  env: @env,
137
162
  env_parser: @env_parser,
138
163
  after_set: @after_set,
164
+ skip_telemetry: @skip_telemetry,
139
165
  resetter: @resetter,
140
166
  setter: @setter,
141
167
  type: @type,