datadog 2.22.0 → 2.23.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 (152) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +59 -2
  3. data/ext/LIBDATADOG_DEVELOPMENT.md +1 -58
  4. data/ext/datadog_profiling_native_extension/collectors_stack.c +4 -0
  5. data/ext/datadog_profiling_native_extension/datadog_ruby_common.h +1 -1
  6. data/ext/datadog_profiling_native_extension/extconf.rb +6 -4
  7. data/ext/datadog_profiling_native_extension/heap_recorder.c +1 -1
  8. data/ext/libdatadog_api/datadog_ruby_common.h +1 -1
  9. data/ext/libdatadog_api/feature_flags.c +554 -0
  10. data/ext/libdatadog_api/feature_flags.h +5 -0
  11. data/ext/libdatadog_api/init.c +2 -0
  12. data/ext/libdatadog_api/library_config.c +12 -11
  13. data/ext/libdatadog_extconf_helpers.rb +1 -1
  14. data/lib/datadog/appsec/api_security/route_extractor.rb +23 -6
  15. data/lib/datadog/appsec/api_security/sampler.rb +7 -4
  16. data/lib/datadog/appsec/assets/blocked.html +8 -0
  17. data/lib/datadog/appsec/assets/blocked.json +1 -1
  18. data/lib/datadog/appsec/assets/blocked.text +3 -1
  19. data/lib/datadog/appsec/assets.rb +1 -1
  20. data/lib/datadog/appsec/remote.rb +4 -0
  21. data/lib/datadog/appsec/response.rb +18 -4
  22. data/lib/datadog/core/configuration/components.rb +30 -3
  23. data/lib/datadog/core/configuration/config_helper.rb +1 -1
  24. data/lib/datadog/core/configuration/settings.rb +14 -0
  25. data/lib/datadog/core/configuration/supported_configurations.rb +330 -301
  26. data/lib/datadog/core/ddsketch.rb +0 -2
  27. data/lib/datadog/core/environment/ext.rb +6 -0
  28. data/lib/datadog/core/environment/process.rb +79 -0
  29. data/lib/datadog/core/feature_flags.rb +61 -0
  30. data/lib/datadog/core/remote/client/capabilities.rb +7 -0
  31. data/lib/datadog/core/remote/transport/config.rb +2 -10
  32. data/lib/datadog/core/remote/transport/http/config.rb +9 -9
  33. data/lib/datadog/core/remote/transport/http/negotiation.rb +17 -8
  34. data/lib/datadog/core/remote/transport/http.rb +2 -0
  35. data/lib/datadog/core/remote/transport/negotiation.rb +2 -18
  36. data/lib/datadog/core/remote/worker.rb +25 -37
  37. data/lib/datadog/core/tag_builder.rb +0 -4
  38. data/lib/datadog/core/tag_normalizer.rb +84 -0
  39. data/lib/datadog/core/telemetry/component.rb +7 -3
  40. data/lib/datadog/core/telemetry/event/app_started.rb +52 -49
  41. data/lib/datadog/core/telemetry/event/synth_app_client_configuration_change.rb +1 -1
  42. data/lib/datadog/core/telemetry/logger.rb +2 -2
  43. data/lib/datadog/core/telemetry/logging.rb +2 -8
  44. data/lib/datadog/core/telemetry/transport/http/telemetry.rb +5 -6
  45. data/lib/datadog/core/telemetry/transport/telemetry.rb +1 -2
  46. data/lib/datadog/core/transport/http/client.rb +69 -0
  47. data/lib/datadog/core/utils/array.rb +29 -0
  48. data/lib/datadog/{appsec/api_security → core/utils}/lru_cache.rb +10 -21
  49. data/lib/datadog/core/utils/network.rb +3 -1
  50. data/lib/datadog/core/utils/only_once_successful.rb +6 -2
  51. data/lib/datadog/core/utils.rb +2 -0
  52. data/lib/datadog/data_streams/configuration/settings.rb +49 -0
  53. data/lib/datadog/data_streams/configuration.rb +11 -0
  54. data/lib/datadog/data_streams/ext.rb +11 -0
  55. data/lib/datadog/data_streams/extensions.rb +16 -0
  56. data/lib/datadog/data_streams/pathway_context.rb +169 -0
  57. data/lib/datadog/data_streams/processor.rb +509 -0
  58. data/lib/datadog/data_streams/transport/http/api.rb +33 -0
  59. data/lib/datadog/data_streams/transport/http/client.rb +21 -0
  60. data/lib/datadog/data_streams/transport/http/stats.rb +87 -0
  61. data/lib/datadog/data_streams/transport/http.rb +41 -0
  62. data/lib/datadog/data_streams/transport/stats.rb +60 -0
  63. data/lib/datadog/data_streams.rb +100 -0
  64. data/lib/datadog/di/component.rb +0 -16
  65. data/lib/datadog/di/el/evaluator.rb +1 -1
  66. data/lib/datadog/di/error.rb +4 -0
  67. data/lib/datadog/di/instrumenter.rb +76 -30
  68. data/lib/datadog/di/probe.rb +20 -0
  69. data/lib/datadog/di/probe_manager.rb +10 -2
  70. data/lib/datadog/di/probe_notification_builder.rb +62 -23
  71. data/lib/datadog/di/proc_responder.rb +32 -0
  72. data/lib/datadog/di/transport/diagnostics.rb +2 -2
  73. data/lib/datadog/di/transport/http/diagnostics.rb +2 -4
  74. data/lib/datadog/di/transport/http/input.rb +2 -4
  75. data/lib/datadog/di/transport/http.rb +6 -2
  76. data/lib/datadog/di/transport/input.rb +64 -4
  77. data/lib/datadog/open_feature/component.rb +60 -0
  78. data/lib/datadog/open_feature/configuration.rb +27 -0
  79. data/lib/datadog/open_feature/evaluation_engine.rb +69 -0
  80. data/lib/datadog/open_feature/exposures/batch_builder.rb +32 -0
  81. data/lib/datadog/open_feature/exposures/buffer.rb +43 -0
  82. data/lib/datadog/open_feature/exposures/deduplicator.rb +30 -0
  83. data/lib/datadog/open_feature/exposures/event.rb +60 -0
  84. data/lib/datadog/open_feature/exposures/reporter.rb +40 -0
  85. data/lib/datadog/open_feature/exposures/worker.rb +116 -0
  86. data/lib/datadog/open_feature/ext.rb +14 -0
  87. data/lib/datadog/open_feature/native_evaluator.rb +38 -0
  88. data/lib/datadog/open_feature/noop_evaluator.rb +26 -0
  89. data/lib/datadog/open_feature/provider.rb +141 -0
  90. data/lib/datadog/open_feature/remote.rb +74 -0
  91. data/lib/datadog/open_feature/resolution_details.rb +35 -0
  92. data/lib/datadog/open_feature/transport.rb +72 -0
  93. data/lib/datadog/open_feature.rb +19 -0
  94. data/lib/datadog/opentelemetry/configuration/settings.rb +159 -0
  95. data/lib/datadog/opentelemetry/metrics.rb +110 -0
  96. data/lib/datadog/opentelemetry/sdk/configurator.rb +25 -1
  97. data/lib/datadog/opentelemetry/sdk/metrics_exporter.rb +38 -0
  98. data/lib/datadog/opentelemetry.rb +3 -0
  99. data/lib/datadog/profiling/collectors/code_provenance.rb +15 -6
  100. data/lib/datadog/profiling/collectors/cpu_and_wall_time_worker.rb +1 -1
  101. data/lib/datadog/profiling/collectors/idle_sampling_helper.rb +1 -1
  102. data/lib/datadog/profiling/profiler.rb +4 -0
  103. data/lib/datadog/profiling/tag_builder.rb +36 -3
  104. data/lib/datadog/profiling.rb +1 -2
  105. data/lib/datadog/single_step_instrument.rb +1 -1
  106. data/lib/datadog/tracing/configuration/ext.rb +9 -0
  107. data/lib/datadog/tracing/configuration/settings.rb +74 -0
  108. data/lib/datadog/tracing/contrib/action_pack/action_controller/instrumentation.rb +4 -4
  109. data/lib/datadog/tracing/contrib/action_pack/utils.rb +1 -2
  110. data/lib/datadog/tracing/contrib/active_job/log_injection.rb +21 -7
  111. data/lib/datadog/tracing/contrib/active_job/patcher.rb +5 -1
  112. data/lib/datadog/tracing/contrib/aws/instrumentation.rb +4 -2
  113. data/lib/datadog/tracing/contrib/ethon/easy_patch.rb +4 -1
  114. data/lib/datadog/tracing/contrib/excon/configuration/settings.rb +11 -3
  115. data/lib/datadog/tracing/contrib/faraday/configuration/settings.rb +11 -7
  116. data/lib/datadog/tracing/contrib/grape/configuration/settings.rb +7 -3
  117. data/lib/datadog/tracing/contrib/graphql/unified_trace.rb +22 -17
  118. data/lib/datadog/tracing/contrib/http/configuration/settings.rb +11 -3
  119. data/lib/datadog/tracing/contrib/httpclient/configuration/settings.rb +11 -3
  120. data/lib/datadog/tracing/contrib/httprb/configuration/settings.rb +11 -3
  121. data/lib/datadog/tracing/contrib/kafka/instrumentation/consumer.rb +66 -0
  122. data/lib/datadog/tracing/contrib/kafka/instrumentation/producer.rb +66 -0
  123. data/lib/datadog/tracing/contrib/kafka/patcher.rb +14 -0
  124. data/lib/datadog/tracing/contrib/karafka/framework.rb +30 -0
  125. data/lib/datadog/tracing/contrib/karafka/monitor.rb +11 -0
  126. data/lib/datadog/tracing/contrib/karafka/patcher.rb +32 -0
  127. data/lib/datadog/tracing/contrib/rack/middlewares.rb +59 -27
  128. data/lib/datadog/tracing/contrib/rack/route_inference.rb +53 -0
  129. data/lib/datadog/tracing/contrib/rails/middlewares.rb +2 -2
  130. data/lib/datadog/tracing/contrib/rest_client/request_patch.rb +4 -1
  131. data/lib/datadog/tracing/contrib/roda/instrumentation.rb +3 -1
  132. data/lib/datadog/tracing/contrib/sinatra/tracer_middleware.rb +3 -1
  133. data/lib/datadog/tracing/contrib/status_range_matcher.rb +7 -0
  134. data/lib/datadog/tracing/contrib/waterdrop/configuration/settings.rb +27 -0
  135. data/lib/datadog/tracing/contrib/waterdrop/distributed/propagation.rb +48 -0
  136. data/lib/datadog/tracing/contrib/waterdrop/ext.rb +17 -0
  137. data/lib/datadog/tracing/contrib/waterdrop/integration.rb +43 -0
  138. data/lib/datadog/tracing/contrib/waterdrop/middleware.rb +46 -0
  139. data/lib/datadog/tracing/contrib/waterdrop/patcher.rb +46 -0
  140. data/lib/datadog/tracing/contrib/waterdrop/producer.rb +50 -0
  141. data/lib/datadog/tracing/contrib/waterdrop.rb +37 -0
  142. data/lib/datadog/tracing/contrib.rb +1 -0
  143. data/lib/datadog/tracing/metadata/ext.rb +1 -1
  144. data/lib/datadog/tracing/transport/http/client.rb +12 -26
  145. data/lib/datadog/tracing/transport/trace_formatter.rb +11 -0
  146. data/lib/datadog/tracing/transport/traces.rb +3 -5
  147. data/lib/datadog/version.rb +2 -2
  148. data/lib/datadog.rb +2 -0
  149. metadata +78 -15
  150. data/lib/datadog/core/remote/transport/http/client.rb +0 -49
  151. data/lib/datadog/core/telemetry/transport/http/client.rb +0 -49
  152. data/lib/datadog/di/transport/http/client.rb +0 -47
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../../../core/transport/http/client'
4
+
5
+ module Datadog
6
+ module DataStreams
7
+ module Transport
8
+ module HTTP
9
+ # HTTP client for Data Streams Monitoring
10
+ class Client < Core::Transport::HTTP::Client
11
+ def send_stats_payload(request)
12
+ send_request(request) do |api, env|
13
+ # TODO how to make api have the derived type for steep?
14
+ api.send_stats(env) # steep:ignore
15
+ end
16
+ end
17
+ end
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,87 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../stats'
4
+ require_relative 'client'
5
+ require_relative '../../../core/transport/http/response'
6
+ require_relative '../../../core/transport/http/api/endpoint'
7
+ require_relative '../../../core/transport/http/api/spec'
8
+ require_relative '../../../core/transport/http/api/instance'
9
+
10
+ module Datadog
11
+ module DataStreams
12
+ module Transport
13
+ module HTTP
14
+ # HTTP transport behavior for Data Streams stats
15
+ module Stats
16
+ # Response from HTTP transport for DSM stats
17
+ class Response
18
+ include Datadog::Core::Transport::HTTP::Response
19
+
20
+ def initialize(http_response)
21
+ super
22
+ end
23
+ end
24
+
25
+ module API
26
+ # HTTP API Spec for DSM
27
+ class Spec < Core::Transport::HTTP::API::Spec
28
+ attr_accessor :stats
29
+
30
+ def send_stats(env, &block)
31
+ raise Core::Transport::HTTP::API::Spec::EndpointNotDefinedError.new('stats', self) if stats.nil?
32
+
33
+ stats.call(env, &block)
34
+ end
35
+
36
+ def encoder
37
+ # DSM handles encoding in the transport layer (MessagePack + gzip)
38
+ # so we don't need an encoder at the API level
39
+ nil
40
+ end
41
+ end
42
+
43
+ # HTTP API Instance for DSM
44
+ class Instance < Core::Transport::HTTP::API::Instance
45
+ def send_stats(env)
46
+ unless spec.is_a?(Stats::API::Spec)
47
+ raise Core::Transport::HTTP::API::Instance::EndpointNotSupportedError.new(
48
+ 'stats', self
49
+ )
50
+ end
51
+
52
+ spec.send_stats(env) do |request_env|
53
+ call(request_env)
54
+ end
55
+ end
56
+ end
57
+
58
+ # Endpoint for submitting DSM stats data
59
+ class Endpoint < Core::Transport::HTTP::API::Endpoint
60
+ def initialize(path)
61
+ super(:post, path)
62
+ end
63
+
64
+ def call(env, &block)
65
+ # Build request
66
+ env.verb = verb
67
+ env.path = path
68
+ env.body = env.request.parcel.data
69
+
70
+ # Send request
71
+ http_response = yield(env)
72
+
73
+ # Build response
74
+ Response.new(http_response)
75
+ end
76
+
77
+ def encoder
78
+ # DSM handles encoding in the transport layer
79
+ nil
80
+ end
81
+ end
82
+ end
83
+ end
84
+ end
85
+ end
86
+ end
87
+ end
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../../core/transport/http'
4
+ require_relative 'http/api'
5
+ require_relative 'http/client'
6
+ require_relative 'http/stats'
7
+ require_relative 'stats'
8
+
9
+ module Datadog
10
+ module DataStreams
11
+ module Transport
12
+ # HTTP transport for Data Streams Monitoring
13
+ module HTTP
14
+ module_function
15
+
16
+ # Builds a new Transport::HTTP::Client with default settings
17
+ def default(
18
+ agent_settings:,
19
+ logger:
20
+ )
21
+ Core::Transport::HTTP.build(
22
+ api_instance_class: Stats::API::Instance,
23
+ agent_settings: agent_settings,
24
+ logger: logger,
25
+ headers: {
26
+ 'Content-Type' => 'application/msgpack',
27
+ 'Content-Encoding' => 'gzip'
28
+ }
29
+ ) do |transport|
30
+ apis = API.defaults
31
+
32
+ transport.api API::V01, apis[API::V01], default: true
33
+
34
+ # Call block to apply any customization, if provided
35
+ yield(transport) if block_given?
36
+ end.to_transport(Transport::Stats::Transport)
37
+ end
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,60 @@
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
+
8
+ module Datadog
9
+ module DataStreams
10
+ module Transport
11
+ module Stats
12
+ # Parcel for encoded DSM stats payload
13
+ class EncodedParcel
14
+ include Datadog::Core::Transport::Parcel
15
+
16
+ def initialize(data)
17
+ @data = data
18
+ end
19
+
20
+ attr_reader :data
21
+ end
22
+
23
+ # Request for DSM stats
24
+ class Request < Datadog::Core::Transport::Request
25
+ end
26
+
27
+ # Transport for Data Streams Monitoring stats
28
+ class Transport
29
+ attr_reader :client, :apis, :current_api_id, :logger
30
+
31
+ def initialize(apis, default_api, logger:)
32
+ @apis = apis
33
+ @logger = logger
34
+ @default_api = default_api
35
+ @current_api_id = default_api
36
+
37
+ @client = DataStreams::Transport::HTTP::Client.new(current_api, logger: @logger)
38
+ end
39
+
40
+ def send_stats(payload)
41
+ # MessagePack encode and gzip compress the payload
42
+ msgpack_data = MessagePack.pack(payload)
43
+ compressed_data = Zlib.gzip(msgpack_data)
44
+
45
+ # Create parcel and request
46
+ parcel = EncodedParcel.new(compressed_data)
47
+ request = Request.new(parcel)
48
+
49
+ # Send to agent
50
+ client.send_stats_payload(request)
51
+ end
52
+
53
+ def current_api
54
+ apis[@current_api_id]
55
+ end
56
+ end
57
+ end
58
+ end
59
+ end
60
+ 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
@@ -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
@@ -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
@@ -195,8 +215,7 @@ module Datadog
195
215
  caller_locations: caller_locs,
