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.
- checksums.yaml +4 -4
- data/ext/datadog_profiling_native_extension/clock_id.h +9 -1
- data/ext/datadog_profiling_native_extension/clock_id_from_mach.c +73 -0
- data/ext/datadog_profiling_native_extension/clock_id_from_pthread.c +1 -1
- data/ext/datadog_profiling_native_extension/collectors_thread_context.c +5 -1
- data/ext/datadog_profiling_native_extension/extconf.rb +3 -0
- data/ext/datadog_profiling_native_extension/stack_recorder.c +3 -9
- data/ext/datadog_profiling_native_extension/time_helpers.h +1 -0
- data/ext/libdatadog_api/crashtracker.c +2 -0
- data/ext/libdatadog_extconf_helpers.rb +1 -1
- data/lib/datadog/ai_guard/autoload.rb +10 -0
- data/lib/datadog/ai_guard/component.rb +1 -1
- data/lib/datadog/ai_guard/contrib/auto_instrument.rb +24 -0
- data/lib/datadog/ai_guard/contrib/rack/integration.rb +42 -0
- data/lib/datadog/ai_guard/contrib/rack/patcher.rb +26 -0
- data/lib/datadog/ai_guard/contrib/rack/request_middleware.rb +83 -0
- data/lib/datadog/ai_guard/contrib/rails/integration.rb +41 -0
- data/lib/datadog/ai_guard/contrib/rails/patcher.rb +97 -0
- data/lib/datadog/ai_guard/evaluation.rb +1 -0
- data/lib/datadog/ai_guard/ext.rb +1 -0
- data/lib/datadog/ai_guard.rb +8 -0
- data/lib/datadog/appsec/contrib/aws_lambda/gateway/watcher.rb +75 -0
- data/lib/datadog/appsec/contrib/aws_lambda/integration.rb +39 -0
- data/lib/datadog/appsec/contrib/aws_lambda/patcher.rb +30 -0
- data/lib/datadog/appsec/contrib/aws_lambda/waf_addresses.rb +111 -0
- data/lib/datadog/appsec.rb +1 -0
- data/lib/datadog/core/configuration/settings.rb +10 -0
- data/lib/datadog/core/configuration/supported_configurations.rb +2 -0
- data/lib/datadog/core/environment/ext.rb +1 -0
- data/lib/datadog/core/environment/socket.rb +13 -0
- data/lib/datadog/opentelemetry/metrics.rb +10 -1
- data/lib/datadog/opentelemetry/sdk/id_generator.rb +16 -10
- data/lib/datadog/profiling/component.rb +0 -1
- data/lib/datadog/profiling/stack_recorder.rb +0 -4
- data/lib/datadog/symbol_database/extractor.rb +17 -26
- data/lib/datadog/symbol_database/scope.rb +16 -12
- data/lib/datadog/symbol_database/scope_batcher.rb +280 -0
- data/lib/datadog/symbol_database/service_version.rb +4 -4
- data/lib/datadog/symbol_database/uploader.rb +3 -0
- data/lib/datadog/tracing/contrib/rack/configuration/settings.rb +6 -0
- data/lib/datadog/tracing/contrib/rack/ext.rb +27 -0
- data/lib/datadog/tracing/contrib/rack/trace_proxy_middleware.rb +117 -1
- data/lib/datadog/tracing/tracer.rb +1 -3
- data/lib/datadog/version.rb +1 -1
- metadata +19 -7
- 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
|
data/lib/datadog/appsec.rb
CHANGED
|
@@ -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,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
|
|
7
|
-
#
|
|
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
|
-
#
|
|
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
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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 =
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
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|
|
|
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 =
|
|
540
|
+
ranges = build_targetable_ranges(lines)
|
|
550
541
|
result = ranges.empty? ? nil : ranges
|
|
551
|
-
@logger.debug { "symdb: #{method.name}
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
26
|
-
# scopes. Key is absent on non-METHOD scopes.
|
|
27
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
@
|
|
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
|
|
63
|
-
def
|
|
64
|
-
!
|
|
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
|
-
#
|
|
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] =
|
|
86
|
-
h[:injectible_lines] =
|
|
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
|