datadog 2.22.0 → 2.24.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 (212) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +100 -1
  3. data/ext/LIBDATADOG_DEVELOPMENT.md +1 -58
  4. data/ext/datadog_profiling_native_extension/collectors_stack.c +21 -5
  5. data/ext/datadog_profiling_native_extension/crashtracking_runtime_stacks.c +239 -0
  6. data/ext/datadog_profiling_native_extension/datadog_ruby_common.h +1 -1
  7. data/ext/datadog_profiling_native_extension/extconf.rb +9 -4
  8. data/ext/datadog_profiling_native_extension/heap_recorder.c +1 -1
  9. data/ext/datadog_profiling_native_extension/private_vm_api_access.c +12 -0
  10. data/ext/datadog_profiling_native_extension/private_vm_api_access.h +4 -0
  11. data/ext/datadog_profiling_native_extension/profiling.c +2 -0
  12. data/ext/libdatadog_api/datadog_ruby_common.h +1 -1
  13. data/ext/libdatadog_api/feature_flags.c +554 -0
  14. data/ext/libdatadog_api/feature_flags.h +5 -0
  15. data/ext/libdatadog_api/init.c +2 -0
  16. data/ext/libdatadog_api/library_config.c +12 -11
  17. data/ext/libdatadog_extconf_helpers.rb +1 -1
  18. data/lib/datadog/appsec/api_security/route_extractor.rb +23 -6
  19. data/lib/datadog/appsec/api_security/sampler.rb +7 -4
  20. data/lib/datadog/appsec/assets/blocked.html +8 -0
  21. data/lib/datadog/appsec/assets/blocked.json +1 -1
  22. data/lib/datadog/appsec/assets/blocked.text +3 -1
  23. data/lib/datadog/appsec/assets.rb +1 -1
  24. data/lib/datadog/appsec/context.rb +2 -1
  25. data/lib/datadog/appsec/remote.rb +5 -9
  26. data/lib/datadog/appsec/response.rb +18 -4
  27. data/lib/datadog/appsec/security_engine/result.rb +2 -1
  28. data/lib/datadog/core/configuration/components.rb +30 -3
  29. data/lib/datadog/core/configuration/config_helper.rb +2 -2
  30. data/lib/datadog/core/configuration/deprecations.rb +2 -2
  31. data/lib/datadog/core/configuration/option_definition.rb +4 -2
  32. data/lib/datadog/core/configuration/options.rb +8 -5
  33. data/lib/datadog/core/configuration/settings.rb +28 -3
  34. data/lib/datadog/core/configuration/supported_configurations.rb +332 -302
  35. data/lib/datadog/core/ddsketch.rb +0 -2
  36. data/lib/datadog/core/environment/cgroup.rb +52 -25
  37. data/lib/datadog/core/environment/container.rb +140 -46
  38. data/lib/datadog/core/environment/ext.rb +7 -0
  39. data/lib/datadog/core/environment/process.rb +87 -0
  40. data/lib/datadog/core/feature_flags.rb +61 -0
  41. data/lib/datadog/core/rate_limiter.rb +9 -1
  42. data/lib/datadog/core/remote/client/capabilities.rb +7 -0
  43. data/lib/datadog/core/remote/client.rb +14 -6
  44. data/lib/datadog/core/remote/component.rb +6 -4
  45. data/lib/datadog/core/remote/configuration/content.rb +15 -2
  46. data/lib/datadog/core/remote/configuration/digest.rb +14 -7
  47. data/lib/datadog/core/remote/configuration/repository.rb +1 -1
  48. data/lib/datadog/core/remote/configuration/target.rb +13 -6
  49. data/lib/datadog/core/remote/transport/config.rb +4 -25
  50. data/lib/datadog/core/remote/transport/http/config.rb +10 -50
  51. data/lib/datadog/core/remote/transport/http/negotiation.rb +14 -44
  52. data/lib/datadog/core/remote/transport/http.rb +15 -24
  53. data/lib/datadog/core/remote/transport/negotiation.rb +8 -33
  54. data/lib/datadog/core/remote/worker.rb +25 -37
  55. data/lib/datadog/core/tag_builder.rb +0 -4
  56. data/lib/datadog/core/tag_normalizer.rb +84 -0
  57. data/lib/datadog/core/telemetry/component.rb +59 -16
  58. data/lib/datadog/core/telemetry/event/app_started.rb +86 -49
  59. data/lib/datadog/core/telemetry/event/synth_app_client_configuration_change.rb +27 -4
  60. data/lib/datadog/core/telemetry/logger.rb +2 -2
  61. data/lib/datadog/core/telemetry/logging.rb +2 -8
  62. data/lib/datadog/core/telemetry/metrics_manager.rb +9 -0
  63. data/lib/datadog/core/telemetry/request.rb +17 -3
  64. data/lib/datadog/core/telemetry/transport/http/telemetry.rb +3 -34
  65. data/lib/datadog/core/telemetry/transport/http.rb +21 -16
  66. data/lib/datadog/core/telemetry/transport/telemetry.rb +3 -11
  67. data/lib/datadog/core/telemetry/worker.rb +88 -32
  68. data/lib/datadog/core/transport/ext.rb +2 -0
  69. data/lib/datadog/core/transport/http/api/endpoint.rb +9 -4
  70. data/lib/datadog/core/transport/http/api/instance.rb +4 -21
  71. data/lib/datadog/core/transport/http/builder.rb +9 -5
  72. data/lib/datadog/core/transport/http/client.rb +80 -0
  73. data/lib/datadog/core/transport/http.rb +22 -19
  74. data/lib/datadog/core/transport/response.rb +9 -0
  75. data/lib/datadog/core/transport/transport.rb +90 -0
  76. data/lib/datadog/core/utils/array.rb +29 -0
  77. data/lib/datadog/{appsec/api_security → core/utils}/lru_cache.rb +10 -21
  78. data/lib/datadog/core/utils/network.rb +3 -1
  79. data/lib/datadog/core/utils/only_once_successful.rb +8 -2
  80. data/lib/datadog/core/utils/time.rb +1 -1
  81. data/lib/datadog/core/utils.rb +2 -0
  82. data/lib/datadog/core/workers/async.rb +10 -1
  83. data/lib/datadog/core/workers/interval_loop.rb +44 -3
  84. data/lib/datadog/core/workers/polling.rb +2 -0
  85. data/lib/datadog/core/workers/queue.rb +100 -1
  86. data/lib/datadog/data_streams/configuration/settings.rb +49 -0
  87. data/lib/datadog/data_streams/configuration.rb +11 -0
  88. data/lib/datadog/data_streams/ext.rb +11 -0
  89. data/lib/datadog/data_streams/extensions.rb +16 -0
  90. data/lib/datadog/data_streams/pathway_context.rb +169 -0
  91. data/lib/datadog/data_streams/processor.rb +509 -0
  92. data/lib/datadog/data_streams/transport/http/stats.rb +52 -0
  93. data/lib/datadog/data_streams/transport/http.rb +40 -0
  94. data/lib/datadog/data_streams/transport/stats.rb +46 -0
  95. data/lib/datadog/data_streams.rb +100 -0
  96. data/lib/datadog/di/component.rb +0 -16
  97. data/lib/datadog/di/contrib/active_record.rb +31 -5
  98. data/lib/datadog/di/el/compiler.rb +8 -4
  99. data/lib/datadog/di/el/evaluator.rb +1 -1
  100. data/lib/datadog/di/error.rb +9 -0
  101. data/lib/datadog/di/instrumenter.rb +93 -34
  102. data/lib/datadog/di/probe.rb +20 -0
  103. data/lib/datadog/di/probe_builder.rb +2 -1
  104. data/lib/datadog/di/probe_manager.rb +47 -33
  105. data/lib/datadog/di/probe_notification_builder.rb +77 -25
  106. data/lib/datadog/di/proc_responder.rb +32 -0
  107. data/lib/datadog/di/remote.rb +89 -84
  108. data/lib/datadog/di/transport/diagnostics.rb +8 -36
  109. data/lib/datadog/di/transport/http/diagnostics.rb +1 -33
  110. data/lib/datadog/di/transport/http/input.rb +1 -33
  111. data/lib/datadog/di/transport/http.rb +32 -17
  112. data/lib/datadog/di/transport/input.rb +67 -34
  113. data/lib/datadog/di.rb +61 -5
  114. data/lib/datadog/open_feature/component.rb +60 -0
  115. data/lib/datadog/open_feature/configuration.rb +27 -0
  116. data/lib/datadog/open_feature/evaluation_engine.rb +70 -0
  117. data/lib/datadog/open_feature/exposures/batch_builder.rb +32 -0
  118. data/lib/datadog/open_feature/exposures/buffer.rb +43 -0
  119. data/lib/datadog/open_feature/exposures/deduplicator.rb +30 -0
  120. data/lib/datadog/open_feature/exposures/event.rb +60 -0
  121. data/lib/datadog/open_feature/exposures/reporter.rb +40 -0
  122. data/lib/datadog/open_feature/exposures/worker.rb +116 -0
  123. data/lib/datadog/open_feature/ext.rb +14 -0
  124. data/lib/datadog/open_feature/native_evaluator.rb +38 -0
  125. data/lib/datadog/open_feature/noop_evaluator.rb +26 -0
  126. data/lib/datadog/open_feature/provider.rb +141 -0
  127. data/lib/datadog/open_feature/remote.rb +67 -0
  128. data/lib/datadog/open_feature/resolution_details.rb +35 -0
  129. data/lib/datadog/open_feature/transport.rb +70 -0
  130. data/lib/datadog/open_feature.rb +19 -0
  131. data/lib/datadog/opentelemetry/api/baggage.rb +1 -1
  132. data/lib/datadog/opentelemetry/configuration/settings.rb +159 -0
  133. data/lib/datadog/opentelemetry/metrics.rb +117 -0
  134. data/lib/datadog/opentelemetry/sdk/configurator.rb +25 -1
  135. data/lib/datadog/opentelemetry/sdk/metrics_exporter.rb +35 -0
  136. data/lib/datadog/opentelemetry.rb +3 -0
  137. data/lib/datadog/profiling/collectors/code_provenance.rb +41 -7
  138. data/lib/datadog/profiling/collectors/cpu_and_wall_time_worker.rb +1 -1
  139. data/lib/datadog/profiling/collectors/idle_sampling_helper.rb +1 -1
  140. data/lib/datadog/profiling/collectors/info.rb +2 -1
  141. data/lib/datadog/profiling/component.rb +12 -11
  142. data/lib/datadog/profiling/http_transport.rb +4 -1
  143. data/lib/datadog/profiling/profiler.rb +4 -0
  144. data/lib/datadog/profiling/tag_builder.rb +36 -3
  145. data/lib/datadog/profiling.rb +1 -2
  146. data/lib/datadog/single_step_instrument.rb +1 -1
  147. data/lib/datadog/tracing/configuration/ext.rb +9 -0
  148. data/lib/datadog/tracing/configuration/settings.rb +74 -0
  149. data/lib/datadog/tracing/contrib/action_pack/action_controller/instrumentation.rb +4 -4
  150. data/lib/datadog/tracing/contrib/action_pack/utils.rb +1 -2
  151. data/lib/datadog/tracing/contrib/active_job/log_injection.rb +21 -7
  152. data/lib/datadog/tracing/contrib/active_job/patcher.rb +5 -1
  153. data/lib/datadog/tracing/contrib/aws/instrumentation.rb +4 -2
  154. data/lib/datadog/tracing/contrib/ethon/easy_patch.rb +4 -1
  155. data/lib/datadog/tracing/contrib/excon/configuration/settings.rb +11 -3
  156. data/lib/datadog/tracing/contrib/extensions.rb +10 -2
  157. data/lib/datadog/tracing/contrib/faraday/configuration/settings.rb +11 -7
  158. data/lib/datadog/tracing/contrib/grape/configuration/settings.rb +7 -3
  159. data/lib/datadog/tracing/contrib/graphql/unified_trace.rb +22 -17
  160. data/lib/datadog/tracing/contrib/http/configuration/settings.rb +11 -3
  161. data/lib/datadog/tracing/contrib/httpclient/configuration/settings.rb +11 -3
  162. data/lib/datadog/tracing/contrib/httprb/configuration/settings.rb +11 -3
  163. data/lib/datadog/tracing/contrib/kafka/instrumentation/consumer.rb +66 -0
  164. data/lib/datadog/tracing/contrib/kafka/instrumentation/producer.rb +66 -0
  165. data/lib/datadog/tracing/contrib/kafka/patcher.rb +14 -0
  166. data/lib/datadog/tracing/contrib/karafka/framework.rb +30 -0
  167. data/lib/datadog/tracing/contrib/karafka/monitor.rb +11 -0
  168. data/lib/datadog/tracing/contrib/karafka/patcher.rb +35 -4
  169. data/lib/datadog/tracing/contrib/rack/middlewares.rb +59 -27
  170. data/lib/datadog/tracing/contrib/rack/route_inference.rb +53 -0
  171. data/lib/datadog/tracing/contrib/rails/middlewares.rb +2 -2
  172. data/lib/datadog/tracing/contrib/rest_client/request_patch.rb +4 -1
  173. data/lib/datadog/tracing/contrib/roda/instrumentation.rb +3 -1
  174. data/lib/datadog/tracing/contrib/sinatra/tracer_middleware.rb +3 -1
  175. data/lib/datadog/tracing/contrib/status_range_matcher.rb +9 -1
  176. data/lib/datadog/tracing/contrib/utils/quantization/hash.rb +3 -1
  177. data/lib/datadog/tracing/contrib/waterdrop/configuration/settings.rb +27 -0
  178. data/lib/datadog/tracing/contrib/waterdrop/distributed/propagation.rb +48 -0
  179. data/lib/datadog/tracing/contrib/waterdrop/ext.rb +17 -0
  180. data/lib/datadog/tracing/contrib/waterdrop/integration.rb +43 -0
  181. data/lib/datadog/tracing/contrib/waterdrop/middleware.rb +46 -0
  182. data/lib/datadog/tracing/contrib/waterdrop/patcher.rb +49 -0
  183. data/lib/datadog/tracing/contrib/waterdrop/producer.rb +50 -0
  184. data/lib/datadog/tracing/contrib/waterdrop.rb +37 -0
  185. data/lib/datadog/tracing/contrib.rb +1 -0
  186. data/lib/datadog/tracing/diagnostics/environment_logger.rb +1 -1
  187. data/lib/datadog/tracing/metadata/ext.rb +1 -1
  188. data/lib/datadog/tracing/remote.rb +1 -9
  189. data/lib/datadog/tracing/span_event.rb +2 -2
  190. data/lib/datadog/tracing/span_operation.rb +9 -4
  191. data/lib/datadog/tracing/trace_operation.rb +44 -6
  192. data/lib/datadog/tracing/tracer.rb +42 -16
  193. data/lib/datadog/tracing/transport/http/client.rb +12 -26
  194. data/lib/datadog/tracing/transport/http/traces.rb +2 -50
  195. data/lib/datadog/tracing/transport/http.rb +15 -9
  196. data/lib/datadog/tracing/transport/io/client.rb +1 -1
  197. data/lib/datadog/tracing/transport/trace_formatter.rb +11 -0
  198. data/lib/datadog/tracing/transport/traces.rb +9 -71
  199. data/lib/datadog/tracing/workers/trace_writer.rb +5 -0
  200. data/lib/datadog/tracing/writer.rb +1 -0
  201. data/lib/datadog/version.rb +2 -2
  202. data/lib/datadog.rb +2 -0
  203. metadata +78 -21
  204. data/lib/datadog/core/remote/transport/http/api.rb +0 -53
  205. data/lib/datadog/core/remote/transport/http/client.rb +0 -49
  206. data/lib/datadog/core/telemetry/transport/http/api.rb +0 -43
  207. data/lib/datadog/core/telemetry/transport/http/client.rb +0 -49
  208. data/lib/datadog/core/transport/http/api/spec.rb +0 -36
  209. data/lib/datadog/di/transport/http/api.rb +0 -42
  210. data/lib/datadog/di/transport/http/client.rb +0 -47
  211. data/lib/datadog/opentelemetry/api/baggage.rbs +0 -26
  212. data/lib/datadog/tracing/transport/http/api.rb +0 -44
