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
@@ -0,0 +1,52 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../stats'
4
+ require_relative '../../../core/transport/http/api/endpoint'
5
+ require_relative '../../../core/transport/http/response'
6
+
7
+ module Datadog
8
+ module DataStreams
9
+ module Transport
10
+ module HTTP
11
+ # HTTP transport behavior for Data Streams stats
12
+ module Stats
13
+ # Response from HTTP transport for DSM stats
14
+ class Response
15
+ include Datadog::Core::Transport::HTTP::Response
16
+
17
+ def initialize(http_response)
18
+ super
19
+ end
20
+ end
21
+
22
+ module API
23
+ # Endpoint for submitting DSM stats data
24
+ class Endpoint < Core::Transport::HTTP::API::Endpoint
25
+ def initialize(path)
26
+ super(:post, path)
27
+ end
28
+
29
+ def call(env, &block)
30
+ # Build request
31
+ env.verb = verb
32
+ env.path = path
33
+ env.body = env.request.parcel.data
34
+
35
+ # Send request
36
+ http_response = yield(env)
37
+
38
+ # Build response
39
+ Response.new(http_response)
40
+ end
41
+
42
+ def encoder
43
+ # DSM handles encoding in the transport layer
44
+ nil
45
+ end
46
+ end
47
+ end
48
+ end
49
+ end
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,40 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../../core/transport/http'
4
+ require_relative 'http/stats'
5
+ require_relative 'stats'
6
+
7
+ module Datadog
8
+ module DataStreams
9
+ module Transport
10
+ # HTTP transport for Data Streams Monitoring
11
+ module HTTP
12
+ V01 = Stats::API::Endpoint.new(
13
+ '/v0.1/pipeline_stats'
14
+ )
15
+
16
+ module_function
17
+
18
+ # Builds a new Transport::HTTP::Client with default settings
19
+ def default(
20
+ agent_settings:,
21
+ logger:
22
+ )
23
+ Core::Transport::HTTP.build(
24
+ agent_settings: agent_settings,
25
+ logger: logger,
26
+ headers: {
27
+ 'Content-Type' => 'application/msgpack',
28
+ 'Content-Encoding' => 'gzip'
29
+ }
30
+ ) do |transport|
31
+ transport.api 'v0.1', V01, default: true
32
+
33
+ # Call block to apply any customization, if provided
34
+ yield(transport) if block_given?
35
+ end.to_transport(Transport::Stats::Transport)
36
+ end
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,46 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'msgpack'
4
+ require 'zlib'
5
+ require_relative '../../core/transport/parcel'
6
+ require_relative '../../core/transport/request'
7
+ require_relative '../../core/transport/transport'
8
+
9
+ module Datadog
10
+ module DataStreams
11
+ module Transport
12
+ module Stats
13
+ # Parcel for encoded DSM stats payload
14
+ class EncodedParcel
15
+ include Datadog::Core::Transport::Parcel
16
+
17
+ def initialize(data)
18
+ @data = data
19
+ end
20
+
21
+ attr_reader :data
22
+ end
23
+
24
+ # Request for DSM stats
25
+ class Request < Datadog::Core::Transport::Request
26
+ end
27
+
28
+ # Transport for Data Streams Monitoring stats
29
+ class Transport < Core::Transport::Transport
30
+ def send_stats(payload)
31
+ # MessagePack encode and gzip compress the payload
32
+ msgpack_data = MessagePack.pack(payload)
33
+ compressed_data = Zlib.gzip(msgpack_data)
34
+
35
+ # Create parcel and request
36
+ parcel = EncodedParcel.new(compressed_data)
37
+ request = Request.new(parcel)
38
+
39
+ # Send to agent
40
+ client.send_request(:stats, request)
41
+ end
42
+ end
43
+ end
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,100 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'data_streams/processor'
4
+ require_relative 'data_streams/pathway_context'
5
+ require_relative 'data_streams/configuration/settings'
6
+ require_relative 'data_streams/extensions'
7
+ require_relative 'core/utils/time'
8
+
9
+ module Datadog
10
+ # Datadog Data Streams Monitoring public API.
11
+ #
12
+ # The Datadog team ensures that public methods in this module
13
+ # only receive backwards compatible changes, and breaking changes
14
+ # will only occur in new major versions releases.
15
+ # @public_api
16
+ module DataStreams
17
+ class << self
18
+ # Set a produce checkpoint for Data Streams Monitoring
19
+ #
20
+ # @param type [String] The type of the checkpoint (e.g., 'kafka', 'kinesis', 'sns')
21
+ # @param destination [String] The destination (e.g., topic, exchange, stream name)
22
+ # @param auto_instrumentation [Boolean] Whether this checkpoint was set by auto-instrumentation (default: false)
23
+ # @param tags [Hash] Additional tags to include
24
+ # @yield [key, value] Block to inject context into carrier
25
+ # @return [String, nil] Base64 encoded pathway context or nil if disabled
26
+ # @public_api
27
+ def set_produce_checkpoint(type:, destination:, auto_instrumentation: false, tags: {}, &block)
28
+ processor&.set_produce_checkpoint(
29
+ type: type,
30
+ destination: destination,
31
+ manual_checkpoint: !auto_instrumentation,
32
+ tags: tags,
33
+ &block
34
+ )
35
+ end
36
+
37
+ # Set a consume checkpoint for Data Streams Monitoring
38
+ #
39
+ # @param type [String] The type of the checkpoint (e.g., 'kafka', 'kinesis', 'sns')
40
+ # @param source [String] The source (e.g., topic, exchange, stream name)
41
+ # @param auto_instrumentation [Boolean] Whether this checkpoint was set by auto-instrumentation (default: false)
42
+ # @param tags [Hash] Additional tags to include
43
+ # @yield [key] Block to extract context from carrier
44
+ # @return [String, nil] Base64 encoded pathway context or nil if disabled
45
+ # @public_api
46
+ def set_consume_checkpoint(type:, source:, auto_instrumentation: false, tags: {}, &block)
47
+ processor&.set_consume_checkpoint(
48
+ type: type,
49
+ source: source,
50
+ manual_checkpoint: !auto_instrumentation,
51
+ tags: tags,
52
+ &block
53
+ )
54
+ end
55
+
56
+ # Track Kafka produce offset for lag monitoring
57
+ #
58
+ # @param topic [String] The Kafka topic name
59
+ # @param partition [Integer] The partition number
60
+ # @param offset [Integer] The offset of the produced message
61
+ # @return [Boolean, nil] true if tracking succeeded, nil if disabled
62
+ # @!visibility private
63
+ def track_kafka_produce(topic, partition, offset)
64
+ processor&.track_kafka_produce(topic, partition, offset, Core::Utils::Time.now)
65
+ end
66
+
67
+ # Track Kafka message consumption for consumer lag monitoring
68
+ #
69
+ # @param topic [String] The Kafka topic name
70
+ # @param partition [Integer] The partition number
71
+ # @param offset [Integer] The offset of the consumed message
72
+ # @return [Boolean, nil] true if tracking succeeded, nil if disabled
73
+ # @!visibility private
74
+ def track_kafka_consume(topic, partition, offset)
75
+ processor&.track_kafka_consume(topic, partition, offset, Core::Utils::Time.now)
76
+ end
77
+
78
+ # Check if Data Streams Monitoring is enabled and available
79
+ #
80
+ # @return [Boolean] true if the processor is available
81
+ # @public_api
82
+ def enabled?
83
+ !processor.nil?
84
+ end
85
+
86
+ private
87
+
88
+ def processor
89
+ components.data_streams
90
+ end
91
+
92
+ def components
93
+ Datadog.send(:components)
94
+ end
95
+ end
96
+
97
+ # Expose Data Streams to global shared objects
98
+ Extensions.activate!
99
+ end
100
+ end
@@ -29,22 +29,6 @@ module Datadog
29
29
  end
