datadog 2.32.0 → 2.33.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 (46) hide show
  1. checksums.yaml +4 -4
  2. data/ext/datadog_profiling_native_extension/clock_id.h +9 -1
  3. data/ext/datadog_profiling_native_extension/clock_id_from_mach.c +73 -0
  4. data/ext/datadog_profiling_native_extension/clock_id_from_pthread.c +1 -1
  5. data/ext/datadog_profiling_native_extension/collectors_thread_context.c +5 -1
  6. data/ext/datadog_profiling_native_extension/extconf.rb +3 -0
  7. data/ext/datadog_profiling_native_extension/stack_recorder.c +3 -9
  8. data/ext/datadog_profiling_native_extension/time_helpers.h +1 -0
  9. data/ext/libdatadog_api/crashtracker.c +2 -0
  10. data/ext/libdatadog_extconf_helpers.rb +1 -1
  11. data/lib/datadog/ai_guard/autoload.rb +10 -0
  12. data/lib/datadog/ai_guard/component.rb +1 -1
  13. data/lib/datadog/ai_guard/contrib/auto_instrument.rb +24 -0
  14. data/lib/datadog/ai_guard/contrib/rack/integration.rb +42 -0
  15. data/lib/datadog/ai_guard/contrib/rack/patcher.rb +26 -0
  16. data/lib/datadog/ai_guard/contrib/rack/request_middleware.rb +83 -0
  17. data/lib/datadog/ai_guard/contrib/rails/integration.rb +41 -0
  18. data/lib/datadog/ai_guard/contrib/rails/patcher.rb +97 -0
  19. data/lib/datadog/ai_guard/evaluation.rb +1 -0
  20. data/lib/datadog/ai_guard/ext.rb +1 -0
  21. data/lib/datadog/ai_guard.rb +8 -0
  22. data/lib/datadog/appsec/contrib/aws_lambda/gateway/watcher.rb +75 -0
  23. data/lib/datadog/appsec/contrib/aws_lambda/integration.rb +39 -0
  24. data/lib/datadog/appsec/contrib/aws_lambda/patcher.rb +30 -0
  25. data/lib/datadog/appsec/contrib/aws_lambda/waf_addresses.rb +111 -0
  26. data/lib/datadog/appsec.rb +1 -0
  27. data/lib/datadog/core/configuration/settings.rb +10 -0
  28. data/lib/datadog/core/configuration/supported_configurations.rb +2 -0
  29. data/lib/datadog/core/environment/ext.rb +1 -0
  30. data/lib/datadog/core/environment/socket.rb +13 -0
  31. data/lib/datadog/opentelemetry/metrics.rb +10 -1
  32. data/lib/datadog/opentelemetry/sdk/id_generator.rb +16 -10
  33. data/lib/datadog/profiling/component.rb +0 -1
  34. data/lib/datadog/profiling/stack_recorder.rb +0 -4
  35. data/lib/datadog/symbol_database/extractor.rb +17 -26
  36. data/lib/datadog/symbol_database/scope.rb +16 -12
  37. data/lib/datadog/symbol_database/scope_batcher.rb +280 -0
  38. data/lib/datadog/symbol_database/service_version.rb +4 -4
  39. data/lib/datadog/symbol_database/uploader.rb +3 -0
  40. data/lib/datadog/tracing/contrib/rack/configuration/settings.rb +6 -0
  41. data/lib/datadog/tracing/contrib/rack/ext.rb +27 -0
  42. data/lib/datadog/tracing/contrib/rack/trace_proxy_middleware.rb +117 -1
  43. data/lib/datadog/tracing/tracer.rb +1 -3
  44. data/lib/datadog/version.rb +1 -1
  45. metadata +19 -7
  46. data/ext/datadog_profiling_native_extension/clock_id_noop.c +0 -21