@@ -1,7 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'datadog/core'
4
-
5
3
  module Datadog
6
4
  module Core
7
5
  # Used to access ddsketch APIs.
@@ -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'
@@ -33,8 +34,14 @@ module Datadog
33
34
  LANG_INTERPRETER = "#{RUBY_ENGINE}-#{RUBY_PLATFORM}"
34
35
  LANG_PLATFORM = RUBY_PLATFORM
35
36
  LANG_VERSION = RUBY_VERSION
37
+ PROCESS_TYPE = 'script' # Out of the options [jar, script, class, executable], we consider Ruby to always be a script
36
38
  RUBY_ENGINE = ::RUBY_ENGINE # e.g. 'ruby', 'jruby', 'truffleruby'
37
39
  TAG_ENV = 'env'
40
+ TAG_ENTRYPOINT_BASEDIR = "entrypoint.basedir"
41
+ TAG_ENTRYPOINT_NAME = "entrypoint.name"
42
+ TAG_ENTRYPOINT_WORKDIR = "entrypoint.workdir"
43
+ TAG_ENTRYPOINT_TYPE = "entrypoint.type"
44
+ TAG_PROCESS_TAGS = "_dd.tags.process"
38
45
  TAG_SERVICE = 'service'
39
46
  TAG_VERSION = 'version'