30
30
  end
31
31
 
32
- def build!(settings, agent_settings, logger, telemetry: nil)
33
- unless settings.respond_to?(:dynamic_instrumentation) && settings.dynamic_instrumentation.enabled
34
- raise "Requested DI component but DI is not enabled in settings"
35
- end
36
-
37
- unless settings.respond_to?(:remote) && settings.remote.enabled
38
- raise "Requested DI component but remote config is not enabled in settings"
39
- end
40
-
41
- unless environment_supported?(settings, logger)
42
- raise "DI does not support the environment (development or Ruby version too low or not MRI)"
43
- end
44
-
45
- new(settings, agent_settings, logger, code_tracker: DI.code_tracker, telemetry: telemetry)
46
- end
47
-
48
32
  # Checks whether the runtime environment is supported by
49
33
  # dynamic instrumentation. Currently we only require that, if Rails
50
34
  # is used, that Rails environment is not development because
@@ -1,12 +1,38 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- Datadog::DI::Serializer.register(condition: lambda { |value| ActiveRecord::Base === value }) do |serializer, value, name:, depth:| # steep:ignore
4
- # steep thinks all of the arguments are nil here
5
- # steep:ignore:start
3
+ # steep thinks all of the arguments are nil here and does not know what ActiveRecord is.
4
+ # steep:ignore:start
5
+
6
+ Datadog::DI::Serializer.register(
7
+ # This serializer uses a dynamic condition to determine its applicability
8
+ # to a particular value. A simpler case could have been a serializer for
9
+ # a particular class, but in this case any ActiveRecord model is covered
10
+ # and they all have different classes.
11
+ #
12
+ # An alternative could have been to make DI specifically provide lookup
13
+ # logic for "instances of classes derived from X", but a condition Proc
14
+ # is more universal.
15
+ condition: lambda { |value| ActiveRecord::Base === value }
16
+ ) do |serializer, value, name:, depth:|
17
+ # +serializer+ is an instance of DI::Serializer.
18
+ # Use it to perform the serialization to primitive values.
19
+ #
20
+ # +value+ is the value to serialize. It should match the condition
21
+ # provided above, meaning it would be an ActiveRecord::Base instance.
22
+ #
23
+ # +name+ is the name of the (local/instance) variable being serialized.
24
+ # The name is used by DI for redaction (upstream of serialization logic),
25
+ # and could potentially be used for redaction here also.
26
+ #
27
+ # +depth+ is the remaining depth for serializing collections and objects.
28
+ # It should always be an integer.
29
+ # Reduce it by 1 when invoking +serialize_value+ on the contents of +value+.
30
+ # This serializer could also potentially do its own depth limiting.
6
31
  value_to_serialize = {
7
32
  attributes: value.attributes,
8
33
  new_record: value.new_record?,
9
34
  }
