datadog 2.23.0 → 2.25.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 (145) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +67 -1
  3. data/ext/datadog_profiling_native_extension/collectors_stack.c +17 -5
  4. data/ext/datadog_profiling_native_extension/crashtracking_runtime_stacks.c +239 -0
  5. data/ext/datadog_profiling_native_extension/extconf.rb +4 -1
  6. data/ext/datadog_profiling_native_extension/private_vm_api_access.c +12 -0
  7. data/ext/datadog_profiling_native_extension/private_vm_api_access.h +4 -0
  8. data/ext/datadog_profiling_native_extension/profiling.c +2 -0
  9. data/lib/datadog/ai_guard/api_client.rb +82 -0
  10. data/lib/datadog/ai_guard/component.rb +42 -0
  11. data/lib/datadog/ai_guard/configuration/ext.rb +17 -0
  12. data/lib/datadog/ai_guard/configuration/settings.rb +98 -0
  13. data/lib/datadog/ai_guard/configuration.rb +11 -0
  14. data/lib/datadog/ai_guard/evaluation/message.rb +25 -0
  15. data/lib/datadog/ai_guard/evaluation/no_op_result.rb +34 -0
  16. data/lib/datadog/ai_guard/evaluation/request.rb +81 -0
  17. data/lib/datadog/ai_guard/evaluation/result.rb +43 -0
  18. data/lib/datadog/ai_guard/evaluation/tool_call.rb +18 -0
  19. data/lib/datadog/ai_guard/evaluation.rb +72 -0
  20. data/lib/datadog/ai_guard/ext.rb +16 -0
  21. data/lib/datadog/ai_guard.rb +153 -0
  22. data/lib/datadog/appsec/context.rb +2 -1
  23. data/lib/datadog/appsec/remote.rb +5 -12
  24. data/lib/datadog/appsec/security_engine/engine.rb +3 -3
  25. data/lib/datadog/appsec/security_engine/result.rb +2 -1
  26. data/lib/datadog/appsec/security_engine/runner.rb +2 -2
  27. data/lib/datadog/core/configuration/components.rb +6 -0
  28. data/lib/datadog/core/configuration/config_helper.rb +1 -1
  29. data/lib/datadog/core/configuration/deprecations.rb +2 -2
  30. data/lib/datadog/core/configuration/option_definition.rb +4 -2
  31. data/lib/datadog/core/configuration/options.rb +8 -5
  32. data/lib/datadog/core/configuration/settings.rb +14 -3
  33. data/lib/datadog/core/configuration/supported_configurations.rb +8 -1
  34. data/lib/datadog/core/environment/cgroup.rb +52 -25
  35. data/lib/datadog/core/environment/container.rb +140 -46
  36. data/lib/datadog/core/environment/ext.rb +1 -0
  37. data/lib/datadog/core/environment/process.rb +9 -1
  38. data/lib/datadog/core/error.rb +6 -6
  39. data/lib/datadog/core/pin.rb +4 -0
  40. data/lib/datadog/core/rate_limiter.rb +9 -1
  41. data/lib/datadog/core/remote/client.rb +14 -6
  42. data/lib/datadog/core/remote/component.rb +6 -4
  43. data/lib/datadog/core/remote/configuration/content.rb +15 -2
  44. data/lib/datadog/core/remote/configuration/digest.rb +14 -7
  45. data/lib/datadog/core/remote/configuration/repository.rb +1 -1
  46. data/lib/datadog/core/remote/configuration/target.rb +13 -6
  47. data/lib/datadog/core/remote/transport/config.rb +3 -16
  48. data/lib/datadog/core/remote/transport/http/config.rb +4 -44
  49. data/lib/datadog/core/remote/transport/http/negotiation.rb +0 -39
  50. data/lib/datadog/core/remote/transport/http.rb +13 -24
  51. data/lib/datadog/core/remote/transport/negotiation.rb +7 -16
  52. data/lib/datadog/core/semaphore.rb +1 -4
  53. data/lib/datadog/core/telemetry/component.rb +52 -13
  54. data/lib/datadog/core/telemetry/event/app_started.rb +36 -1
  55. data/lib/datadog/core/telemetry/event/synth_app_client_configuration_change.rb +27 -4
  56. data/lib/datadog/core/telemetry/metrics_manager.rb +9 -0
  57. data/lib/datadog/core/telemetry/request.rb +17 -3
  58. data/lib/datadog/core/telemetry/transport/http/telemetry.rb +2 -32
  59. data/lib/datadog/core/telemetry/transport/http.rb +21 -16
  60. data/lib/datadog/core/telemetry/transport/telemetry.rb +3 -10
  61. data/lib/datadog/core/telemetry/worker.rb +88 -32
  62. data/lib/datadog/core/transport/ext.rb +2 -0
  63. data/lib/datadog/core/transport/http/api/endpoint.rb +9 -4
  64. data/lib/datadog/core/transport/http/api/instance.rb +4 -21
  65. data/lib/datadog/core/transport/http/builder.rb +9 -5
  66. data/lib/datadog/core/transport/http/client.rb +19 -8
  67. data/lib/datadog/core/transport/http.rb +22 -19
  68. data/lib/datadog/core/transport/response.rb +12 -1
  69. data/lib/datadog/core/transport/transport.rb +90 -0
  70. data/lib/datadog/core/utils/only_once_successful.rb +2 -0
  71. data/lib/datadog/core/utils/safe_dup.rb +2 -2
  72. data/lib/datadog/core/utils/sequence.rb +2 -0
  73. data/lib/datadog/core/utils/time.rb +1 -1
  74. data/lib/datadog/core/workers/async.rb +10 -1
  75. data/lib/datadog/core/workers/interval_loop.rb +44 -3
  76. data/lib/datadog/core/workers/polling.rb +2 -0
  77. data/lib/datadog/core/workers/queue.rb +100 -1
  78. data/lib/datadog/data_streams/processor.rb +1 -1
  79. data/lib/datadog/data_streams/transport/http/stats.rb +1 -36
  80. data/lib/datadog/data_streams/transport/http.rb +5 -6
  81. data/lib/datadog/data_streams/transport/stats.rb +3 -17
  82. data/lib/datadog/di/boot.rb +4 -2
  83. data/lib/datadog/di/contrib/active_record.rb +30 -5
  84. data/lib/datadog/di/el/compiler.rb +8 -4
  85. data/lib/datadog/di/error.rb +5 -0
  86. data/lib/datadog/di/instrumenter.rb +26 -7
  87. data/lib/datadog/di/logger.rb +2 -2
  88. data/lib/datadog/di/probe_builder.rb +2 -1
  89. data/lib/datadog/di/probe_file_loader/railtie.rb +1 -1
  90. data/lib/datadog/di/probe_manager.rb +37 -31
  91. data/lib/datadog/di/probe_notification_builder.rb +15 -2
  92. data/lib/datadog/di/probe_notifier_worker.rb +5 -5
  93. data/lib/datadog/di/remote.rb +89 -84
  94. data/lib/datadog/di/transport/diagnostics.rb +7 -35
  95. data/lib/datadog/di/transport/http/diagnostics.rb +1 -31
  96. data/lib/datadog/di/transport/http/input.rb +1 -31
  97. data/lib/datadog/di/transport/http.rb +28 -17
  98. data/lib/datadog/di/transport/input.rb +7 -34
  99. data/lib/datadog/di.rb +61 -5
  100. data/lib/datadog/error_tracking/filters.rb +2 -2
  101. data/lib/datadog/kit/appsec/events/v2.rb +2 -3
  102. data/lib/datadog/open_feature/evaluation_engine.rb +2 -1
  103. data/lib/datadog/open_feature/remote.rb +3 -10
  104. data/lib/datadog/open_feature/transport.rb +9 -11
  105. data/lib/datadog/opentelemetry/api/baggage.rb +1 -1
  106. data/lib/datadog/opentelemetry/configuration/settings.rb +2 -2
  107. data/lib/datadog/opentelemetry/metrics.rb +21 -14
  108. data/lib/datadog/opentelemetry/sdk/metrics_exporter.rb +5 -8
  109. data/lib/datadog/profiling/collectors/code_provenance.rb +27 -2
  110. data/lib/datadog/profiling/collectors/info.rb +5 -4
  111. data/lib/datadog/profiling/component.rb +12 -11
  112. data/lib/datadog/profiling/ext/dir_monkey_patches.rb +18 -0
  113. data/lib/datadog/profiling/http_transport.rb +4 -1
  114. data/lib/datadog/tracing/contrib/extensions.rb +10 -2
  115. data/lib/datadog/tracing/contrib/karafka/patcher.rb +31 -32
  116. data/lib/datadog/tracing/contrib/status_range_matcher.rb +2 -1
  117. data/lib/datadog/tracing/contrib/utils/quantization/hash.rb +3 -1
  118. data/lib/datadog/tracing/contrib/waterdrop/patcher.rb +6 -3
  119. data/lib/datadog/tracing/contrib/waterdrop.rb +4 -0
  120. data/lib/datadog/tracing/diagnostics/environment_logger.rb +1 -1
  121. data/lib/datadog/tracing/distributed/baggage.rb +3 -2
  122. data/lib/datadog/tracing/remote.rb +1 -9
  123. data/lib/datadog/tracing/sampling/priority_sampler.rb +3 -1
  124. data/lib/datadog/tracing/span.rb +1 -1
  125. data/lib/datadog/tracing/span_event.rb +2 -2
  126. data/lib/datadog/tracing/span_operation.rb +20 -9
  127. data/lib/datadog/tracing/trace_operation.rb +44 -6
  128. data/lib/datadog/tracing/tracer.rb +42 -16
  129. data/lib/datadog/tracing/transport/http/traces.rb +2 -50
  130. data/lib/datadog/tracing/transport/http.rb +15 -9
  131. data/lib/datadog/tracing/transport/io/client.rb +1 -1
  132. data/lib/datadog/tracing/transport/traces.rb +6 -66
  133. data/lib/datadog/tracing/workers/trace_writer.rb +5 -0
  134. data/lib/datadog/tracing/writer.rb +1 -0
  135. data/lib/datadog/version.rb +2 -2
  136. data/lib/datadog.rb +1 -0
  137. metadata +24 -17
  138. data/lib/datadog/core/remote/transport/http/api.rb +0 -53
  139. data/lib/datadog/core/telemetry/transport/http/api.rb +0 -43
  140. data/lib/datadog/core/transport/http/api/spec.rb +0 -36
  141. data/lib/datadog/data_streams/transport/http/api.rb +0 -33
  142. data/lib/datadog/data_streams/transport/http/client.rb +0 -21
  143. data/lib/datadog/di/transport/http/api.rb +0 -42
  144. data/lib/datadog/opentelemetry/api/baggage.rbs +0 -26
  145. data/lib/datadog/tracing/transport/http/api.rb +0 -44