196
216
  return_value: rv, duration: duration, exception: exc,)
197
217
 
198
- # & is to stop steep complaints, block is always present here.
199
- block&.call(context)
218
+ responder.probe_executed_callback(context)
200
219
  if exc
201
220
  raise exc
202
221
  else
@@ -258,11 +277,7 @@ module Datadog
258
277
  # not for eval'd code, unless the eval'd code is associated with
259
278
  # a file name and client invokes this method with the correct
260
279
  # 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
-
280
+ def hook_line(probe, responder)
266
281
  lock.synchronize do
267
282
  if probe.instrumentation_trace_point
268
283
  # Already instrumented, warn?
@@ -367,14 +382,44 @@ module Datadog
367
382
 
368
383
  if continue
369
384
  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)
385
+ begin
386
+ context = Context.new(
387
+ locals: Instrumenter.get_local_variables(tp),
388
+ target_self: tp.self,
389
+ probe: probe, settings: settings, serializer: serializer,
390
+ path: tp.path,
391
+ caller_locations: caller_locations,
392
+ )
393
+ continue = condition.satisfied?(context)
394
+ rescue => exc
395
+ # Evaluation error exception can be raised for "expected"
396
+ # errors, we probably need another setting to control whether
397
+ # these exceptions are propagated.
398
+ raise if settings.dynamic_instrumentation.internal.propagate_all_exceptions &&
399
+ !exc.is_a?(DI::Error::ExpressionEvaluationError)
400
+
401
+ continue = false
402
+ if context
403
+ # We want to report evaluation errors for conditions
404
+ # as probe snapshots. However, if we failed to create
405
+ # the context, we won't be able to report anything as
406
+ # the probe notifier builder requires a context.
407
+ begin
408
+ responder.probe_condition_evaluation_failed_callback(context, condition, exc)
409
+ rescue
410
+ raise if settings.dynamic_instrumentation.internal.propagate_all_exceptions
411
+
412
+ # TODO log / report via telemetry?
413
+ end
414
+ else
415
+ _ = 42 # stop standard from wrecking this code
416
+
417
+ raise if settings.dynamic_instrumentation.internal.propagate_all_exceptions
418
+
419
+ # TODO log / report via telemetry?
420
+ # If execution gets here, there is probably a bug in the tracer.
421
+ end
422
+ end
378
423
  end