10
- serializer.serialize_value(value_to_serialize, depth: depth ? depth - 1 : nil, type: value.class)
11
- # steep:ignore:end
35
+ serializer.serialize_value(value_to_serialize, depth: depth - 1, type: value.class)
12
36
  end
37
+
38
+ # steep:ignore:end
@@ -22,7 +22,8 @@ module Datadog
22
22
 
23
23
  private
24
24
 
25
- OPERATORS = {
25
+ # Steep: https://github.com/soutaro/steep/issues/363
26
+ OPERATORS = { # steep:ignore IncompatibleAssignment
26
27
  'eq' => '==',
27
28
  'ne' => '!=',
28
29
  'ge' => '>=',
@@ -31,16 +32,19 @@ module Datadog
31
32
  'lt' => '<',
32
33
  }.freeze
33
34
 
35
+ # Steep: https://github.com/soutaro/steep/issues/363
34
36
  SINGLE_ARG_METHODS = %w[
35
37
  len isEmpty isUndefined
36
- ].freeze
38
+ ].freeze # steep:ignore IncompatibleAssignment
37
39
 
40
+ # Steep: https://github.com/soutaro/steep/issues/363
38
41
  TWO_ARG_METHODS = %w[
39
42
  startsWith endsWith contains matches
40
43
  getmember index instanceof
41
- ].freeze
44
+ ].freeze # steep:ignore IncompatibleAssignment
42
45
 