@@ -14,6 +14,7 @@ require_relative '../remote/component'
14
14
  require_relative '../../tracing/component'
15
15
  require_relative '../../profiling/component'
16
16
  require_relative '../../appsec/component'
17
+ require_relative '../../ai_guard/component'
17
18
  require_relative '../../di/component'
18
19
  require_relative '../../open_feature/component'
19
20
  require_relative '../../error_tracking/component'
@@ -107,6 +108,7 @@ module Datadog
107
108
  :error_tracking,
108
109
  :dynamic_instrumentation,
109
110
  :appsec,
111
+ :ai_guard,
110
112
  :agent_info,
111
113
  :data_streams,
112
114
  :open_feature
@@ -143,6 +145,7 @@ module Datadog
143
145
  @runtime_metrics = self.class.build_runtime_metrics_worker(settings, @logger, telemetry)
144
146
  @health_metrics = self.class.build_health_metrics(settings, @logger, telemetry)
145
147
  @appsec = Datadog::AppSec::Component.build_appsec_component(settings, telemetry: telemetry)
148
+ @ai_guard = Datadog::AIGuard::Component.build(settings, logger: @logger, telemetry: telemetry)
146
149
  @open_feature = OpenFeature::Component.build(settings, agent_settings, logger: @logger, telemetry: telemetry)