40
47
 
@@ -0,0 +1,87 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'ext'
4
+ require_relative '../tag_normalizer'
5
+
6
+ module Datadog
7
+ module Core
8
+ module Environment
9
+ # Retrieves process level information such that it can be attached to various payloads
10
+ #
11
+ # @api private
12
+ module Process
13
+ # This method returns a key/value part of serialized tags in the format of k1:v1,k2:v2,k3:v3
14
+ # @return [String] comma-separated normalized key:value pairs
15
+ def self.serialized
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)
25
+ tags = []
26
+
27
+ workdir = TagNormalizer.normalize_process_value(entrypoint_workdir.to_s)
28
+ tags << "#{Environment::Ext::TAG_ENTRYPOINT_WORKDIR}:#{workdir}" unless workdir.empty?
29
+
30
+ entry_name = TagNormalizer.normalize_process_value(entrypoint_name.to_s)
31
+ tags << "#{Environment::Ext::TAG_ENTRYPOINT_NAME}:#{entry_name}" unless entry_name.empty?
32
+
33
+ basedir = TagNormalizer.normalize_process_value(entrypoint_basedir.to_s)
34
+ tags << "#{Environment::Ext::TAG_ENTRYPOINT_BASEDIR}:#{basedir}" unless basedir.empty?
35
+
36
+ tags << "#{Environment::Ext::TAG_ENTRYPOINT_TYPE}:#{TagNormalizer.normalize(entrypoint_type, remove_digit_start_char: false)}"
37
+
38
+ @tags = tags.freeze
39
+ end
40
+
41
+ # Returns the last segment of the working directory of the process
42
+ # Example: /app/myapp -> myapp
43
+ # @return [String] the last segment of the working directory
44
+ def self.entrypoint_workdir
45
+ File.basename(Dir.pwd)
46
+ end
47
+
48
+ # Returns the entrypoint type of the process
49
+ # In Ruby, the entrypoint type is always 'script'
50
+ # @return [String] the type of the process, which is fixed in Ruby
51
+ def self.entrypoint_type
52
+ Environment::Ext::PROCESS_TYPE
53
+ end
54
+
55
+ # Returns the last segment of the base directory of the process
56
+ # Example 1: /bin/mybin -> mybin
57
+ # Example 2: ruby /test/myapp.rb -> myapp
58
+ # @return [String] the last segment of base directory of the script
59
+ #
60
+ # @note Determining true entrypoint name is rather complicated. This method
61
+ # is the initial implementation but it does not produce optimal output in all cases.
62
+ # For example, all Rails applications launched via `rails server` get `rails`
63
+ # as their entrypoint name.
64
+ # We might improve the behavior in the future if there is customer demand for it.
65
+ def self.entrypoint_name
66
+ File.basename($0)
67
+ end
68
+
69
+ # Returns the last segment of the base directory of the process
70
+ # Example 1: /bin/mybin -> bin
71
+ # Example 2: ruby /test/myapp.js -> test
72
+ # @return [String] the last segment of the base directory of the script
73
+ #
74
+ # @note As with entrypoint name, determining true entrypoint directory is complicated.
75
+ # This method has an initial implementation that does not necessarily return good
76
+ # results in all cases. For example, for Rails applications launched via `rails server`
77
+ # the entrypoint basedir is `bin` which is not very helpful.
78
+ # We might improve this in the future if there is customer demand.
79
+ def self.entrypoint_basedir
80
+ File.basename(File.expand_path(File.dirname($0)))
81
+ end
82
+
83
+ private_class_method :entrypoint_workdir, :entrypoint_type, :entrypoint_name, :entrypoint_basedir
84
+ end
85
+ end
86
+ end
87
+ end
@@ -0,0 +1,61 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'json'
4
+
5
+ module Datadog
6
+ module Core
7
+ # Feature flags evaluation using libdatadog
8
+ # The classes in this module are defined as C extensions in ext/libdatadog_api/feature_flags.c
9
+ #
10
+ # @api private
11
+ module FeatureFlags
12
+ # A top-level error raised by the extension
13
+ class Error < StandardError # rubocop:disable Lint/EmptyClass
14
+ end
15
+
16
+ # Configuration for feature flags evaluation
17
+ # This class is defined in the C extension
18
+ class Configuration # rubocop:disable Lint/EmptyClass
19
+ end
20
+
21
+ # Resolution details for a feature flag evaluation
22
+ # Base class is defined in the C extension, with Ruby methods added here
23
+ class ResolutionDetails
24
+ attr_writer :value
25
+
26
+ # Get the resolved value, with JSON parsing for object types
27
+ #
28
+ # @return [Object] The resolved value (parsed from JSON if object type)
29
+ # @raise [Datadog::Core::FeatureFlags::Error] If JSON parsing fails
30
+ def value
31
+ return @value if defined?(@value)
32
+
33
+ # NOTE: Raw value method call doesn't support memoization right now
34
+ value = raw_value
35
+
36
+ # NOTE: Lazy parsing of the JSON is a temporary solution and will be
37
+ # moved into C extension
38
+ @value = json?(value) ? JSON.parse(value) : value
39
+ rescue JSON::ParserError => e
40
+ raise Error, "Failed to parse JSON value: #{e.class}: #{e}"
41
+ end
42
+
43
+ # Check if the resolution resulted in an error
44
+ #
45
+ # @return [Boolean] True if there was an error
46
+ def error?
47
+ reason == 'ERROR'
48
+ end
49
+
50
+ private
51
+
52
+ # NOTE: A JSON raw string will be returned by the `libdatadog` as
53
+ # a Ruby String class with a flag type `:object`, otherwise it's
54
+ # just a string.
55
+ def json?(value)
56
+ flag_type == :object && value.is_a?(String)
57
+ end
58
+ end
59
+ end
60
+ end
61
+ 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 steep:ignore
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
@@ -3,6 +3,7 @@
3
3
  require_relative '../../utils/base64'
