datadog 2.36.0 → 2.37.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/CHANGELOG.md +46 -1
- data/ext/datadog_profiling_native_extension/clock_id_from_mach.c +5 -12
- data/ext/datadog_profiling_native_extension/collectors_cpu_and_wall_time_worker.c +6 -3
- data/ext/datadog_profiling_native_extension/collectors_idle_sampling_helper.c +17 -3
- data/ext/datadog_profiling_native_extension/collectors_thread_context.c +219 -98
- data/ext/datadog_profiling_native_extension/collectors_thread_context.h +2 -0
- data/ext/datadog_profiling_native_extension/extconf.rb +11 -0
- data/ext/datadog_profiling_native_extension/ruby_helpers.c +7 -0
- data/ext/libdatadog_api/di.c +76 -0
- data/ext/libdatadog_api/extconf.rb +6 -0
- data/ext/libdatadog_extconf_helpers.rb +1 -1
- data/lib/datadog/ai_guard/api_client.rb +6 -4
- data/lib/datadog/appsec/api_security/route_extractor.rb +7 -4
- data/lib/datadog/appsec/configuration.rb +6 -0
- data/lib/datadog/appsec/context.rb +4 -2
- data/lib/datadog/appsec/contrib/excon/ssrf_detection_middleware.rb +59 -4
- data/lib/datadog/appsec/contrib/faraday/ssrf_detection_middleware.rb +59 -5
- data/lib/datadog/appsec/contrib/graphql/gateway/multiplex.rb +1 -1
- data/lib/datadog/appsec/contrib/rack/gateway/watcher.rb +4 -0
- data/lib/datadog/appsec/contrib/rack/input_peeker.rb +4 -17
- data/lib/datadog/appsec/contrib/rack/request_middleware.rb +21 -0
- data/lib/datadog/appsec/contrib/rails/gateway/request.rb +2 -1
- data/lib/datadog/appsec/contrib/rest_client/request_ssrf_detection_patch.rb +60 -7
- data/lib/datadog/appsec/contrib/sinatra/gateway/watcher.rb +4 -0
- data/lib/datadog/appsec/ext.rb +1 -0
- data/lib/datadog/appsec/metrics/collector.rb +18 -1
- data/lib/datadog/appsec/metrics/exporter.rb +9 -3
- data/lib/datadog/appsec/remote.rb +9 -3
- data/lib/datadog/appsec/route_normalizer/rails_route_pattern.rb +176 -0
- data/lib/datadog/appsec/route_normalizer/route_pattern.rb +378 -0
- data/lib/datadog/appsec/route_normalizer/route_text.rb +57 -0
- data/lib/datadog/appsec/route_normalizer.rb +80 -0
- data/lib/datadog/appsec/utils/http/body_reader.rb +61 -0
- data/lib/datadog/core/configuration/components.rb +82 -2
- data/lib/datadog/core/configuration/components_state.rb +6 -1
- data/lib/datadog/core/configuration/settings.rb +0 -3
- data/lib/datadog/core/configuration/supported_configurations.rb +2 -0
- data/lib/datadog/core/evp.rb +11 -0
- data/lib/datadog/core/remote/client/capabilities.rb +34 -7
- data/lib/datadog/core/telemetry/event/app_started.rb +8 -1
- data/lib/datadog/di/base.rb +3 -1
- data/lib/datadog/di/code_tracker.rb +5 -2
- data/lib/datadog/di/component.rb +110 -36
- data/lib/datadog/di/error.rb +10 -0
- data/lib/datadog/di/fatal_exceptions.rb +26 -0
- data/lib/datadog/di/instrumenter.rb +381 -165
- data/lib/datadog/di/probe.rb +14 -0
- data/lib/datadog/di/probe_file_loader.rb +8 -3
- data/lib/datadog/di/probe_manager.rb +67 -9
- data/lib/datadog/di/probe_notification_builder.rb +4 -1
- data/lib/datadog/di/probe_notifier_worker.rb +52 -32
- data/lib/datadog/di/redactor.rb +16 -1
- data/lib/datadog/di/remote.rb +175 -14
- data/lib/datadog/di/serializer.rb +12 -5
- data/lib/datadog/di/transport/input.rb +8 -4
- data/lib/datadog/di.rb +63 -0
- data/lib/datadog/kit/appsec/events/v2.rb +60 -2
- data/lib/datadog/open_feature/component.rb +29 -6
- data/lib/datadog/open_feature/configuration.rb +8 -0
- data/lib/datadog/open_feature/ext.rb +2 -0
- data/lib/datadog/open_feature/flag_evaluation/aggregator.rb +286 -0
- data/lib/datadog/open_feature/flag_evaluation/writer.rb +433 -0
- data/lib/datadog/open_feature/hooks/flag_eval_evp_hook.rb +101 -0
- data/lib/datadog/open_feature/hooks/{flag_eval_hook.rb → flag_eval_metrics_hook.rb} +1 -1
- data/lib/datadog/open_feature/native_evaluator.rb +22 -0
- data/lib/datadog/open_feature/provider.rb +59 -27
- data/lib/datadog/open_feature/transport.rb +55 -1
- data/lib/datadog/opentelemetry/sdk/logs_exporter.rb +5 -12
- data/lib/datadog/opentelemetry/sdk/metrics_exporter.rb +4 -11
- data/lib/datadog/opentelemetry/sdk.rb +19 -0
- data/lib/datadog/profiling/collectors/cpu_and_wall_time_worker.rb +1 -1
- data/lib/datadog/profiling/collectors/idle_sampling_helper.rb +5 -3
- data/lib/datadog/symbol_database/component.rb +164 -47
- data/lib/datadog/symbol_database/configuration.rb +12 -4
- data/lib/datadog/symbol_database/extensions.rb +19 -0
- data/lib/datadog/symbol_database/extractor.rb +124 -87
- data/lib/datadog/symbol_database/file_hash.rb +3 -1
- data/lib/datadog/symbol_database/remote.rb +8 -3
- data/lib/datadog/symbol_database/scope_batcher.rb +7 -3
- data/lib/datadog/symbol_database/uploader.rb +9 -2
- data/lib/datadog/symbol_database.rb +22 -0
- data/lib/datadog/tracing/distributed/baggage.rb +2 -1
- data/lib/datadog/tracing/ext.rb +9 -0
- data/lib/datadog/tracing/remote.rb +34 -3
- data/lib/datadog/version.rb +1 -1
- data/lib/datadog.rb +1 -0
- metadata +19 -6
|
@@ -4,6 +4,7 @@ require_relative 'scope'
|
|
|
4
4
|
require_relative 'symbol'
|
|
5
5
|
require_relative 'file_hash'
|
|
6
6
|
require_relative '../core/utils/enumerable_compat'
|
|
7
|
+
require_relative '../di/fatal_exceptions'
|
|
7
8
|
|
|
8
9
|
module Datadog
|
|
9
10
|
module SymbolDatabase
|
|
@@ -89,6 +90,26 @@ module Datadog
|
|
|
89
90
|
# so we bind the original Module#name to get the real module name safely.
|
|
90
91
|
MODULE_NAME = Module.instance_method(:name)
|
|
91
92
|
|
|
93
|
+
# Cached UnboundMethods for the remaining class/module/object introspection
|
|
94
|
+
# used during extraction. Like MODULE_NAME, these bind the original
|
|
95
|
+
# implementations and are dispatched explicitly so that application code
|
|
96
|
+
# which overrides any of them (per-class, on a subclass, or in a singleton
|
|
97
|
+
# class) can neither intercept extraction nor be executed as a side effect
|
|
98
|
+
# of it. KERNEL_CLASS recovers an object's real class without calling a
|
|
99
|
+
# possibly-overridden #class.
|
|
100
|
+
CLASS_SUPERCLASS = Class.instance_method(:superclass)
|
|
101
|
+
MODULE_INCLUDED_MODULES = Module.instance_method(:included_modules)
|
|
102
|
+
MODULE_ANCESTORS = Module.instance_method(:ancestors)
|
|
103
|
+
MODULE_CLASS_VARIABLES = Module.instance_method(:class_variables)
|
|
104
|
+
MODULE_CONSTANTS = Module.instance_method(:constants)
|
|
105
|
+
MODULE_AUTOLOAD_P = Module.instance_method(:autoload?)
|
|
106
|
+
MODULE_CONST_GET = Module.instance_method(:const_get)
|
|
107
|
+
MODULE_CONST_DEFINED = Module.instance_method(:const_defined?)
|
|
108
|
+
KERNEL_CLASS = ::Kernel.instance_method(:class)
|
|
109
|
+
private_constant :CLASS_SUPERCLASS, :MODULE_INCLUDED_MODULES, :MODULE_ANCESTORS,
|
|
110
|
+
:MODULE_CLASS_VARIABLES, :MODULE_CONSTANTS, :MODULE_AUTOLOAD_P, :MODULE_CONST_GET,
|
|
111
|
+
:MODULE_CONST_DEFINED, :KERNEL_CLASS
|
|
112
|
+
|
|
92
113
|
# @param logger [Logger] Logger instance (SymbolDatabase::Logger facade or compatible)
|
|
93
114
|
# @param settings [Configuration::Settings] Tracer settings
|
|
94
115
|
def initialize(logger:, settings:)
|
|
@@ -109,7 +130,7 @@ module Datadog
|
|
|
109
130
|
# @param mod [Module, Class] The module or class to extract from
|
|
110
131
|
# @return [Scope, nil] FILE scope wrapping extracted scope, or nil if filtered out
|
|
111
132
|
def extract(mod)
|
|
112
|
-
return nil unless mod
|
|
133
|
+
return nil unless Module === mod
|
|
113
134
|
mod_name = safe_mod_name(mod)
|
|
114
135
|
return nil unless mod_name
|
|
115
136
|
|
|
@@ -118,14 +139,15 @@ module Datadog
|
|
|
118
139
|
source_file = find_source_file(mod)
|
|
119
140
|
return nil unless source_file
|
|
120
141
|
|
|
121
|
-
inner_scope = if mod
|
|
142
|
+
inner_scope = if Class === mod
|
|
122
143
|
extract_class_scope(mod)
|
|
123
144
|
else
|
|
124
145
|
extract_module_scope(mod)
|
|
125
146
|
end
|
|
126
147
|
|
|
127
148
|
wrap_in_file_scope(source_file, [inner_scope])
|
|
128
|
-
rescue => e
|
|
149
|
+
rescue Exception => e # standard:disable Lint/RescueException
|
|
150
|
+
Datadog::DI.reraise_if_fatal(e)
|
|
129
151
|
@logger.debug { "symdb: failed to extract #{mod_name || '<unknown>'}: #{e.class}: #{e.message}" }
|
|
130
152
|
nil
|
|
131
153
|
end
|
|
@@ -174,7 +196,7 @@ module Datadog
|
|
|
174
196
|
# returns [key, value] on a non-empty hash and nil when empty, so the
|
|
175
197
|
# `while (pair = ...)` form is the drain. Indexing pair[0]/pair[1] rather
|
|
176
198
|
# than destructuring avoids introducing names into method scope that would
|
|
177
|
-
# then shadow the else-branch's block parameters
|
|
199
|
+
# then shadow the else-branch's block parameters.
|
|
178
200
|
while (pair = index.shift)
|
|
179
201
|
scope = build_file_scope(pair[0], pair[1])
|
|
180
202
|
yield scope if scope
|
|
@@ -190,7 +212,8 @@ module Datadog
|
|
|
190
212
|
end
|
|
191
213
|
result
|
|
192
214
|
end
|
|
193
|
-
rescue => e
|
|
215
|
+
rescue Exception => e # standard:disable Lint/RescueException
|
|
216
|
+
Datadog::DI.reraise_if_fatal(e)
|
|
194
217
|
@logger.debug { "symdb: error in extract_all: #{e.class}: #{e.message}" }
|
|
195
218
|
block_given? ? nil : []
|
|
196
219
|
end
|
|
@@ -204,7 +227,8 @@ module Datadog
|
|
|
204
227
|
# @return [String, nil] Module name or nil
|
|
205
228
|
def safe_mod_name(mod)
|
|
206
229
|
MODULE_NAME.bind(mod).call
|
|
207
|
-
rescue => e
|
|
230
|
+
rescue Exception => e # standard:disable Lint/RescueException
|
|
231
|
+
Datadog::DI.reraise_if_fatal(e)
|
|
208
232
|
@logger.debug { "symdb: safe_mod_name failed: #{e.class}: #{e.message}" }
|
|
209
233
|
nil
|
|
210
234
|
end
|
|
@@ -228,15 +252,10 @@ module Datadog
|
|
|
228
252
|
# subclass with its own binding resolves through the binding, an
|
|
229
253
|
# inherited autoload triggers nothing.
|
|
230
254
|
#
|
|
231
|
-
#
|
|
232
|
-
#
|
|
233
|
-
# registered directly on this namespace?"
|
|
234
|
-
#
|
|
235
|
-
# branch uses `current.autoload?(sym)` which also reports inherited
|
|
236
|
-
# autoloads. The consequence on 2.5/2.6: a subclass binding whose
|
|
237
|
-
# name collides with an ancestor's pending autoload is conservatively
|
|
238
|
-
# treated as stale and dropped from extraction. The minimum supported
|
|
239
|
-
# Ruby is 2.5 per lib/datadog/version.rb, so this fallback ships.
|
|
255
|
+
# Uses `current.autoload?(sym, false)` (the inherit=false form, added
|
|
256
|
+
# in Ruby 2.7) so the question is strictly "is there an autoload
|
|
257
|
+
# registered directly on this namespace?" and ancestors' pending
|
|
258
|
+
# autoloads at the same name do not affect the result.
|
|
240
259
|
# @param mod_name [String]
|
|
241
260
|
# @param mod [Module]
|
|
242
261
|
# @return [Boolean]
|
|
@@ -244,17 +263,12 @@ module Datadog
|
|
|
244
263
|
current = Object
|
|
245
264
|
mod_name.split('::').each do |seg|
|
|
246
265
|
sym = seg.to_sym
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
current.autoload?(sym)
|
|
251
|
-
end
|
|
252
|
-
return false if pending_autoload
|
|
253
|
-
return false unless current.const_defined?(sym, false)
|
|
254
|
-
current = current.const_get(sym, false)
|
|
266
|
+
return false if MODULE_AUTOLOAD_P.bind(current).call(sym, false)
|
|
267
|
+
return false unless MODULE_CONST_DEFINED.bind(current).call(sym, false)
|
|
268
|
+
current = MODULE_CONST_GET.bind(current).call(sym, false)
|
|
255
269
|
end
|
|
256
270
|
current.equal?(mod)
|
|
257
|
-
rescue NameError, ArgumentError
|
|
271
|
+
rescue NameError, ArgumentError, TypeError
|
|
258
272
|
# Expected "no" outcome for stale/detached classes — the whole point
|
|
259
273
|
# of this predicate. Per the rescue convention in this file's header
|
|
260
274
|
# comment: inner per-item rescues are expected failures, no logging.
|
|
@@ -324,15 +338,10 @@ module Datadog
|
|
|
324
338
|
# AR models get filtered out as gem code.
|
|
325
339
|
#
|
|
326
340
|
# For namespace-only modules (no instance or singleton methods), falls back to
|
|
327
|
-
# Module#const_source_location
|
|
341
|
+
# Module#const_source_location to locate the module via its constants.
|
|
328
342
|
# This handles patterns like `module ApplicationCable; class Channel...; end; end`
|
|
329
343
|
# where the namespace module itself has no methods but defines user-code classes.
|
|
330
344
|
#
|
|
331
|
-
# On Ruby 2.6 (where const_source_location is unavailable), namespace-only modules
|
|
332
|
-
# and classes whose only methods are generated (e.g., AR models with only associations)
|
|
333
|
-
# may not be found — the extraction silently omits them. This is a graceful degradation:
|
|
334
|
-
# fewer symbols uploaded, no errors.
|
|
335
|
-
#
|
|
336
345
|
# @param mod [Module] The module
|
|
337
346
|
# @return [String, nil] Source file path or nil
|
|
338
347
|
def find_source_file(mod)
|
|
@@ -362,18 +371,19 @@ module Datadog
|
|
|
362
371
|
fallback ||= path # steep:ignore
|
|
363
372
|
end
|
|
364
373
|
|
|
365
|
-
#
|
|
374
|
+
# Use const_source_location to find where this class/module is declared.
|
|
366
375
|
# This handles two cases:
|
|
367
376
|
# 1. Classes with no user-defined methods (e.g. AR models with only associations) whose
|
|
368
377
|
# generated methods point to gem code — we find the `class Foo` declaration instead.
|
|
369
378
|
# 2. Namespace-only modules (`module Foo; class Bar; end; end`) with no methods at all.
|
|
370
|
-
|
|
379
|
+
mod_name = safe_mod_name(mod)
|
|
380
|
+
if mod_name
|
|
371
381
|
# Look up the class/module by its last name component in its enclosing namespace.
|
|
372
|
-
parts =
|
|
382
|
+
parts = mod_name.split('::')
|
|
373
383
|
const_name = parts.last
|
|
374
384
|
namespace = if parts.length > 1
|
|
375
385
|
begin
|
|
376
|
-
Object.
|
|
386
|
+
MODULE_CONST_GET.bind(Object).call(parts[0..-2].join('::')) # steep:ignore
|
|
377
387
|
rescue NameError
|
|
378
388
|
nil
|
|
379
389
|
end
|
|
@@ -384,7 +394,8 @@ module Datadog
|
|
|
384
394
|
if namespace
|
|
385
395
|
location = begin
|
|
386
396
|
namespace.const_source_location(const_name)
|
|
387
|
-
rescue => e
|
|
397
|
+
rescue Exception => e # standard:disable Lint/RescueException
|
|
398
|
+
Datadog::DI.reraise_if_fatal(e)
|
|
388
399
|
@logger.debug { "symdb: const_source_location(#{const_name}) failed: #{e.class}: #{e.message}" }
|
|
389
400
|
nil
|
|
390
401
|
end
|
|
@@ -397,10 +408,11 @@ module Datadog
|
|
|
397
408
|
end
|
|
398
409
|
|
|
399
410
|
# Also scan constants defined by mod itself (namespace-only modules).
|
|
400
|
-
mod.
|
|
411
|
+
MODULE_CONSTANTS.bind(mod).call(false).each do |child_const_name|
|
|
401
412
|
location = begin
|
|
402
413
|
mod.const_source_location(child_const_name)
|
|
403
|
-
rescue => e
|
|
414
|
+
rescue Exception => e # standard:disable Lint/RescueException
|
|
415
|
+
Datadog::DI.reraise_if_fatal(e)
|
|
404
416
|
@logger.debug { "symdb: const_source_location(#{child_const_name}) failed: #{e.class}: #{e.message}" }
|
|
405
417
|
nil
|
|
406
418
|
end
|
|
@@ -416,7 +428,8 @@ module Datadog
|
|
|
416
428
|
end
|
|
417
429
|
|
|
418
430
|
fallback
|
|
419
|
-
rescue => e
|
|
431
|
+
rescue Exception => e # standard:disable Lint/RescueException
|
|
432
|
+
Datadog::DI.reraise_if_fatal(e)
|
|
420
433
|
@logger.debug { "symdb: error finding source file for #{safe_mod_name(mod) || '<unknown>'}: #{e.class}: #{e.message}" }
|
|
421
434
|
nil
|
|
422
435
|
end
|
|
@@ -453,7 +466,7 @@ module Datadog
|
|
|
453
466
|
|
|
454
467
|
Scope.new(
|
|
455
468
|
scope_type: 'MODULE',
|
|
456
|
-
name: mod
|
|
469
|
+
name: safe_mod_name(mod),
|
|
457
470
|
source_file: source_file,
|
|
458
471
|
start_line: UNKNOWN_MIN_LINE,
|
|
459
472
|
end_line: UNKNOWN_MAX_LINE,
|
|
@@ -471,7 +484,7 @@ module Datadog
|
|
|
471
484
|
|
|
472
485
|
Scope.new(
|
|
473
486
|
scope_type: 'CLASS',
|
|
474
|
-
name: klass
|
|
487
|
+
name: safe_mod_name(klass),
|
|
475
488
|
source_file: source_file,
|
|
476
489
|
start_line: start_line,
|
|
477
490
|
end_line: end_line,
|
|
@@ -502,8 +515,9 @@ module Datadog
|
|
|
502
515
|
return [UNKNOWN_MIN_LINE, UNKNOWN_MAX_LINE] if starts.empty?
|
|
503
516
|
|
|
504
517
|
[starts.min, ends.max]
|
|
505
|
-
rescue => e
|
|
506
|
-
|
|
518
|
+
rescue Exception => e # standard:disable Lint/RescueException
|
|
519
|
+
Datadog::DI.reraise_if_fatal(e)
|
|
520
|
+
@logger.debug { "symdb: error calculating line range for #{safe_mod_name(klass)}: #{e.class}: #{e.message}" }
|
|
507
521
|
[UNKNOWN_MIN_LINE, UNKNOWN_MAX_LINE]
|
|
508
522
|
end
|
|
509
523
|
|
|
@@ -517,8 +531,9 @@ module Datadog
|
|
|
517
531
|
# Emitted as an array named super_classes — consistent with Java, .NET, and Python.
|
|
518
532
|
# Array allows for multiple entries if future Ruby versions or mixins expand the chain.
|
|
519
533
|
# Anonymous superclasses (class Foo < Class.new { ... }) have nil name; compact to skip.
|
|
520
|
-
|
|
521
|
-
|
|
534
|
+
superclass = CLASS_SUPERCLASS.bind(klass).call
|
|
535
|
+
if superclass && !superclass.equal?(Object) && !superclass.equal?(BasicObject)
|
|
536
|
+
super_name = safe_mod_name(superclass)
|
|
522
537
|
specifics[:super_classes] = [super_name] if super_name
|
|
523
538
|
end
|
|
524
539
|
|
|
@@ -526,7 +541,7 @@ module Datadog
|
|
|
526
541
|
# included_modules returns the entire ancestor chain's mixins, not only directly
|
|
527
542
|
# included ones. This is intentional: the field reports "modules this class
|
|
528
543
|
# responds to," which is what the consumer (UI navigation, probe context) needs.
|
|
529
|
-
included = klass.
|
|
544
|
+
included = MODULE_INCLUDED_MODULES.bind(klass).call.map { |m| safe_mod_name(m) }.reject do |name|
|
|
530
545
|
name.nil? || EXCLUDED_COMMON_MODULES.any? { |prefix| name.start_with?(prefix) }
|
|
531
546
|
end
|
|
532
547
|
specifics[:included_modules] = included unless included.empty?
|
|
@@ -537,16 +552,17 @@ module Datadog
|
|
|
537
552
|
# Single-pass collection avoids the intermediate arrays from take_while.map.compact.
|
|
538
553
|
# Test coverage: spec/datadog/symbol_database/extractor_spec.rb tests prepend behavior.
|
|
539
554
|
prepended = []
|
|
540
|
-
klass.
|
|
541
|
-
break if a
|
|
542
|
-
name = a
|
|
555
|
+
MODULE_ANCESTORS.bind(klass).call.each do |a|
|
|
556
|
+
break if a.equal?(klass)
|
|
557
|
+
name = safe_mod_name(a)
|
|
543
558
|
prepended << name if name
|
|
544
559
|
end
|
|
545
560
|
specifics[:prepended_modules] = prepended unless prepended.empty?
|
|
546
561
|
|
|
547
562
|
specifics
|
|
548
|
-
rescue => e
|
|
549
|
-
|
|
563
|
+
rescue Exception => e # standard:disable Lint/RescueException
|
|
564
|
+
Datadog::DI.reraise_if_fatal(e)
|
|
565
|
+
@logger.debug { "symdb: error building language specifics for #{safe_mod_name(klass)}: #{e.class}: #{e.message}" }
|
|
550
566
|
{}
|
|
551
567
|
end
|
|
552
568
|
|
|
@@ -568,8 +584,9 @@ module Datadog
|
|
|
568
584
|
end
|
|
569
585
|
|
|
570
586
|
scopes
|
|
571
|
-
rescue => e
|
|
572
|
-
|
|
587
|
+
rescue Exception => e # standard:disable Lint/RescueException
|
|
588
|
+
Datadog::DI.reraise_if_fatal(e)
|
|
589
|
+
@logger.debug { "symdb: failed to extract methods from #{safe_mod_name(klass)}: #{e.class}: #{e.message}" }
|
|
573
590
|
[]
|
|
574
591
|
end
|
|
575
592
|
|
|
@@ -603,8 +620,9 @@ module Datadog
|
|
|
603
620
|
},
|
|
604
621
|
symbols: extract_method_parameters(method)
|
|
605
622
|
)
|
|
606
|
-
rescue => e
|
|
607
|
-
|
|
623
|
+
rescue Exception => e # standard:disable Lint/RescueException
|
|
624
|
+
Datadog::DI.reraise_if_fatal(e)
|
|
625
|
+
@logger.debug { "symdb: failed to extract method #{safe_mod_name(klass)}##{method_name}: #{e.class}: #{e.message}" }
|
|
608
626
|
nil
|
|
609
627
|
end
|
|
610
628
|
|
|
@@ -681,7 +699,8 @@ module Datadog
|
|
|
681
699
|
def extract_method_parameters(method)
|
|
682
700
|
method_name = begin
|
|
683
701
|
method.name.to_s
|
|
684
|
-
rescue => e
|
|
702
|
+
rescue Exception => e # standard:disable Lint/RescueException
|
|
703
|
+
Datadog::DI.reraise_if_fatal(e)
|
|
685
704
|
@logger.debug { "symdb: method.name failed: #{e.class}: #{e.message}" }
|
|
686
705
|
'unknown'
|
|
687
706
|
end
|
|
@@ -703,7 +722,8 @@ module Datadog
|
|
|
703
722
|
line: UNKNOWN_MIN_LINE, # Parameters available in entire method
|
|
704
723
|
)
|
|
705
724
|
end
|
|
706
|
-
rescue => e
|
|
725
|
+
rescue Exception => e # standard:disable Lint/RescueException
|
|
726
|
+
Datadog::DI.reraise_if_fatal(e)
|
|
707
727
|
@logger.debug { "symdb: failed to extract parameters from #{method_name}: #{e.class}: #{e.message}" }
|
|
708
728
|
[]
|
|
709
729
|
end
|
|
@@ -743,11 +763,7 @@ module Datadog
|
|
|
743
763
|
ObjectSpace.each_object(Module) do |mod|
|
|
744
764
|
# Singleton classes (per-object metaclasses) are never user-code classes.
|
|
745
765
|
# They're not const-referenced, DI cannot instrument methods on a singular
|
|
746
|
-
# object instance,
|
|
747
|
-
# singleton classes with long ancestor chains (e.g. through monkey-patches
|
|
748
|
-
# prepended into Kernel, common in dd-trace-rb test processes) is O(ancestors)
|
|
749
|
-
# — measured ~20ms per call, which dominates extract_all on heavily-loaded
|
|
750
|
-
# processes. Ruby 2.7+ optimized this path; the skip is a no-op there.
|
|
766
|
+
# object instance, so skipping them is both correct and cheap.
|
|
751
767
|
next if MODULE_SINGLETON_CLASS_PRED.bind(mod).call
|
|
752
768
|
|
|
753
769
|
seen += 1
|
|
@@ -772,7 +788,8 @@ module Datadog
|
|
|
772
788
|
file_to_names.each do |file_path, method_names|
|
|
773
789
|
(index[file_path] ||= []) << [mod_name, mod, method_names]
|
|
774
790
|
end
|
|
775
|
-
rescue => e
|
|
791
|
+
rescue Exception => e # standard:disable Lint/RescueException
|
|
792
|
+
Datadog::DI.reraise_if_fatal(e)
|
|
776
793
|
@logger.debug { "symdb: error indexing #{mod_name || '<unknown>'}: #{e.class}: #{e.message}" }
|
|
777
794
|
end
|
|
778
795
|
|
|
@@ -797,13 +814,15 @@ module Datadog
|
|
|
797
814
|
next unless user_code_path?(loc[0])
|
|
798
815
|
|
|
799
816
|
result[loc[0]] << method_name
|
|
800
|
-
rescue => e
|
|
817
|
+
rescue Exception => e # standard:disable Lint/RescueException
|
|
818
|
+
Datadog::DI.reraise_if_fatal(e)
|
|
801
819
|
@logger.debug { "symdb: error indexing method #{method_name}: #{e.class}: #{e.message}" }
|
|
802
820
|
end
|
|
803
821
|
end
|
|
804
822
|
|
|
805
823
|
result
|
|
806
|
-
rescue => e
|
|
824
|
+
rescue Exception => e # standard:disable Lint/RescueException
|
|
825
|
+
Datadog::DI.reraise_if_fatal(e)
|
|
807
826
|
@logger.debug { "symdb: error indexing methods: #{e.class}: #{e.message}" }
|
|
808
827
|
{}
|
|
809
828
|
end
|
|
@@ -844,7 +863,8 @@ module Datadog
|
|
|
844
863
|
loc = method.source_location
|
|
845
864
|
next nil unless loc && loc[0] == file_path
|
|
846
865
|
{name: name, method: method, type: :instance}
|
|
847
|
-
rescue => e
|
|
866
|
+
rescue Exception => e # standard:disable Lint/RescueException
|
|
867
|
+
Datadog::DI.reraise_if_fatal(e)
|
|
848
868
|
@logger.debug { "symdb: error resolving #{mod_name}##{name}: #{e.class}: #{e.message}" }
|
|
849
869
|
nil
|
|
850
870
|
end
|
|
@@ -857,7 +877,8 @@ module Datadog
|
|
|
857
877
|
|
|
858
878
|
parts = mod_name.split('::')
|
|
859
879
|
place_in_tree(root, parts, mod, mod_name, method_infos, file_path)
|
|
860
|
-
rescue => e
|
|
880
|
+
rescue Exception => e # standard:disable Lint/RescueException
|
|
881
|
+
Datadog::DI.reraise_if_fatal(e)
|
|
861
882
|
@logger.debug { "symdb: error placing #{mod_name} in tree: #{e.class}: #{e.message}" }
|
|
862
883
|
end
|
|
863
884
|
|
|
@@ -895,12 +916,12 @@ module Datadog
|
|
|
895
916
|
if leaf
|
|
896
917
|
# Node exists (was created as intermediate or from another entry).
|
|
897
918
|
# Update type and mod — the actual module object is authoritative.
|
|
898
|
-
leaf[:type] =
|
|
919
|
+
leaf[:type] = (Class === mod) ? 'CLASS' : 'MODULE'
|
|
899
920
|
leaf[:mod] = mod
|
|
900
921
|
else
|
|
901
922
|
leaf = {
|
|
902
923
|
name: mod_name,
|
|
903
|
-
type:
|
|
924
|
+
type: (Class === mod) ? 'CLASS' : 'MODULE',
|
|
904
925
|
children: {}, methods: [],
|
|
905
926
|
mod: mod, source_file: file_path,
|
|
906
927
|
fqn: mod_name
|
|
@@ -917,9 +938,21 @@ module Datadog
|
|
|
917
938
|
# @param fqn [String] Fully-qualified name (e.g. "Authentication::Strategies")
|
|
918
939
|
# @return [String] 'CLASS' or 'MODULE'
|
|
919
940
|
def resolve_scope_type(fqn)
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
941
|
+
current = Object
|
|
942
|
+
fqn.split('::').each do |seg|
|
|
943
|
+
sym = seg.to_sym
|
|
944
|
+
pending_autoload = if RubyVersion.is?('>= 2.7')
|
|
945
|
+
MODULE_AUTOLOAD_P.bind(current).call(sym, false)
|
|
946
|
+
else
|
|
947
|
+
MODULE_AUTOLOAD_P.bind(current).call(sym)
|
|
948
|
+
end
|
|
949
|
+
return 'MODULE' if pending_autoload
|
|
950
|
+
return 'MODULE' unless MODULE_CONST_DEFINED.bind(current).call(sym, false)
|
|
951
|
+
current = MODULE_CONST_GET.bind(current).call(sym, false)
|
|
952
|
+
end
|
|
953
|
+
(Class === current) ? 'CLASS' : 'MODULE'
|
|
954
|
+
rescue Exception => e # standard:disable Lint/RescueException
|
|
955
|
+
Datadog::DI.reraise_if_fatal(e)
|
|
923
956
|
@logger.debug { "symdb: resolve_scope_type(#{fqn}) failed: #{e.class}: #{e.message}, defaulting to MODULE" }
|
|
924
957
|
'MODULE'
|
|
925
958
|
end
|
|
@@ -1014,7 +1047,8 @@ module Datadog
|
|
|
1014
1047
|
},
|
|
1015
1048
|
symbols: extract_method_parameters(method)
|
|
1016
1049
|
)
|
|
1017
|
-
rescue => e
|
|
1050
|
+
rescue Exception => e # standard:disable Lint/RescueException
|
|
1051
|
+
Datadog::DI.reraise_if_fatal(e)
|
|
1018
1052
|
klass_name = klass ? (safe_mod_name(klass) || '<unknown>') : '<unknown>'
|
|
1019
1053
|
@logger.debug { "symdb: failed to build method scope #{klass_name}##{method_name}: #{e.class}: #{e.message}" }
|
|
1020
1054
|
nil
|
|
@@ -1028,8 +1062,8 @@ module Datadog
|
|
|
1028
1062
|
symbols = []
|
|
1029
1063
|
|
|
1030
1064
|
# Class variables (only for classes)
|
|
1031
|
-
if mod
|
|
1032
|
-
mod.
|
|
1065
|
+
if Class === mod
|
|
1066
|
+
MODULE_CLASS_VARIABLES.bind(mod).call(false).each do |var_name|
|
|
1033
1067
|
symbols << Symbol.new(
|
|
1034
1068
|
symbol_type: 'STATIC_FIELD',
|
|
1035
1069
|
name: var_name.to_s,
|
|
@@ -1040,30 +1074,33 @@ module Datadog
|
|
|
1040
1074
|
|
|
1041
1075
|
# Constants (excluding nested modules/classes).
|
|
1042
1076
|
# Skip autoloaded constants to avoid triggering loading as a side effect.
|
|
1043
|
-
mod.
|
|
1044
|
-
next if mod.
|
|
1045
|
-
const_value = mod.
|
|
1046
|
-
next if const_value
|
|
1077
|
+
MODULE_CONSTANTS.bind(mod).call(false).each do |const_name|
|
|
1078
|
+
next if MODULE_AUTOLOAD_P.bind(mod).call(const_name)
|
|
1079
|
+
const_value = MODULE_CONST_GET.bind(mod).call(const_name)
|
|
1080
|
+
next if Module === const_value
|
|
1047
1081
|
|
|
1048
1082
|
symbols << Symbol.new(
|
|
1049
1083
|
symbol_type: 'STATIC_FIELD',
|
|
1050
1084
|
name: const_name.to_s,
|
|
1051
1085
|
line: UNKNOWN_MIN_LINE,
|
|
1052
|
-
type: const_value.
|
|
1086
|
+
type: safe_mod_name(KERNEL_CLASS.bind(const_value).call)
|
|
1053
1087
|
)
|
|
1054
|
-
rescue NameError, LoadError, NoMethodError => e # standard:disable Lint/ShadowedException
|
|
1055
|
-
# Expected: constant removed/undefined, autoload failure,
|
|
1056
|
-
#
|
|
1057
|
-
#
|
|
1058
|
-
#
|
|
1059
|
-
#
|
|
1088
|
+
rescue NameError, LoadError, NoMethodError, TypeError => e # standard:disable Lint/ShadowedException
|
|
1089
|
+
# Expected: constant removed/undefined (NameError), autoload failure (LoadError),
|
|
1090
|
+
# or a value whose class cannot be read (NoMethodError/TypeError). Skipping one
|
|
1091
|
+
# constant here keeps the rest of the module's symbols. Logged separately from
|
|
1092
|
+
# unexpected errors so the latter stand out in triage. Lint/ShadowedException
|
|
1093
|
+
# disabled: these descend from StandardError, but Ruby's rescue-clause-order
|
|
1094
|
+
# semantics ensure the catch-all rescue below only catches exceptions not matched here.
|
|
1060
1095
|
@logger.debug { "symdb: skipping module constant #{const_name}: #{e.class}: #{e.message}" }
|
|
1061
|
-
rescue => e
|
|
1096
|
+
rescue Exception => e # standard:disable Lint/RescueException
|
|
1097
|
+
Datadog::DI.reraise_if_fatal(e)
|
|
1062
1098
|
@logger.debug { "symdb: unexpected error reading module constant #{const_name}: #{e.class}: #{e.message}" }
|
|
1063
1099
|
end
|
|
1064
1100
|
|
|
1065
1101
|
symbols
|
|
1066
|
-
rescue => e
|
|
1102
|
+
rescue Exception => e # standard:disable Lint/RescueException
|
|
1103
|
+
Datadog::DI.reraise_if_fatal(e)
|
|
1067
1104
|
mod_name = safe_mod_name(mod) || '<unknown>'
|
|
1068
1105
|
@logger.debug { "symdb: failed to extract symbols from #{mod_name}: #{e.class}: #{e.message}" }
|
|
1069
1106
|
[]
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require 'digest/sha1'
|
|
4
|
+
require_relative '../di/fatal_exceptions'
|
|
4
5
|
|
|
5
6
|
module Datadog
|
|
6
7
|
module SymbolDatabase
|
|
@@ -37,7 +38,8 @@ module Datadog
|
|
|
37
38
|
# This is not a security vulnerability - we're computing file content hashes
|
|
38
39
|
# to match against Git objects, not using SHA-1 for authentication/integrity.
|
|
39
40
|
Digest::SHA1.hexdigest(git_blob) # nosemgrep: ruby.lang.security.weak-hashes-sha1.weak-hashes-sha1
|
|
40
|
-
rescue => e
|
|
41
|
+
rescue Exception => e # standard:disable Lint/RescueException
|
|
42
|
+
Datadog::DI.reraise_if_fatal(e)
|
|
41
43
|
logger.debug { "symdb: file hash failed for #{file_path}: #{e.class}: #{e.message}" }
|
|
42
44
|
nil
|
|
43
45
|
end
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require_relative '../di/fatal_exceptions'
|
|
4
|
+
|
|
3
5
|
module Datadog
|
|
4
6
|
module SymbolDatabase
|
|
5
7
|
# Provides remote configuration integration for symbol database.
|
|
@@ -44,7 +46,8 @@ module Datadog
|
|
|
44
46
|
telemetry = lookup_telemetry
|
|
45
47
|
component = begin
|
|
46
48
|
Datadog.send(:components, allow_initialization: false)&.symbol_database
|
|
47
|
-
rescue => e
|
|
49
|
+
rescue Exception => e # standard:disable Lint/RescueException
|
|
50
|
+
Datadog::DI.reraise_if_fatal(e)
|
|
48
51
|
Datadog.logger.debug { "symdb: failed to look up component in RC receiver: #{e.class}: #{e.message}" }
|
|
49
52
|
telemetry&.report(e, description: 'symdb: failed to look up component in RC receiver')
|
|
50
53
|
nil
|
|
@@ -76,7 +79,8 @@ module Datadog
|
|
|
76
79
|
# @api private
|
|
77
80
|
def lookup_telemetry
|
|
78
81
|
Datadog.send(:components, allow_initialization: false)&.telemetry
|
|
79
|
-
rescue
|
|
82
|
+
rescue Exception => e # standard:disable Lint/RescueException
|
|
83
|
+
Datadog::DI.reraise_if_fatal(e)
|
|
80
84
|
nil
|
|
81
85
|
end
|
|
82
86
|
|
|
@@ -107,7 +111,8 @@ module Datadog
|
|
|
107
111
|
# the Repository::Change union type where `Deleted` lacks `content`.
|
|
108
112
|
change.content.errored("Unrecognized change type: #{change.type}") if change.respond_to?(:content) # steep:ignore NoMethod
|
|
109
113
|
end
|
|
110
|
-
rescue => e
|
|
114
|
+
rescue Exception => e # standard:disable Lint/RescueException
|
|
115
|
+
Datadog::DI.reraise_if_fatal(e)
|
|
111
116
|
component.logger.debug { "symdb: error processing remote config change: #{e.class}: #{e.message}" }
|
|
112
117
|
telemetry&.report(e, description: 'symdb: error processing remote config change')
|
|
113
118
|
# Rescue runs regardless of which branch raised — Steep cannot narrow the
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require 'set'
|
|
4
|
+
require_relative '../di/fatal_exceptions'
|
|
4
5
|
|
|
5
6
|
module Datadog
|
|
6
7
|
module SymbolDatabase
|
|
@@ -122,7 +123,8 @@ module Datadog
|
|
|
122
123
|
|
|
123
124
|
# Upload outside mutex (if batch was full)
|
|
124
125
|
perform_upload(scopes_to_upload) if scopes_to_upload
|
|
125
|
-
rescue => e
|
|
126
|
+
rescue Exception => e # standard:disable Lint/RescueException
|
|
127
|
+
Datadog::DI.reraise_if_fatal(e)
|
|
126
128
|
@logger.debug { "symdb: failed to add scope: #{e.class}: #{e.message}" }
|
|
127
129
|
# Don't propagate, continue operation
|
|
128
130
|
end
|
|
@@ -267,7 +269,8 @@ module Datadog
|
|
|
267
269
|
flush
|
|
268
270
|
end
|
|
269
271
|
end
|
|
270
|
-
rescue => e
|
|
272
|
+
rescue Exception => e # standard:disable Lint/RescueException
|
|
273
|
+
Datadog::DI.reraise_if_fatal(e)
|
|
271
274
|
@logger.debug { "symdb: timer thread error: #{e.class}: #{e.message}" }
|
|
272
275
|
end
|
|
273
276
|
|
|
@@ -279,7 +282,8 @@ module Datadog
|
|
|
279
282
|
|
|
280
283
|
@uploader.upload_scopes(scopes)
|
|
281
284
|
@on_upload&.call(scopes) # Notify tests after upload
|
|
282
|
-
rescue => e
|
|
285
|
+
rescue Exception => e # standard:disable Lint/RescueException
|
|
286
|
+
Datadog::DI.reraise_if_fatal(e)
|
|
283
287
|
@logger.debug { "symdb: upload failed: #{e.class}: #{e.message}" }
|
|
284
288
|
# Don't propagate, uploader handles retries
|
|
285
289
|
end
|
|
@@ -6,8 +6,10 @@ require 'zlib'
|
|
|
6
6
|
require 'stringio'
|
|
7
7
|
require_relative '../core/environment/identity'
|
|
8
8
|
require_relative '../core/vendor/multipart-post/multipart/post/composite_read_io'
|
|
9
|
+
require_relative '../tracing/ext'
|
|
9
10
|
require_relative 'service_version'
|
|
10
11
|
require_relative 'transport/http'
|
|
12
|
+
require_relative '../di/fatal_exceptions'
|
|
11
13
|
|
|
12
14
|
module Datadog
|
|
13
15
|
module SymbolDatabase
|
|
@@ -68,7 +70,11 @@ module Datadog
|
|
|
68
70
|
compressed_data = Zlib.gzip(json_data)
|
|
69
71
|
|
|
70
72
|
# Emitted unconditionally so the rare oversized case is observable.
|
|
71
|
-
@telemetry&.distribution(
|
|
73
|
+
@telemetry&.distribution(
|
|
74
|
+
Tracing::Ext::TELEMETRY_METRICS_NAMESPACE,
|
|
75
|
+
'symbol_database.payload_size',
|
|
76
|
+
compressed_data.bytesize
|
|
77
|
+
)
|
|
72
78
|
|
|
73
79
|
# Symbols for very large applications (>50MB after gzip) are dropped:
|
|
74
80
|
# the upload is skipped and the customer sees no autocomplete /
|
|
@@ -81,7 +87,8 @@ module Datadog
|
|
|
81
87
|
end
|
|
82
88
|
|
|
83
89
|
perform_http_upload(compressed_data, scopes.size, upload_id: upload_id, batch_num: batch_num)
|
|
84
|
-
rescue => e
|
|
90
|
+
rescue Exception => e # standard:disable Lint/RescueException
|
|
91
|
+
Datadog::DI.reraise_if_fatal(e)
|
|
85
92
|
@logger.debug { "symdb: upload failed: #{e.class}: #{e.message}" }
|
|
86
93
|
@telemetry&.report(e, description: 'symdb: upload failed')
|
|
87
94
|
end
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require_relative 'ruby_version'
|
|
4
|
+
|
|
3
5
|
module Datadog
|
|
4
6
|
# Namespace for Datadog symbol database upload.
|
|
5
7
|
#
|
|
@@ -45,5 +47,25 @@ module Datadog
|
|
|
45
47
|
# Reference: Symbol Database Backend RFC, section "Scope" and "Edge Cases"
|
|
46
48
|
# @see https://www.postgresql.org/docs/current/datatype-numeric.html
|
|
47
49
|
UNKNOWN_MAX_LINE = 2147483647
|
|
50
|
+
|
|
51
|
+
# Collapses the symbol_database.enabled tri-state setting to a boolean.
|
|
52
|
+
# An explicit true/false wins; nil (unconfigured) yields the caller-supplied
|
|
53
|
+
# fallback.
|
|
54
|
+
# @param setting_value [Boolean, nil] the symbol_database.enabled setting
|
|
55
|
+
# @param di_fallback [Boolean] value used when the setting is unconfigured
|
|
56
|
+
# @return [Boolean]
|
|
57
|
+
def self.resolve_enabled(setting_value, di_fallback)
|
|
58
|
+
setting_value.nil? ? di_fallback : setting_value
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
# Whether the current Ruby runtime can run symbol database extraction:
|
|
62
|
+
# MRI (CRuby) on Ruby 2.7 or later. Used by the remote-config layer to avoid
|
|
63
|
+
# advertising the product on runtimes where Component.build would return nil
|
|
64
|
+
# (e.g. Ruby 2.6, which Dynamic Instrumentation supports but Symbol Database
|
|
65
|
+
# does not).
|
|
66
|
+
# @return [Boolean]
|
|
67
|
+
def self.supported_runtime?
|
|
68
|
+
RUBY_ENGINE == 'ruby' && RubyVersion.is?('>= 2.7')
|
|
69
|
+
end
|
|
48
70
|
end
|
|
49
71
|
end
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require_relative '../ext'
|
|
3
4
|
require_relative '../metadata/ext'
|
|
4
5
|
require_relative '../trace_digest'
|
|
5
6
|
require_relative 'datadog_tags_codec'
|
|
@@ -243,7 +244,7 @@ module Datadog
|
|
|
243
244
|
# Record telemetry metrics for baggage operations
|
|
244
245
|
def record_telemetry_metric(metric_name, value, tags)
|
|
245
246
|
telemetry = ::Datadog.send(:components).telemetry
|
|
246
|
-
telemetry.inc(
|
|
247
|
+
telemetry.inc(Tracing::Ext::TELEMETRY_METRICS_NAMESPACE, metric_name, value, tags: tags)
|
|
247
248
|
end
|
|
248
249
|
end
|
|
249
250
|
end
|