147
150
  @dynamic_instrumentation = Datadog::DI::Component.build(settings, agent_settings, @logger, telemetry: telemetry)
148
151
  @error_tracking = Datadog::ErrorTracking::Component.build(settings, @tracer, @logger)
@@ -209,6 +212,9 @@ module Datadog
209
212
  # Decommission AppSec
210
213
  appsec&.shutdown!
211
214
 
215
+ # Shutdown AIGuard component
216
+ ai_guard&.shutdown!
217
+
212
218
  # Shutdown the old tracer, unless it's still being used.
213
219
  # (e.g. a custom tracer instance passed in.)
214
220
  tracer.shutdown! unless replacement && tracer.equal?(replacement.tracer)
@@ -9,7 +9,7 @@ module Datadog
9
9
  class ConfigHelper
10
10
  def initialize(
11
11
  source_env: ENV,
12
- supported_configurations: SUPPORTED_CONFIGURATIONS,
12
+ supported_configurations: SUPPORTED_CONFIGURATION_NAMES,
13
13
  aliases: ALIASES,
14
14
  alias_to_canonical: ALIAS_TO_CANONICAL,
15
15
  raise_on_unknown_env_var: false
@@ -21,12 +21,12 @@ module Datadog
21
21
  end
22
22
 
23
23
  private_class_method def self.log_deprecated_environment_variables(logger, source_env, source_name, deprecations, alias_to_canonical)
24
- deprecations.each do |deprecated_env_var, message|
24
+ deprecations.each do |deprecated_env_var|
25
25
  next unless source_env.key?(deprecated_env_var)
26
26
 
27
27
  Datadog::Core.log_deprecation(disallowed_next_major: false, logger: logger) do
28
28
  "#{deprecated_env_var} #{source_name} variable is deprecated" +
29
- (alias_to_canonical[deprecated_env_var] ? ", use #{alias_to_canonical[deprecated_env_var]} instead." : ". #{message}.")
29
+ (alias_to_canonical[deprecated_env_var] ? ", use #{alias_to_canonical[deprecated_env_var]} instead." : ".")
30
30
  end
31
31
  end
32
32
  end
@@ -42,7 +42,8 @@ module Datadog
42
42
  # Acts as DSL for building OptionDefinitions
43
43
  # @public_api
44
44
  class Builder
45
- InvalidOptionError = Class.new(StandardError)
45
+ # Steep: https://github.com/soutaro/steep/issues/1880
46
+ InvalidOptionError = Class.new(StandardError) # steep:ignore IncompatibleAssignment
46
47
 
47
48
  attr_reader \
48
49
  :helpers
@@ -119,7 +120,8 @@ module Datadog
119
120
  env_parser(&options[:env_parser]) if options.key?(:env_parser)
120
121
  after_set(&options[:after_set]) if options.key?(:after_set)
121
122
  resetter(&options[:resetter]) if options.key?(:resetter)
122
- setter(&options[:setter]) if options.key?(:setter)
123
+ # Steep: https://github.com/soutaro/steep/issues/1979
124
+ setter(&options[:setter]) if options.key?(:setter) # steep:ignore BlockTypeMismatch
123
125
  type(options[:type], **(options[:type_options] || {})) if options.key?(:type)
124
126
  end
125
127
 
@@ -40,14 +40,16 @@ module Datadog
40
40
 
41
41
  def default_helpers(name)
42
42
  option_name = name.to_sym
43
- # @type var opt_getter: Configuration::OptionDefinition::helper_proc
44
- opt_getter = proc do
43
+ # Steep: https://github.com/soutaro/steep/issues/335
44
+ # @type var opt_getter: Configuration::OptionDefinition::generic_proc
45
+ opt_getter = proc do # steep:ignore IncompatibleAssignment
45
46
  # These Procs uses `get/set_option`, but we only add them to the OptionDefinition helpers here.
46
47
  # Steep is right that these methods are not defined, but we only run these Procs in instance context.
47
48
  get_option(option_name) # steep:ignore NoMethod
48
49
  end
49
- # @type var opt_setter: Configuration::OptionDefinition::helper_proc
50
- opt_setter = proc do |value|
50
+ # Steep: https://github.com/soutaro/steep/issues/335
51
+ # @type var opt_setter: Configuration::OptionDefinition::generic_proc
52
+ opt_setter = proc do |value| # steep:ignore IncompatibleAssignment
51
53
  set_option(option_name, value) # steep:ignore NoMethod
52
54
  end
53
55
  {
@@ -127,7 +129,8 @@ module Datadog
127
129
  end
128
130
  end
129
131
 
130
- InvalidOptionError = Class.new(StandardError)
132
+ # Steep: https://github.com/soutaro/steep/issues/1880
133
+ InvalidOptionError = Class.new(StandardError) # steep:ignore IncompatibleAssignment
131
134
  end
132
135
  end
133
136
  end
@@ -170,6 +170,20 @@ module Datadog
170
170
  o.env Core::Environment::Ext::ENV_ENVIRONMENT
171
171
  end
172
172
 
173
+ # Configuration for container environments. For internal use only.
174
+ # @!visibility private
175
+ settings :container do
176
+ # Data supplied by the container runner to assist in uniquely identifying this process.
177
+ # Used in [Origin Detection](https://docs.datadoghq.com/developers/dogstatsd/unix_socket/?tab=host#origin-detection)
178
+ #
179
+ # @default `DD_EXTERNAL_ENV` environment variable, otherwise `nil`
180
+ # @return [String,nil]
181
+ option :external_env do |o|
182
+ o.type :string, nilable: true
183
+ o.env Core::Environment::Ext::ENV_EXTERNAL_ENV
184
+ end
185
+ end
186
+
173
187
  # Internal {Datadog::Statsd} metrics collection.
174
188
  #
175
189
  # @public_api
@@ -293,9 +307,6 @@ module Datadog
293
307
  # for Ruby versions 2.x, 3.1.4+, 3.2.3+ and 3.3.0+
294
308
  # (more details in {Datadog::Profiling::Component.enable_gc_profiling?})
295
309
  #
296
- # @warn Due to a VM bug in the Ractor implementation (https://bugs.ruby-lang.org/issues/19112) this feature
297
- # stops working when Ractors get garbage collected.
298
- #
299
310
  # @default `DD_PROFILING_GC_ENABLED` environment variable, otherwise `true`
300
311
  option :gc_enabled do |o|
301
312
  o.type :bool
@@ -8,8 +8,13 @@ require 'set'
8
8
  module Datadog
9
9
  module Core
10
10
  module Configuration
11
- SUPPORTED_CONFIGURATIONS =
11
+ SUPPORTED_CONFIGURATION_NAMES =
12
12
  Set["DD_AGENT_HOST",
13
+ "DD_AI_GUARD_ENABLED",
14
+ "DD_AI_GUARD_ENDPOINT",
15
+ "DD_AI_GUARD_MAX_CONTENT_SIZE",
16
+ "DD_AI_GUARD_MAX_MESSAGES_LENGTH",
17
+ "DD_AI_GUARD_TIMEOUT",
13
18
  "DD_API_KEY",
14
19
  "DD_API_SECURITY_ENABLED",
15
20
  "DD_API_SECURITY_ENDPOINT_COLLECTION_ENABLED",
@@ -34,6 +39,7 @@ module Datadog
34
39
  "DD_APPSEC_TRACE_RATE_LIMIT",
35
40
  "DD_APPSEC_WAF_DEBUG",
36
41
  "DD_APPSEC_WAF_TIMEOUT",
42
+ "DD_APP_KEY",
37
43
  "DD_CRASHTRACKING_ENABLED",
38
44
  "DD_DATA_STREAMS_ENABLED",
39
45
  "DD_DBM_PROPAGATION_MODE",
@@ -47,6 +53,7 @@ module Datadog
47
53
  "DD_ERROR_TRACKING_HANDLED_ERRORS_INCLUDE",
48
54
  "DD_EXPERIMENTAL_FLAGGING_PROVIDER_ENABLED",
49
55
  "DD_EXPERIMENTAL_PROPAGATE_PROCESS_TAGS_ENABLED",
56
+ "DD_EXTERNAL_ENV",
50
57
  "DD_GIT_COMMIT_SHA",
51
58
  "DD_GIT_REPOSITORY_URL",
52
59
  "DD_HEALTH_METRICS_ENABLED",
@@ -10,40 +10,67 @@ module Datadog
10
10
  # about the current Linux container identity.
11
11
  # @see https://man7.org/linux/man-pages/man7/cgroups.7.html
12
12
  module Cgroup
13
- LINE_REGEX = /^(\d+):([^:]*):(.+)$/.freeze
14
-
15
- Descriptor = Struct.new(
16
- :id,
17
- :groups,
13
+ # A parsed cgroup entry from /proc/<pid>/cgroup
14
+ Entry = Struct.new(
15
+ :hierarchy,
16
+ :controllers,
18
17
  :path,
19
- :controllers
18
+ :inode
20
19
  )
21
20
 
22
21
  module_function
23
22
 
24
- def descriptors(process = 'self')
25
- [].tap do |descriptors|
26
- filepath = "/proc/#{process}/cgroup"
27
-
28
- if File.exist?(filepath)
29
- File.foreach("/proc/#{process}/cgroup") do |line|
30
- line = line.strip
31
- descriptors << parse(line) unless line.empty?
32
- end
33
- end
34
- rescue => e
35
- Datadog.logger.error(
36
- "Error while parsing cgroup. Cause: #{e.class.name} #{e.message} Location: #{Array(e.backtrace).first}"
37
- )
23
+ # Parses the /proc/self/cgroup file,
24
+ # @return [Array<Entry>] one entry for each valid cgroup line
25
+ def entries
26
+ filepath = '/proc/self/cgroup'
27
+ return [] unless File.exist?(filepath)
28
+
29
+ ret = []
30
+ File.foreach(filepath) do |entry_line|
31
+ ret << parse(entry_line) unless entry_line.empty?
38
32
  end
33
+ ret
39
34
  end
40
35
 
41
- def parse(line)
42
- id, groups, path = line.scan(LINE_REGEX).first
36
+ # Parses a single cgroup entry from /proc/<pid>/cgroup.
37
+ #
38
+ # Files can have cgroup v1 and v2 entries mixed. Their format is the same.
39
+ #
40
+ # Each entry has 3 colon-separated fields:
41
+ # hierarchy-ID:controllers:path
42
+ # Examples:
43
+ # 10:memory:/docker/1234567890abcdef (cgroup v1)
44
+ # 0::/docker/1234567890abcdef (cgroup v2)
45
+ #
46
+ # @see https://man7.org/linux/man-pages/man7/cgroups.7.html#:~:text=%2Fproc%2Fpid%2Fcgroup
47
+ # @return [Entry]
48
+ def parse(entry_line)
49
+ hierarchy, controllers, path = entry_line.split(':', 3)
43
50
 
44
- Descriptor.new(id, groups, path).tap do |descriptor|
45
- descriptor.controllers = groups.split(',') unless groups.nil?
46
- end
51
+ Entry.new(
52
+ hierarchy,
53
+ controllers,
54
+ path,
55
+ inode_for(controllers, path)
56
+ )
57
+ end
58
+
59
+ # We can find the container inode by running a file stat on the cgroup filesystem path.
60
+ # Example:
61
+ # For the entry `0:cpu:/docker/abc123`,
62
+ # we read `stat -c '%i' /sys/fs/cgroup/cpu/docker/abc123`
63
+ def inode_for(controllers, path)
64
+ return if controllers.nil? || path.nil?
65
+
66
+ # In cgroup v1, when multiple controllers are co-mounted, the controllers
67
+ # becomes part of the directory name (with commas preserved).
68
+ # Example entry:
69
+ # For the line "10:cpu,cpuacct:/docker/abc123", the path is
70
+ # "/sys/fs/cgroup/cpu,cpuacct/docker/abc123"
71
+ inode_path = File.join('/sys/fs/cgroup', controllers, path)
72
+
73
+ File.stat(inode_path).ino if File.exist?(inode_path)
47
74
  end
48
75
  end
49
76
  end
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require_relative 'cgroup'
4
+ require_relative 'ext'
4
5
 
5
6
  module Datadog
6
7
  module Core
@@ -15,73 +16,166 @@ module Datadog
15
16
  CONTAINER_REGEX = /(?<container>#{UUID_PATTERN}|#{CONTAINER_PATTERN})(?:.scope)?$/.freeze
16
17
  FARGATE_14_CONTAINER_REGEX = /(?<container>[0-9a-f]{32}-[0-9]{1,10})/.freeze
17
18
 
18
- Descriptor = Struct.new(
19
+ # From https://github.com/torvalds/linux/blob/5859a2b1991101d6b978f3feb5325dad39421f29/include/linux/proc_ns.h#L41-L49
20
+ # Currently, the host namespace inode number is hardcoded.
21
+ # We use it to determine if we're running in the host namespace.
22
+ # This detection approach does not work when running in
23
+ # ["Docker-in-Docker"](https://www.docker.com/resources/docker-in-docker-containerized-ci-workflows-dockercon-2023/).
24
+ HOST_CGROUP_NAMESPACE_INODE = 0xEFFFFFFB
25
+
26
+ Entry = Struct.new(
19
27
  :platform,
28
+ :task_uid,
20
29
  :container_id,
21
- :task_uid
30
+ :inode
22
31
  )
23
32
 
24
33
  module_function
25
34
 
35
+ # Returns HTTP headers representing container information.
36
+ # These can used in any Datadog request that requires origin detection.
37
+ # This is the recommended method to call to get container information.
38
+ def to_headers
39
+ headers = {}
40
+ headers["Datadog-Container-ID"] = container_id if container_id
41
+ headers["Datadog-Entity-ID"] = entity_id if entity_id
42
+ headers["Datadog-External-Env"] = external_env if external_env
43
+ headers
44
+ end
45
+
46
+ # Container ID, prefixed with "ci-" or Inode, prefixed with "in-".
47
+ def entity_id
48
+ if container_id
49
+ "ci-#{container_id}"
50
+ elsif inode
51
+ "in-#{inode}"
52
+ end
53
+ end
54
+
55
+ # External data supplied by the Datadog Cluster Agent Admission Controller.
56
+ # @see {Ext::ENV_EXTERNAL_ENV} for more details.
57
+ def external_env
58
+ Datadog.configuration.container.external_env
59
+ end
60
+
61
+ # The container orchestration platform or runtime environment.
62
+ #
63
+ # Examples: Docker, Kubernetes, AWS Fargate, LXC, etc.
64
+ #
65
+ # @return [String, nil] The platform name (e.g., "docker", "kubepods", "fargate"), or nil if not containerized
26
66
  def platform
27
- descriptor.platform
67
+ entry.platform
28
68
  end
29
69
 
70
+ # The unique identifier of the current container in the container environment.
71
+ #
72
+ # @return [String, nil] The container ID, or nil if not running in a containerized environment
30
73
  def container_id
31
- descriptor.container_id
74
+ entry.container_id
32
75
  end
33
76
 
77
+ # The unique identifier of the task or pod containing this container.
78
+ #
79
+ # In Kubernetes, this is the Pod UID; in AWS ECS/Fargate, the task ID.
80
+ # Used to identify higher-level workloads beyond this container,
81
+ # enabling correlation across container restarts and multi-container applications.
82
+ #
83
+ # @return [String, nil] The task/pod UID, or nil if not available in the current environment
34
84
  def task_uid
35
- descriptor.task_uid
85
+ entry.task_uid
36
86
  end
37
87
 
38
- def descriptor
39
- @descriptor ||= Descriptor.new.tap do |descriptor|
40
- Cgroup.descriptors.each do |cgroup_descriptor|
41
- # Parse container data from cgroup descriptor
42
- path = cgroup_descriptor.path
43
- next if path.nil?
44
-
45
- # Split path into parts
46
- parts = path.split('/')
47
- parts.shift # Remove leading empty part
48
-
49
- # Read info from path
50
- next if parts.empty?
51
-
52
- platform = parts[0][PLATFORM_REGEX, :platform]
53
- container_id, task_uid = nil
54
-
55
- case parts.length
56
- when 0..1
57
- next
58
- when 2
59
- container_id = parts[-1][CONTAINER_REGEX, :container] \
60
- || parts[-1][FARGATE_14_CONTAINER_REGEX, :container]
61
- else
62
- if (container_id = parts[-1][CONTAINER_REGEX, :container])
88
+ # A unique identifier for the execution context (container or host namespace).
89
+ #
90
+ # Used as a fallback identifier when {#container_id} is unavailable.
91
+ #
92
+ # @return [Integer, nil] The namespace inode, or nil if unavailable
93
+ def inode
94
+ entry.inode
95
+ end
96
+
97
+ # Checks if the current process is running on the host cgroup namespace.
98
+ # This indicates that the process is not running inside a container.
99
+ # When unsure, we return `false` (not running on host).
100
+ def running_on_host?
101
+ return @running_on_host if defined?(@running_on_host)
102
+
103
+ @running_on_host = begin
104
+ if File.exist?('/proc/self/ns/cgroup')
105
+ File.stat('/proc/self/ns/cgroup').ino == HOST_CGROUP_NAMESPACE_INODE
106
+ else
107
+ false
108
+ end
109
+ rescue => e
110
+ Datadog.logger.debug(
111
+ "Error while checking cgroup namespace. Cause: #{e.class.name} #{e.message} Location: #{Array(e.backtrace).first}"
112
+ )
113
+ false
114
+ end
115
+ end
116
+
117
+ # All cgroup entries have the same container identity.
118
+ # The first valid one is sufficient.
119
+ # v2 entries are preferred over v1.
120
+ def entry
121
+ return @entry if defined?(@entry)
122
+
123
+ # Scan all v2 entries first, only then falling back to v1 entries.
124
+ #
125
+ # To do this, we {Enumerable#partition} the list between v1 and v2,
126
+ # with a `true` predicate for v2 entries, making v2 first
127
+ # partition returned.
128
+ #
129
+ # All v2 entries have the `hierarchy` set to zero.
130
+ # v1 entries have a non-zero `hierarchy`.
131
+ entries = Cgroup.entries.partition { |d| d.hierarchy == '0' }.flatten(1)
132
+ entries.each do |entry_obj|
133
+ path = entry_obj.path
134
+ next unless path
135
+
136
+ # To ease handling, remove the emtpy leading "",
137
+ # as `path` starts with a "/".
138
+ path.delete_prefix!('/')
139
+ parts = path.split('/')
140
+
141
+ # With not path information, we can still use the inode
142
+ if parts.empty? && entry_obj.inode && !running_on_host?
143
+ return @entry = Entry.new(nil, nil, nil, entry_obj.inode)
144
+ end
145
+
146
+ platform = parts[0][PLATFORM_REGEX, :platform]
147
+
148
+ # Extract container_id and task_uid based on path structure
149
+ container_id = task_uid = nil
150
+ if parts.length >= 2
151
+ # Try standard container regex first
152
+ if (container_id = parts[-1][CONTAINER_REGEX, :container])
153
+ # For 3+ parts, also extract task_uid
154
+ if parts.length > 2
63
155
  task_uid = parts[-2][POD_REGEX, :pod] || parts[1][POD_REGEX, :pod]
64
- else
65
- container_id = parts[-1][FARGATE_14_CONTAINER_REGEX, :container]
66
156
  end
157
+ else
158
+ # Fall back to Fargate regex
159
+ container_id = parts[-1][FARGATE_14_CONTAINER_REGEX, :container]
67
160
  end
161
+ end
68
162
 
69
- # If container ID wasn't found, ignore.
70
- # Path might describe a non-container environment.
71
- next if container_id.nil?
72
-
73
- descriptor.platform = platform
74
- descriptor.container_id = container_id
75
- descriptor.task_uid = task_uid
76
-
77
- break
163
+ # container_id is a better container identifier than inode.
164
+ # We MUST only populate one of them, to avoid container identification ambiguity.
165
+ if container_id
166
+ return @entry = Entry.new(platform, task_uid, container_id)
167
+ elsif entry_obj.inode && !running_on_host?
168
+ return @entry = Entry.new(platform, task_uid, nil, entry_obj.inode)
78
169
  end
79
- rescue => e
80
- Datadog.logger.error(
81
- "Error while parsing container info. Cause: #{e.class.name} #{e.message} " \
82
- "Location: #{Array(e.backtrace).first}"
83
- )
84
170
  end
171
+
172
+ @entry = Entry.new # Empty entry if no valid cgroup entry is found
173
+ rescue => e
174
+ Datadog.logger.debug(
175
+ "Error while reading container entry. Cause: #{e.class.name} #{e.message} Location: #{Array(e.backtrace).first}"
176
+ )
177
+ @entry = Entry.new unless defined?(@entry)
178
+ @entry
85
179
  end
86
180
  end
87
181
  end
@@ -17,6 +17,7 @@ module Datadog
17
17
 
18
18
  ENV_API_KEY = 'DD_API_KEY'
19
19
  ENV_ENVIRONMENT = 'DD_ENV'
20
+ ENV_EXTERNAL_ENV = 'DD_EXTERNAL_ENV'
20
21
  ENV_SERVICE = 'DD_SERVICE'
21
22
  ENV_SITE = 'DD_SITE'
22
23
  ENV_TAGS = 'DD_TAGS'
@@ -14,6 +14,14 @@ module Datadog
14
14
  # @return [String] comma-separated normalized key:value pairs
15
15
  def self.serialized
16
16
  return @serialized if defined?(@serialized)
17
+
18
+ @serialized = tags.join(',').freeze
19
+ end
20
+
21
+ # This method returns an array in the format ["k1:v1","k2:v2","k3:v3"]
22
+ # @return [Array<String>] array of normalized key:value pairs
23
+ def self.tags
24
+ return @tags if defined?(@tags)
17
25
  tags = []
18
26
 
19
27
  workdir = TagNormalizer.normalize_process_value(entrypoint_workdir.to_s)
@@ -27,7 +35,7 @@ module Datadog
27
35
 
28
36
  tags << "#{Environment::Ext::TAG_ENTRYPOINT_TYPE}:#{TagNormalizer.normalize(entrypoint_type, remove_digit_start_char: false)}"
29
37
 
30
- @serialized = tags.join(',').freeze
38
+ @tags = tags.freeze
31
39
  end
32
40
 
33
41
  # Returns the last segment of the working directory of the process
@@ -13,12 +13,12 @@ module Datadog
13
13
  case value
14
14
  # A Ruby {Exception} is the most common parameter type.
15
15
  when Exception then new(value.class, value.message, full_backtrace(value))
16
- # steep:ignore:start
17
- # Steep doesn't like an array with up to 3 elements to be passed here: it thinks the array is unbounded.
18
- when Array then new(*value)
19
- # Steep can not follow the logic inside the lambda, thus it doesn't know `value` responds to `:message`.
20
- when ->(v) { v.respond_to?(:message) } then new(value.class, value.message)
21
- # steep:ignore:end
16
+ # Steep: Steep doesn't like an array with up to 3 elements to be passed here: it thinks the array is unbounded.
17
+ when Array then new(*value) # steep:ignore UnexpectedPositionalArgument
18
+ when ->(v) { v.respond_to?(:message) }
19
+ # Steep: Steep can not follow the logic inside the lambda, thus it doesn't know `value` responds to `:message`.
20
+ # @type var value: _ContainsMessage
21
+ new(value.class, value.message)
22
22
  when String then new(nil, value)
23
23
  when Error then value
24
24
  else Error.new # Blank error
@@ -44,12 +44,16 @@ module Datadog
44
44
  def onto(obj)
45
45
  unless obj.respond_to? :datadog_pin=
46
46
  obj.define_singleton_method(:datadog_pin=) do |pin|
47
+ # Steep: https://github.com/soutaro/steep/issues/380
48
+ # @type self: PinnedObject
47
49
  @datadog_pin = pin
48
50
  end
49
51
  end
50
52
 
51
53
  unless obj.respond_to? :datadog_pin
52
54
  obj.define_singleton_method(:datadog_pin) do
55
+ # Steep: https://github.com/soutaro/steep/issues/380
56
+ # @type self: PinnedObject
53
57
  @datadog_pin
54
58
  end
55
59
  end
@@ -81,6 +81,10 @@ module Datadog
81
81
 
82
82
  return current_window_rate if @prev_conforming_messages.nil? || @prev_total_messages.nil?
83
83
 
84
+ # Steep: Due to https://github.com/soutaro/steep/issues/477,
85
+ # the previous nil check does not narrow type to Integer
86
+ # The annotation fixes the typing error, but it takes effect in the method
87
+ # @type ivar @prev_total_messages: Integer
84
88
  (@conforming_messages.to_f + @prev_conforming_messages.to_f) / (@total_messages + @prev_total_messages)
85
89
  end
86
90
 
@@ -154,7 +158,11 @@ module Datadog
154
158
  if @current_window.nil?
155
159
  @current_window = now
156
160
  # If more than 1 second has past since last window, reset
157
- elsif now - @current_window >= 1
161
+ #
162
+ # Steep: @current_window is a Float, but for some reason annotations does not work here
163
+ # Once a fix will be out for nil checks on instance variables, we can remove the ignore comment
164
+ # https://github.com/soutaro/steep/issues/477
165
+ elsif now - @current_window >= 1 # steep:ignore UnresolvedOverloading
158
166
  @prev_conforming_messages = @conforming_messages
159
167
  @prev_total_messages = @total_messages
160
168
  @conforming_messages = 0
@@ -14,10 +14,11 @@ module Datadog
14
14
 
15
15
  class SyncError < StandardError; end
16
16
 
17
- attr_reader :transport, :repository, :id, :dispatcher, :logger
17
+ attr_reader :transport, :repository, :id, :dispatcher, :settings, :logger
18
18
 
19
- def initialize(transport, capabilities, logger: Datadog.logger, repository: Configuration::Repository.new)
19
+ def initialize(transport, capabilities, settings:, logger:, repository: Configuration::Repository.new)
20
20
  @transport = transport
21
+ @settings = settings
21
22
  @logger = logger
22
23
 
23
24
  @repository = repository
@@ -97,7 +98,9 @@ module Datadog
97
98
  content = contents.find_content(path, target)
98
99
 
99
100
  # abort entirely if matching content not found
100
- raise SyncError, "no valid content for target at path '#{path}'" if content.nil?
101
+ if content.nil?
102
+ raise SyncError, "no valid content for target at path '#{path}'"
103
+ end
101
104
 
102
105
  # to be added or updated << config
103
106
  # TODO: metadata (hash, version, etc...)
@@ -153,14 +156,19 @@ module Datadog
153
156
  language: Core::Environment::Identity.lang,
154
157
  tracer_version: tracer_version,
155
158
  service: service_name,
156
- env: Datadog.configuration.env,
159
+ env: settings.env,
157
160
  tags: client_tracer_tags,
158
161
  }
159
162
 
160
- app_version = Datadog.configuration.version
163
+ app_version = settings.version
161
164
 
162
165
  client_tracer[:app_version] = app_version if app_version
163
166
 
167
+ if settings.experimental_propagate_process_tags_enabled
168
+ process_tags = Core::Environment::Process.tags
169
+ client_tracer[:process_tags] = process_tags if process_tags.any?
170
+ end
171
+
164
172
  {
165
173
  client: {
166
174
  state: {
@@ -184,7 +192,7 @@ module Datadog
184
192
  end
185
193
 
186
194
  def service_name
187
- Datadog.configuration.remote.service || Datadog.configuration.service
195
+ settings.remote.service || settings.service
188
196
  end
189
197
 
190
198
  def tracer_version