4
4
  require_relative '../../../appsec/remote'
5
5
  require_relative '../../../tracing/remote'
6
+ require_relative '../../../open_feature/remote'
6
7
 
7
8
  module Datadog
8
9
  module Core
@@ -38,6 +39,12 @@ module Datadog
38
39
  register_receivers(Datadog::DI::Remote.receivers(@telemetry))
39
40
  end
40
41
 
42
+ if settings.respond_to?(:open_feature) && settings.open_feature.enabled
43
+ register_capabilities(Datadog::OpenFeature::Remote.capabilities)
44
+ register_products(Datadog::OpenFeature::Remote.products)
45
+ register_receivers(Datadog::OpenFeature::Remote.receivers(@telemetry))
46
+ end
47
+
41
48
  register_capabilities(Datadog::Tracing::Remote.capabilities)
42
49
  register_products(Datadog::Tracing::Remote.products)
43
50
  register_receivers(Datadog::Tracing::Remote.receivers(@telemetry))
@@ -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
@@ -11,9 +11,11 @@ module Datadog
11
11
  module Core
12
12
  module Remote
13
13
  # Configures the HTTP transport to communicate with the agent
14
- # to fetch and sync the remote configuration
14
+ # to fetch and sync the remote configuration.
15
+ #
16
+ # @api private
15
17
  class Component