43
- MULTI_ARG_METHODS = {
46
+ # Steep: https://github.com/soutaro/steep/issues/363
47
+ MULTI_ARG_METHODS = { # steep:ignore IncompatibleAssignment
44
48
  'and' => '&&',
45
49
  'or' => '||',
46
50
  }.freeze
@@ -17,7 +17,7 @@ module Datadog
17
17
 
18
18
  def len(var, var_name)
19
19
  case var
20
- when Array, String
20
+ when Array, String, Hash
21
21
  var.length
22
22
  else
23
23
  raise DI::Error::ExpressionEvaluationError, "Unsupported type for length: #{var.class}: #{var_name}"
@@ -10,6 +10,10 @@ module Datadog
10
10
  #
11
11
  # @api private
12
12
  class Error < StandardError
13
+ # Internal Dynamic Instrumentation error ("should never happen").
14
+ class InternalError < Error
15
+ end
16
+
13
17
  # Probe does not contain a line number (i.e., is not a line probe).
14
18
  class MissingLineNumber < Error
15
19
  end
@@ -38,6 +42,11 @@ module Datadog
38
42
  class ProbePreviouslyFailed < Error
39
43
  end
40
44
 
45
+ # Raised when trying to instrument a probe when there is existing
46
+ # instrumentation for the same probe id.
47
+ class AlreadyInstrumented < Error
48
+ end
49
+
41
50
  # Raised when installing a line probe and multiple files match the
42
51
  # specified path suffix.
43
52
  # A probe must be installed into one file only, since UI only
@@ -89,11 +89,7 @@ module Datadog
89
89
  # from the method but from outside of the method).
90
90
  Location = Struct.new(:path, :lineno, :label)
91
91
 
92
- def hook_method(probe, &block)
93
- unless block
94
- raise ArgumentError, 'block is required'
95
- end
96
-
92
+ def hook_method(probe, responder)
97
93
  lock.synchronize do
98
94
  if probe.instrumentation_module
99
95
  # Already instrumented, warn?
@@ -130,10 +126,34 @@ module Datadog
130
126
  caller_locations: caller_locations,
131
127
  )
132
128
  continue = condition.satisfied?(context)
133
- rescue
134
- raise if settings.dynamic_instrumentation.internal.propagate_all_exceptions
129
+ rescue => exc
130
+ # Evaluation error exception can be raised for "expected"
131
+ # errors, we probably need another setting to control whether
132
+ # these exceptions are propagated.
133
+ raise if settings.dynamic_instrumentation.internal.propagate_all_exceptions &&
134
+ !exc.is_a?(DI::Error::ExpressionEvaluationError)
135
+
136
+ if context
137
+ # We want to report evaluation errors for conditions
138
+ # as probe snapshots. However, if we failed to create
139
+ # the context, we won't be able to report anything as
140
+ # the probe notifier builder requires a context.
141
+ begin
142
+ responder.probe_condition_evaluation_failed_callback(context, exc)
143
+ rescue
144
+ raise if settings.dynamic_instrumentation.internal.propagate_all_exceptions
145
+
146
+ # TODO log / report via telemetry?
147
+ end
148
+ else
149
+ _ = 42 # stop standard from wrecking this code
150
+
151
+ raise if settings.dynamic_instrumentation.internal.propagate_all_exceptions
152
+
153
+ # TODO log / report via telemetry?
154
+ # If execution gets here, there is probably a bug in the tracer.
155
+ end
135
156
 
136
- # TODO log / report via telemetry?
137
157
  continue = false
138
158
  end
139
159
  end
@@ -146,7 +166,10 @@ module Datadog
146
166
  depth: probe.max_capture_depth || settings.dynamic_instrumentation.max_capture_depth,
147
167
  attribute_count: probe.max_capture_attribute_count || settings.dynamic_instrumentation.max_capture_attribute_count)
148
168
  end
149
- start_time = Core::Utils::Time.get_time
169
+ # We intentionally do not use Core::Utils::Time.get_time
170
+ # here because the time provider may be overridden by the
171
+ # customer, and DI is not allowed to invoke customer code.
172
+ start_time = Process.clock_gettime(Process::CLOCK_MONOTONIC)
150
173
 
151
174
  rv = nil
152
175
  begin
@@ -170,7 +193,7 @@ module Datadog
170
193
  # the instrumentation callback runs.
171
194
  end
172
195
 