@@ -0,0 +1,39 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'patcher'
4
+ require_relative '../integration'
5
+
6
+ module Datadog
7
+ module AppSec
8
+ module Contrib
9
+ module AwsLambda
10
+ class Integration
11
+ include Datadog::AppSec::Contrib::Integration
12
+
13
+ register_as :aws_lambda, auto_patch: false
14
+
15
+ # NOTE: AWS Lambda is a runtime environment, not an installable gem
16
+ def self.version
17
+ nil
18
+ end
19
+
20
+ def self.loaded?
21
+ true
22
+ end
23
+
24
+ def self.compatible?
25
+ super
26
+ end
27
+
28
+ def self.auto_instrument?
29
+ false
30
+ end
31
+
32
+ def patcher
33
+ Patcher
34
+ end
35
+ end
36
+ end
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../../monitor'
4
+ require_relative 'gateway/watcher'
5
+
6
+ module Datadog
7
+ module AppSec
8
+ module Contrib
9
+ module AwsLambda
10
+ module Patcher
11
+ module_function
12
+
13
+ def patched?
14
+ Patcher.instance_variable_get(:@patched)
15
+ end
16
+
17
+ def target_version
18
+ Integration.version
19
+ end
20
+
21
+ def patch
22
+ Monitor::Gateway::Watcher.watch
23
+ Gateway::Watcher.watch
24
+ Patcher.instance_variable_set(:@patched, true)
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,111 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'uri'
4
+
5
+ require_relative '../../utils/http/media_type'
6
+ require_relative '../../utils/http/body'
7
+ require_relative '../../../core/utils/base64'
8
+ require_relative '../../../core/header_collection'
9
+ require_relative '../../../tracing/client_ip'
10
+
11
+ module Datadog
12
+ module AppSec
13
+ module Contrib
14
+ module AwsLambda
15
+ # Extracts WAF input addresses from normalized AWS Lambda API Gateway event payloads.
16
+ # @api private
17
+ module WAFAddresses
18
+ module_function
19
+
20
+ def from_request(payload)
21
+ return {} if payload.nil? || payload.empty?
22
+
23
+ headers = parse_headers(payload)
24
+ data = {
25
+ 'server.request.cookies' => parse_cookies(payload, headers),
26
+ 'server.request.query' => payload['query'],
27
+ 'server.request.uri.raw' => build_fullpath(payload),
28
+ 'server.request.headers' => headers,
29
+ 'server.request.headers.no_cookies' => headers.dup.tap { |h| h.delete('cookie') },
30
+ 'http.client_ip' => extract_client_ip(payload['source_ip'], headers),
31
+ 'server.request.method' => payload['method'],
32
+ 'server.request.body' => parse_body(payload, headers),
33
+ 'server.request.path_params' => payload['path_params']
34
+ }
35
+
36
+ data.compact!
37
+ data
38
+ end
39
+
40
+ def from_response(payload)
41
+ return {} if payload.nil? || payload.empty?
42
+
43
+ headers = parse_headers(payload)
44
+ data = {
45
+ 'server.response.status' => payload['statusCode']&.to_s,
46
+ 'server.response.headers' => headers,
47
+ 'server.response.headers.no_cookies' => headers.dup.tap { |h| h.delete('set-cookie') }
48
+ }
49
+
50
+ data.compact!
51
+ data
52
+ end
53
+
54
+ def parse_headers(payload)
55
+ (payload['headers'] || {}).each_with_object({}) do |(key, value), hash|
56
+ hash[key.downcase] = value
57
+ end
58
+ end
59
+
60
+ def parse_cookies(payload, headers)
61
+ raw_pairs = payload['cookies'] || headers['cookie']&.split(';')
62
+ return unless raw_pairs
63
+
64
+ raw_pairs.each_with_object({}) do |pair, hash|
65
+ name, value = pair.strip.split('=', 2)
66
+ hash[name] = value if name
67
+ end
68
+ end
69
+
70
+ def build_fullpath(payload)
71
+ path = payload['path']
72
+ return unless path
73
+
74
+ query_string = build_query_string(payload)
75
+ query_string ? "#{path}?#{query_string}" : path
76
+ end
77
+
78
+ def build_query_string(payload)
79
+ query_string = payload['query_string']
80
+ return query_string if query_string && !query_string.empty?
81
+
82
+ query = payload['query']
83
+ return if query.nil? || query.empty?
84
+
85
+ URI.encode_www_form(query)
86
+ end
87
+
88
+ def extract_client_ip(remote_ip, headers)
89
+ header_collection = Datadog::Core::HeaderCollection.from_hash(headers)
90
+ Datadog::Tracing::ClientIp.extract_client_ip(header_collection, remote_ip)
91
+ end
92
+
93
+ def parse_body(payload, headers)
94
+ body = payload['body']
95
+ return unless body
96
+
97
+ body = Core::Utils::Base64.strict_decode64(body) if payload['base64_encoded']
98
+
99
+ content_type = headers['content-type']
100
+ return unless content_type
101
+
102
+ media_type = AppSec::Utils::HTTP::MediaType.parse(content_type)
103
+ return unless media_type
104
+
105
+ AppSec::Utils::HTTP::Body.parse(body, media_type: media_type)
106
+ end
107
+ end
108
+ end
109
+ end
110
+ end
111
+ end
@@ -63,5 +63,6 @@ require_relative 'appsec/contrib/graphql/integration'
63
63
  require_relative 'appsec/contrib/faraday/integration'