16
- attr_reader :logger, :client, :healthy
18
+ attr_reader :logger, :client, :healthy, :worker
17
19
 
18
20
  def initialize(settings, capabilities, agent_settings, logger:)
19
21
  @logger = logger
@@ -23,7 +25,7 @@ module Datadog
23
25
 
24
26
  @barrier = Barrier.new(settings.remote.boot_timeout_seconds)
25
27
 
26
- @client = Client.new(transport_v7, capabilities, logger: logger)
28
+ @client = Client.new(transport_v7, capabilities, settings: settings, logger: logger)
27
29
  @healthy = false
28
30
  logger.debug { "new remote configuration client: #{@client.id}" }
29
31
 
@@ -55,7 +57,7 @@ module Datadog
55
57
  end
56
58
 
57
59
  # client state is unknown, state might be corrupted
58
- @client = Client.new(transport_v7, capabilities, logger: logger)
60
+ @client = Client.new(transport_v7, capabilities, settings: settings, logger: logger)
59
61
  @healthy = false
60
62
  logger.debug { "new remote configuration client: #{@client.id}" }
61
63
 
@@ -22,6 +22,18 @@ module Datadog
22
22
  attr_accessor :version
23
23
 
24
24
  def initialize(path:, data:)
25
+ if data.nil?
26
+ # +data+ is passed to Digest calculation and also is
27
+ # unconditionally taken length of by +length+ method.
28
+ # As such, the class is not written to expect +data+ to be nil.
29
+ # Detect bad incoming values here to provide earlier diagnostics
30
+ # when developing tests, for example.
31
+ raise ArgumentError, 'data must not be nil'
32
+ end
33
+ unless String === data
34
+ raise ArgumentError, "Invalid type for data: #{data.class}: expected String"
35
+ end
36
+
25
37
  @path = path
26
38
  @data = data
27
39
  @apply_state = ApplyState::UNACKNOWLEDGED
@@ -72,8 +84,9 @@ module Datadog
72
84
  private_class_method :new
73
85
  end
74
86
 
75
- # ContentList stores a list of Conetnt instances
76
- # It provides convinient methods for finding content base on Configuration::Path and Configuration::Target
87
+ # ContentList stores a list of Content instances.
88
+ # It provides convenient methods for finding content based on
89
+ # Configuration::Path and Configuration::Target.
77
90
  class ContentList < Array
78
91
  class << self
79
92
  def parse(array)