379
424
  end
380
425
 
@@ -393,8 +438,7 @@ module Datadog
393
438
  caller_locations: caller_locations,
394
439
  )
395
440
 
396
- # & is to stop steep complaints, block is always present here.
397
- block&.call(context)
441
+ responder.probe_executed_callback(context)
398
442
  end
399
443
  rescue => exc
400
444
  raise if settings.dynamic_instrumentation.internal.propagate_all_exceptions
@@ -409,9 +453,11 @@ module Datadog
409
453
  # TODO test this path
410
454
  end
411
455
 
412
- # TODO internal check - remove or use a proper exception
456
+ # Internal sanity check - untargeted trace points create a huge
457
+ # performance impact, and we absolutely do not want to set them
458
+ # accidentally.
413
459
  if !iseq && !permit_untargeted_trace_points
414
- raise "Trying to use an untargeted trace point when user did not permit it"
460
+ raise Error::InternalError, "Trying to use an untargeted trace point when user did not permit it"
415
461
  end
416
462
 
417
463
  lock.synchronize do
@@ -443,11 +489,11 @@ module Datadog
443
489
  end
444
490
  end
445
491
 
446
- def hook(probe, &block)
492
+ def hook(probe, responder)
447
493
  if probe.method?
448
- hook_method(probe, &block)
494
+ hook_method(probe, responder)
449
495
  elsif probe.line?