173
- duration = Core::Utils::Time.get_time - start_time
196
+ duration = Process.clock_gettime(Process::CLOCK_MONOTONIC) - start_time
174
197
  # The method itself is not part of the stack trace because
175
198
  # we are getting the stack trace from outside of the method.
176
199
  # Add the method in manually as the top frame.
@@ -195,8 +218,7 @@ module Datadog
195
218
  caller_locations: caller_locs,
196
219
  return_value: rv, duration: duration, exception: exc,)
197
220
 
198
- # & is to stop steep complaints, block is always present here.
199
- block&.call(context)
221
+ responder.probe_executed_callback(context)
200
222
  if exc
201
223
  raise exc
202
224
  else
@@ -237,6 +259,8 @@ module Datadog
237
259
 
238
260
  probe.instrumentation_module = mod
239
261
  cls.send(:prepend, mod)
262
+
263
+ DI.instrumented_count_inc(:method)
240
264
  end
241
265
  end
242
266
 
@@ -249,6 +273,8 @@ module Datadog
249
273
  if mod = probe.instrumentation_module
250
274
  mod.send(:remove_method, probe.method_name)
251
275
  probe.instrumentation_module = nil
276
+
277
+ DI.instrumented_count_dec(:method)
252
278
  end
253
279
  end
254
280
  end
@@ -258,11 +284,7 @@ module Datadog
258
284
  # not for eval'd code, unless the eval'd code is associated with
259
285
  # a file name and client invokes this method with the correct
260
286
  # file name for the eval'd code.
261
- def hook_line(probe, &block)
262
- unless block
263
- raise ArgumentError, 'No block given to hook_line'
264
- end
265
-
287
+ def hook_line(probe, responder)
266
288
  lock.synchronize do
267
289
  if probe.instrumentation_trace_point
268
290
  # Already instrumented, warn?
@@ -367,14 +389,44 @@ module Datadog
367
389
 
368
390
  if continue
369
391
  if condition = probe.condition
370
- context = Context.new(
371
- locals: Instrumenter.get_local_variables(tp),
372
- target_self: tp.self,
373
- probe: probe, settings: settings, serializer: serializer,
374
- path: tp.path,
375
- caller_locations: caller_locations,
376
- )
377
- continue = condition.satisfied?(context)
392
+ begin
393
+ context = Context.new(
394
+ locals: Instrumenter.get_local_variables(tp),
395
+ target_self: tp.self,
396
+ probe: probe, settings: settings, serializer: serializer,
397
+ path: tp.path,
398
+ caller_locations: caller_locations,
399
+ )
400
+ continue = condition.satisfied?(context)
401
+ rescue => exc
402
+ # Evaluation error exception can be raised for "expected"
403
+ # errors, we probably need another setting to control whether
404
+ # these exceptions are propagated.
405
+ raise if settings.dynamic_instrumentation.internal.propagate_all_exceptions &&
406
+ !exc.is_a?(DI::Error::ExpressionEvaluationError)
407
+
408
+ continue = false
409
+ if context
410
+ # We want to report evaluation errors for conditions
411
+ # as probe snapshots. However, if we failed to create
412
+ # the context, we won't be able to report anything as
413
+ # the probe notifier builder requires a context.
414
+ begin
415
+ responder.probe_condition_evaluation_failed_callback(context, condition, exc)
416
+ rescue
417
+ raise if settings.dynamic_instrumentation.internal.propagate_all_exceptions
418
+
419
+ # TODO log / report via telemetry?
420
+ end
421
+ else
422
+ _ = 42 # stop standard from wrecking this code
423
+
424
+ raise if settings.dynamic_instrumentation.internal.propagate_all_exceptions
425
+
426
+ # TODO log / report via telemetry?
427
+ # If execution gets here, there is probably a bug in the tracer.
428
+ end
429
+ end
378
430
  end
379
431
  end
380
432
 
@@ -393,8 +445,7 @@ module Datadog
393
445
  caller_locations: caller_locations,
394
446
  )
395
447
 
396
- # & is to stop steep complaints, block is always present here.
397
- block&.call(context)
448
+ responder.probe_executed_callback(context)
398
449
  end