64
64
  require_relative 'appsec/contrib/excon/integration'
65
65
  require_relative 'appsec/contrib/rest_client/integration'
66
+ require_relative 'appsec/contrib/aws_lambda/integration'
66
67
 
67
68
  require_relative 'appsec/autoload'
@@ -171,6 +171,16 @@ module Datadog
171
171
  o.env Core::Environment::Ext::ENV_ENVIRONMENT
172
172
  end
173
173
 
174
+ # Override the hostname reported by this process.
175
+ # When `report_hostname` is enabled, sets the hostname on traces and
176
+ # the `host.name` resource attribute in OpenTelemetry.
177
+ # @default `DD_HOSTNAME` environment variable, otherwise `nil`
178
+ # @return [String,nil]
179
+ option :hostname do |o|
180
+ o.type :string, nilable: true
181
+ o.env Core::Environment::Ext::ENV_HOSTNAME
182
+ end
183
+
174
184
  # Configuration for container environments. For internal use only.
175
185
  # @!visibility private
176
186
  settings :container do
@@ -61,6 +61,7 @@ module Datadog
61
61
  "DD_GIT_COMMIT_SHA",
62
62
  "DD_GIT_REPOSITORY_URL",
63
63
  "DD_HEALTH_METRICS_ENABLED",
64
+ "DD_HOSTNAME",
64
65
  "DD_INJECTION_ENABLED",
65
66
  "DD_INJECT_FORCE",
66
67
  "DD_INSTRUMENTATION_INSTALL_ID",
@@ -228,6 +229,7 @@ module Datadog
228
229
  "DD_TRACE_HTTP_ENABLED",
229
230
  "DD_TRACE_HTTP_ERROR_STATUS_CODES",
230
231
  "DD_TRACE_HTTP_SERVER_ERROR_STATUSES",
232
+ "DD_TRACE_INFERRED_PROXY_SERVICES_ENABLED",
231
233
  "DD_TRACE_KAFKA_ANALYTICS_ENABLED",
232
234
  "DD_TRACE_KAFKA_ANALYTICS_SAMPLE_RATE",
233
235
  "DD_TRACE_KAFKA_ENABLED",
@@ -18,6 +18,7 @@ module Datadog
18
18
  ENV_API_KEY = 'DD_API_KEY'
19
19
  ENV_ENVIRONMENT = 'DD_ENV'
20
20
  ENV_EXTERNAL_ENV = 'DD_EXTERNAL_ENV'
21
+ ENV_HOSTNAME = 'DD_HOSTNAME'
21
22
  ENV_SERVICE = 'DD_SERVICE'
22
23
  ENV_SITE = 'DD_SITE'
23
24
  ENV_TAGS = 'DD_TAGS'
@@ -18,6 +18,19 @@ module Datadog
18
18
 
19
19
  @hostname ||= ::Socket.gethostname.freeze
20
20
  end
21
+
22
+ # Returns the resolved hostname when `report_hostname` is enabled:
23
+ # the configured DD_HOSTNAME if set, otherwise the system hostname.
24
+ # Returns nil when `report_hostname` is disabled or no hostname is available.
25
+ def resolved_hostname(settings)
26
+ return nil unless settings.tracing.report_hostname
27
+
28
+ configured = settings.hostname
29
+ return configured if configured && !configured.empty?
30
+
31
+ resolved_hostname = hostname
32
+ resolved_hostname if resolved_hostname && !resolved_hostname.empty?
33
+ end
21
34
  end
22
35
  end
23
36
  end
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require_relative '../core/configuration/ext'
4
+ require_relative '../core/environment/socket'
4
5
 
5
6
  module Datadog
6
7
  module OpenTelemetry