450
- hook_line(probe, &block)
496
+ hook_line(probe, responder)
451
497
  else
452
498
  # TODO add test coverage for this path
453
499
  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
@@ -101,7 +101,7 @@ module Datadog
101
101
  end
102
102
 
103
103
  begin
104
- instrumenter.hook(probe, &method(:probe_executed_callback))
104
+ instrumenter.hook(probe, self)
105
105
 
106
106
  @installed_probes[probe.id] = probe
107
107
  payload = probe_notification_builder.build_installed(probe)
@@ -184,7 +184,7 @@ module Datadog
184
184
  begin
185
185
  # TODO is it OK to hook from trace point handler?
186
186
  # TODO the class is now defined, but can hooking still fail?
187
- instrumenter.hook(probe, &method(:probe_executed_callback))
187
+ instrumenter.hook(probe, self)
188
188
  @pending_probes.delete(probe.id)
189
189
  break
190
190
  rescue Error::DITargetNotDefined
@@ -242,6 +242,14 @@ module Datadog
242
242
  probe_notifier_worker.add_snapshot(payload)
243
243
  end
244
244
 
245
+ def probe_condition_evaluation_failed_callback(context, expr, exc)
246
+ probe = context.probe
247
+ if probe.condition_evaluation_failed_rate_limiter&.allow?
248
+ payload = probe_notification_builder.build_condition_evaluation_failed(context, expr, exc)
249
+ probe_notifier_worker.add_snapshot(payload)
250
+ end
251
+ end
252
+
245
253
  # Class/module definition trace point (:end type).
246
254
  # Used to install hooks when the target classes/modules aren't yet
247
255
  # defined when the hook request is received.