399
450
  rescue => exc
400
451
  raise if settings.dynamic_instrumentation.internal.propagate_all_exceptions
@@ -409,9 +460,11 @@ module Datadog
409
460
  # TODO test this path
410
461
  end
411
462
 
412
- # TODO internal check - remove or use a proper exception
463
+ # Internal sanity check - untargeted trace points create a huge
464
+ # performance impact, and we absolutely do not want to set them
465
+ # accidentally.
413
466
  if !iseq && !permit_untargeted_trace_points
414
- raise "Trying to use an untargeted trace point when user did not permit it"
467
+ raise Error::InternalError, "Trying to use an untargeted trace point when user did not permit it"
415
468
  end
416
469
 
417
470
  lock.synchronize do
@@ -424,12 +477,16 @@ module Datadog
424
477
  # actual_path could be nil if we don't use targeted trace points.
425
478
  probe.instrumented_path = actual_path
426
479
 
427
- if iseq
480
+ # TracePoint#enable returns false when it succeeds.
481
+ rv = if iseq
428
482
  tp.enable(target: iseq, target_line: line_no)
429
483
  else
430
484
  tp.enable
431
485
  end
432
- # TracePoint#enable returns false when it succeeds.
486
+
487
+ DI.instrumented_count_inc(:line)
488
+
489
+ rv
433
490
  end
434
491
  true
435
492
  end
@@ -439,15 +496,17 @@ module Datadog
439
496
  if tp = probe.instrumentation_trace_point
440
497
  tp.disable
441
498
  probe.instrumentation_trace_point = nil
499
+
500
+ DI.instrumented_count_dec(:line)
442
501
  end
443
502
  end
444
503
  end
445
504
 
446
- def hook(probe, &block)
505
+ def hook(probe, responder)
447
506
  if probe.method?
448
- hook_method(probe, &block)
507
+ hook_method(probe, responder)
449
508
  elsif probe.line?
450
- hook_line(probe, &block)
509
+ hook_line(probe, responder)
451
510
  else
452
511
  # TODO add test coverage for this path
453
512
  logger.debug { "di: unknown probe type to hook: #{probe}" }
@@ -86,6 +86,16 @@ module Datadog
86
86
  @rate_limit = rate_limit || (@capture_snapshot ? 1 : 5000)
87
87
  @rate_limiter = Datadog::Core::TokenBucket.new(@rate_limit)
88
88
 
89
+ # At most one report per second.
90
+ # We create the rate limiter here even though it may never be used,
91
+ # to avoid having to synchronize the creation since method probes
92
+ # can be executed on multiple threads concurrently (even if line
93
+ # probes are never executed concurrently since those are done in a
94
+ # trace point).
95
+ if condition
96
+ @condition_evaluation_failed_rate_limiter = Datadog::Core::TokenBucket.new(1)
97
+ end
98
+
89
99
  @emitting_notified = false
90
100
  end
91
101
 
@@ -115,6 +125,16 @@ module Datadog
115
125
  # Rate limiter object. For internal DI use only.
116
126
  attr_reader :rate_limiter
117
127
 
128
+ # Rate limiter object for sending snapshots with evaluation errors
129
+ # for when probe condition evaluation fails.
130
+ # This rate limit is separate from the "base" rate limit for the probe
131
+ # because when the condition evaluation succeeds we want the "base"
132
+ # rate limit applied, not tainted by any evaluation errors
133
+ # (for example, the condition can be highly selective, and when it
134
+ # does not hold the evaluation may fail - we don't want to use up the
135
+ # probe rate limit for the errors).
136
+ attr_reader :condition_evaluation_failed_rate_limiter
137
+
118
138
  def capture_snapshot?
119
139
  @capture_snapshot
120
140
  end
@@ -20,7 +20,8 @@ module Datadog
20
20
  #
21
21
  # @api private
22
22
  module ProbeBuilder
23
- PROBE_TYPES = {
23
+ # Steep: https://github.com/soutaro/steep/issues/363
24
+ PROBE_TYPES = { # steep:ignore IncompatibleAssignment
24
25
  'LOG_PROBE' => :log,
25
26
  }.freeze
26
27