@@ -42,7 +43,6 @@ module Datadog
42
43
 
43
44
  def create_resource
44
45
  resource_attributes = {}
45
- resource_attributes['host.name'] = Datadog::Core::Environment::Socket.hostname if @settings.tracing.report_hostname
46
46
 
47
47
  @settings.tags&.each do |key, value|
48
48
  otel_key = case key
@@ -58,6 +58,15 @@ module Datadog
58
58
  resource_attributes['deployment.environment'] = @settings.env if @settings.env
59
59
  resource_attributes['service.version'] = @settings.version if @settings.version
60
60
 
61
+ hostname = Datadog::Core::Environment::Socket.resolved_hostname(@settings)
62
+ if hostname
63
+ if hostname == @settings.hostname
64
+ resource_attributes['host.name'] = hostname
65
+ elsif !resource_attributes.key?('host.name')
66
+ resource_attributes['host.name'] = hostname
67
+ end
68
+ end
69
+
61
70
  ::OpenTelemetry::SDK::Resources::Resource.create(resource_attributes)
62
71
  end
63
72
 
@@ -1,23 +1,29 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative '../../tracing/utils'
4
+
3
5
  module Datadog
4
6
  module OpenTelemetry
5
7
  module SDK
6
- # Generates Datadog-compatible IDs for OpenTelemetry traces.
7
- # OpenTelemetry traces already produce Datadog-compatible IDs.
8
+ # Generates Datadog-compatible trace IDs for OpenTelemetry spans.
9
+ #
10
+ # Reuses the same 128-bit ID format as non-OTel Datadog tracing:
11
+ # [32-bit seconds since Epoch | 32 zero bits | 64 random bits]
12
+ #
13
+ # When DD_TRACE_128_BIT_TRACEID_GENERATION_ENABLED is false the high 64
14
+ # bits are zero, preserving the OTel 16-byte wire format while keeping
15
+ # backward compatibility with 64-bit Datadog trace IDs.
8
16
  class IdGenerator
9
17
  class << self
10
18
  include ::OpenTelemetry::Trace
11
19
 
12
- # Generates a valid trace identifier, a 16-byte string with at least one
13
- # non-zero byte.
14
- #
15
- # @return [String] a valid trace ID.
20
+ # @return [String] a valid 16-byte trace ID.
16
21
  def generate_trace_id
17
- loop do
18
- id = Random.bytes(8) # DEV: Change to 16 (16*8-byte) when 128-bit trace_id is supported.
19
- return id unless id == ::OpenTelemetry::Trace::INVALID_SPAN_ID
20
- end
22
+ trace_id = Tracing::Utils::TraceId.next_id
23
+ [
24
+ Tracing::Utils::TraceId.to_high_order(trace_id),
25
+ Tracing::Utils::TraceId.to_low_order(trace_id),
26
+ ].pack('Q>Q>')
21
27
  end
22
28
  end
23
29
  end
@@ -49,7 +49,6 @@ module Datadog
49
49
  valid_cpu_sampling_interval(settings.profiling.advanced.experimental_cpu_sampling_interval_ms, logger)
50
50
 
51
51
  recorder = Datadog::Profiling::StackRecorder.new(
52
- cpu_time_enabled: RUBY_PLATFORM.include?("linux"), # Only supported on Linux currently
53
52
  alloc_samples_enabled: allocation_profiling_enabled,
54
53
  heap_samples_enabled: heap_profiling_enabled,
55
54
  heap_size_enabled: heap_size_profiling_enabled,
@@ -9,7 +9,6 @@ module Datadog
9
9
  # Methods prefixed with _native_ are implemented in `stack_recorder.c`
10
10
  class StackRecorder
11
11
  def initialize(
12
- cpu_time_enabled:,
13
12
  alloc_samples_enabled:,
14
13
  heap_samples_enabled:,
15
14
  heap_size_enabled:,
@@ -27,7 +26,6 @@ module Datadog
27
26
 
28
27
  self.class._native_initialize(
29
28
  self_instance: self,
30
- cpu_time_enabled: cpu_time_enabled,
31
29
  alloc_samples_enabled: alloc_samples_enabled,
32
30
  heap_samples_enabled: heap_samples_enabled,
33
31
  heap_size_enabled: heap_size_enabled,
@@ -38,7 +36,6 @@ module Datadog
38
36
  end
39
37
 
40
38
  def self.for_testing(
41
- cpu_time_enabled: true,
42
39
  alloc_samples_enabled: false,
43
40
  heap_samples_enabled: false,
44
41
  heap_size_enabled: false,
@@ -48,7 +45,6 @@ module Datadog
48
45
  **options
49
46
  )
50
47
  new(
51
- cpu_time_enabled: cpu_time_enabled,
52
48
  alloc_samples_enabled: alloc_samples_enabled,
53
49
  heap_samples_enabled: heap_samples_enabled,
54
50
  heap_size_enabled: heap_size_enabled,
@@ -9,10 +9,6 @@ module Datadog
9
9
  module SymbolDatabase
10
10
  # Extracts symbol metadata from loaded Ruby modules and classes via introspection.
11
11
  #
12
- # Instance created by Component with injected dependencies (logger, settings,
13
- # telemetry). All methods are instance methods accessing @logger, @settings,
14
- # @telemetry directly — no parameter threading needed.
15
- #
16
12
  # Uses Ruby's reflection APIs (Module#constants, Class#instance_methods, Method#parameters)
17
13
  # to build hierarchical Scope structures representing code organization.
18
14
  # Filters to user code only (excludes gems, stdlib, test files).
@@ -46,10 +42,9 @@ module Datadog
46
42
  # for post-hoc diagnosis, return nil or empty array. One bad method/module
47
43
  # doesn't kill the entire class extraction.
48
44
  #
49
- # 3. **Top-level entry rescues** (`rescue => e` with logging + telemetry):
45
+ # 3. **Top-level entry rescues** (`rescue => e` with logging):
50
46
  # extract() and extract_all() are the error boundaries. Any exception that
51
- # escapes layers 1-2 is caught here, logged, and tracked via telemetry.
52
- # These are the only rescue blocks that increment telemetry counters.
47
+ # escapes layers 1-2 is caught here and logged.
53
48
  #
54
49
  # @api private
55
50
  class Extractor
@@ -71,11 +66,11 @@ module Datadog
71
66
  EXCLUDED_COMMON_MODULES = ['Kernel', 'PP::', 'JSON::', 'Enumerable', 'Comparable'].freeze
72
67
 
73
68
  # RubyVM::InstructionSequence#trace_points event types included when
74
- # computing injectable lines on METHOD scopes.
69
+ # computing targetable lines on METHOD scopes.
75
70
  # :line — any line with executable bytecode (primary line probe target)
76
71
  # :return — last expression before method returns (DI instruments return events)
77
72
  # :call excluded — method entry is handled by method probes, not line probes
78
- INJECTABLE_LINE_EVENTS = [:line, :return].freeze
73
+ TARGETABLE_LINE_EVENTS = [:line, :return].freeze
79
74
 
80
75
  # Cached unbound Module#singleton_class? — dispatched explicitly so user classes
81
76
  # that define their own `singleton_class?` (e.g. with required arguments) cannot
@@ -87,11 +82,9 @@ module Datadog
87
82
 
88
83
  # @param logger [Logger] Logger instance (SymbolDatabase::Logger facade or compatible)
89
84
  # @param settings [Configuration::Settings] Tracer settings
90
- # @param telemetry [Telemetry, nil] Optional telemetry for metrics
91
- def initialize(logger:, settings:, telemetry: nil)
85
+ def initialize(logger:, settings:)
92
86
  @logger = logger
93
87
  @settings = settings
94
- @telemetry = telemetry
95
88
  end
96
89
 
97
90
  # Extract symbols from a single module or class.
@@ -125,7 +118,6 @@ module Datadog
125
118
  wrap_in_file_scope(source_file, [inner_scope])
126
119
  rescue => e
127
120
  @logger.debug { "symdb: failed to extract #{mod_name || '<unknown>'}: #{e.class}: #{e.message}" }
128
- @telemetry&.inc('tracers', 'symbol_database.extract_error', 1)
129
121
  nil
130
122
  end
131
123
 
@@ -147,7 +139,6 @@ module Datadog
147
139
  convert_trees_to_scopes(file_trees)
148
140
  rescue => e
149
141
  @logger.debug { "symdb: error in extract_all: #{e.class}: #{e.message}" }
150
- @telemetry&.inc('tracers', 'symbol_database.extract_all_error', 1)
151
142
  []
152
143
  end
153
144
 
@@ -399,7 +390,7 @@ module Datadog
399
390
  location = method.source_location
400
391
  next unless location && location[0]
401
392
  starts << location[1]
402
- _ranges, method_end = extract_injectable_lines(method, location[1])
393
+ _ranges, method_end = extract_targetable_lines(method, location[1])
403
394
  ends << method_end
404
395
  end
405
396
 
@@ -491,7 +482,7 @@ module Datadog
491
482
  source_file, line = location
492
483
  return nil unless user_code_path?(source_file) # Skip gem/stdlib methods
493
484
 
494
- injectable_lines, end_line = extract_injectable_lines(method, line)
485
+ targetable_lines, end_line = extract_targetable_lines(method, line)
495
486
 
496
487
  Scope.new(
497
488
  scope_type: 'METHOD',
@@ -499,7 +490,7 @@ module Datadog
499
490
  source_file: source_file,
500
491
  start_line: line,
501
492
  end_line: end_line,
502
- injectible_lines: injectable_lines,
493
+ targetable_lines: targetable_lines,
503
494
  language_specifics: {
504
495
  visibility: method_visibility(klass, method_name),
505
496
  method_type: method_type.to_s,
@@ -526,29 +517,29 @@ module Datadog
526
517
  end
527
518
  end
528
519
 
529
- # Extract injectable lines and end_line from a method's bytecode.
520
+ # Extract targetable lines and end_line from a method's bytecode.
530
521
  # Returns [ranges, end_line] where ranges is an array of {start:, end:} hashes
531
522
  # or nil if iseq is unavailable (C-extension methods).
532
523
  # @param method [Method, UnboundMethod] The method
533
524
  # @param start_line [Integer] Fallback end_line if iseq unavailable
534
525
  # @return [Array(Array<Hash>, Integer), Array(nil, Integer)]
535
- def extract_injectable_lines(method, start_line)
526
+ def extract_targetable_lines(method, start_line)
536
527
  iseq = RubyVM::InstructionSequence.of(method) # steep:ignore
537
528
  unless iseq
538
- @logger.debug { "symdb: no iseq for #{method.name} (C extension or native), skipping injectable lines" }
529
+ @logger.debug { "symdb: no iseq for #{method.name} (C extension or native), skipping targetable lines" }
539
530
  return [nil, start_line]
540
531
  end
541
532
 
542
533
  lines = iseq.trace_points
543
- .select { |_, event| INJECTABLE_LINE_EVENTS.include?(event) }
534
+ .select { |_, event| TARGETABLE_LINE_EVENTS.include?(event) }
544
535
  .map(&:first)
545
536
  .uniq
546
537
  .sort
547
538
 
548
539
  end_line = lines.max || start_line
549
- ranges = build_injectable_ranges(lines)
540
+ ranges = build_targetable_ranges(lines)
550
541
  result = ranges.empty? ? nil : ranges
551
- @logger.debug { "symdb: #{method.name} injectable lines: #{result ? "#{ranges.size} range(s), lines #{lines.first}..#{lines.last}" : 'none (no matching events)'}" }
542
+ @logger.debug { "symdb: #{method.name} targetable lines: #{result ? "#{ranges.size} range(s), lines #{lines.first}..#{lines.last}" : 'none (no matching events)'}" }
552
543
  [result, end_line]
553
544
  end
554
545
 
@@ -556,7 +547,7 @@ module Datadog
556
547
  # [4, 5, 6, 8, 10, 11] => [{start: 4, end: 6}, {start: 8, end: 8}, {start: 10, end: 11}]
557
548
  # @param lines [Array<Integer>] Sorted, deduplicated line numbers
558
549
  # @return [Array<Hash>] Array of {start:, end:} range hashes
559
- def build_injectable_ranges(lines)
550
+ def build_targetable_ranges(lines)
560
551
  return [] if lines.empty?
561
552
 
562
553
  ranges = []
@@ -840,7 +831,7 @@ module Datadog
840
831
 
841
832
  source_file, line = location
842
833
 
843
- injectable_lines, end_line = extract_injectable_lines(method, line)
834
+ targetable_lines, end_line = extract_targetable_lines(method, line)
844
835
 
845
836
  Scope.new(
846
837
  scope_type: 'METHOD',
@@ -848,7 +839,7 @@ module Datadog
848
839
  source_file: source_file,
849
840
  start_line: line,
850
841
  end_line: end_line,
851
- injectible_lines: injectable_lines,
842
+ targetable_lines: targetable_lines,
852
843
  language_specifics: {
853
844
  visibility: klass ? method_visibility(klass, method_name) : 'public', # steep:ignore
854
845
  method_type: 'instance',
@@ -22,9 +22,11 @@ module Datadog
22
22
  # - nil: not computed (source unreadable, native/C-extension method)
23
23
  # - []: computed but no executable lines found (comments/whitespace only)
24
24
  # - non-empty: computed, contains executable line ranges
25
- # nil and [] both serialize as injectible_lines?: false on METHOD
26
- # scopes. Key is absent on non-METHOD scopes.
27
- :injectible_lines,
25
+ # nil and [] both serialize as has_injectible_lines: false on METHOD
26
+ # scopes. Key is absent on non-METHOD scopes. The wire format key
27
+ # name keeps the historical spelling +injectible+ for backend
28
+ # compatibility; the Ruby identifier is +targetable_lines+.
29
+ :targetable_lines,
28
30
  :language_specifics, :symbols, :scopes
29
31
 
30
32
  # Initialize a new Scope
@@ -33,7 +35,7 @@ module Datadog
33
35
  # @param source_file [String, nil] Path to source file
34
36
  # @param start_line [Integer, nil] Starting line number (UNKNOWN_MIN_LINE for unknown)
35
37
  # @param end_line [Integer, nil] Ending line number (UNKNOWN_MAX_LINE for entire file)
36
- # @param injectible_lines [Array<Hash>, nil] Ranges of executable lines [{start:, end:}]
38
+ # @param targetable_lines [Array<Hash>, nil] Ranges of executable lines [{start:, end:}]
37
39
  # @param language_specifics [Hash, nil] Ruby-specific metadata
38
40
  # @param symbols [Array<Symbol>, nil] Symbols defined in this scope
39
41
  # @param scopes [Array<Scope>, nil] Nested child scopes
@@ -43,7 +45,7 @@ module Datadog
43
45
  source_file: nil,
44
46
  start_line: nil,
45
47
  end_line: nil,
46
- injectible_lines: nil,
48
+ targetable_lines: nil,
47
49
  language_specifics: nil,
48
50
  symbols: nil,
49
51
  scopes: nil
@@ -53,15 +55,15 @@ module Datadog
53
55
  @source_file = source_file
54
56
  @start_line = start_line
55
57
  @end_line = end_line
56
- @injectible_lines = injectible_lines
58
+ @targetable_lines = targetable_lines
57
59
  @language_specifics = language_specifics || {}
58
60
  @symbols = symbols || []
59
61
  @scopes = scopes || []
60
62
  end
61
63
 
62
- # @return [Boolean] true when injectible_lines is non-nil and non-empty
63
- def injectible_lines?
64
- !injectible_lines.nil? && !injectible_lines.empty?
64
+ # @return [Boolean] true when targetable_lines is non-nil and non-empty
65
+ def targetable_lines?
66
+ !targetable_lines.nil? && !targetable_lines.empty?
65
67
  end
66
68
 
67
69
  # Convert scope to Hash for JSON serialization.
@@ -79,11 +81,13 @@ module Datadog
79
81
  scopes: scopes.empty? ? nil : scopes.map(&:to_h),
80
82
  }
81
83
  h.compact!
82
- # Injectable lines only on METHOD scopes (per spec — not on CLASS/MODULE/FILE).
84
+ # Targetable lines only on METHOD scopes (per spec — not on CLASS/MODULE/FILE).
83
85
  # Always emit has_injectible_lines (even when false) on METHOD scopes.
86
+ # Wire format keeps the historical spelling +injectible+; Ruby identifier
87
+ # is +targetable_lines+.
84
88
  if scope_type == 'METHOD'
85
- h[:has_injectible_lines] = injectible_lines? # steep:ignore ArgumentTypeMismatch
86
- h[:injectible_lines] = injectible_lines if injectible_lines && !injectible_lines.empty?
89
+ h[:has_injectible_lines] = targetable_lines? # steep:ignore ArgumentTypeMismatch
90
+ h[:injectible_lines] = targetable_lines if targetable_lines && !targetable_lines.empty?
87
91
  end
88
92
  h
89
93
  end