datadog 2.42.0 → 2.44.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 +67 -2
- data/README.md +3 -0
- data/ext/datadog_profiling_native_extension/collectors_cpu_and_wall_time_worker.c +46 -5
- data/ext/datadog_profiling_native_extension/collectors_idle_sampling_helper.c +6 -7
- data/ext/datadog_profiling_native_extension/collectors_stack.c +6 -7
- data/ext/datadog_profiling_native_extension/collectors_stack.h +1 -1
- data/ext/datadog_profiling_native_extension/collectors_thread_context.c +80 -36
- data/ext/datadog_profiling_native_extension/collectors_thread_context.h +3 -2
- data/ext/datadog_profiling_native_extension/extconf.rb +4 -2
- data/ext/datadog_profiling_native_extension/heap_recorder.c +13 -0
- data/ext/datadog_profiling_native_extension/heap_recorder.h +4 -0
- data/ext/datadog_profiling_native_extension/private_vm_api_access.c +34 -2
- data/ext/datadog_profiling_native_extension/private_vm_api_access.h +1 -0
- data/ext/datadog_profiling_native_extension/profiling.c +1 -0
- data/ext/datadog_profiling_native_extension/stack_recorder.c +17 -4
- data/ext/datadog_profiling_native_extension/time_helpers.h +5 -4
- data/ext/libdatadog_api/di.c +8 -3
- data/ext/libdatadog_api/extconf.rb +1 -1
- data/ext/libdatadog_api/feature_flags.c +8 -0
- data/ext/libdatadog_api/init.c +3 -1
- data/ext/libdatadog_api/otel_thread_context.c +51 -15
- data/ext/libdatadog_api/trace_exporter.c +642 -22
- data/ext/libdatadog_extconf_helpers.rb +14 -9
- data/lib/datadog/ai_guard/component.rb +17 -8
- data/lib/datadog/ai_guard/configuration/ext.rb +2 -0
- data/lib/datadog/ai_guard/configuration.rb +13 -0
- data/lib/datadog/ai_guard/contrib/ruby_llm/chat_instrumentation.rb +48 -55
- data/lib/datadog/ai_guard/contrib/ruby_llm/integration.rb +1 -1
- data/lib/datadog/ai_guard/contrib/ruby_llm/message_adapter.rb +76 -0
- data/lib/datadog/ai_guard/contrib/ruby_llm/patcher.rb +1 -0
- data/lib/datadog/ai_guard/contrib/ruby_llm/redaction_change_set.rb +135 -0
- data/lib/datadog/ai_guard/evaluation/client.rb +50 -0
- data/lib/datadog/ai_guard/evaluation/content_part.rb +12 -0
- data/lib/datadog/ai_guard/evaluation/message.rb +25 -7
- data/lib/datadog/ai_guard/evaluation/message_builder.rb +31 -0
- data/lib/datadog/ai_guard/evaluation/no_op_result.rb +6 -6
- data/lib/datadog/ai_guard/evaluation/outcome.rb +24 -0
- data/lib/datadog/ai_guard/evaluation/request.rb +4 -60
- data/lib/datadog/ai_guard/evaluation/response.rb +33 -0
- data/lib/datadog/ai_guard/evaluation/result.rb +11 -18
- data/lib/datadog/ai_guard/evaluation/tool_call.rb +15 -2
- data/lib/datadog/ai_guard/evaluation.rb +25 -11
- data/lib/datadog/ai_guard/ext.rb +1 -0
- data/lib/datadog/ai_guard/{api_client.rb → http_client.rb} +6 -4
- data/lib/datadog/ai_guard/metrics/telemetry.rb +43 -0
- data/lib/datadog/ai_guard/redaction/replacements.rb +97 -0
- data/lib/datadog/ai_guard/redaction/result.rb +29 -0
- data/lib/datadog/ai_guard/redaction.rb +80 -0
- data/lib/datadog/ai_guard.rb +58 -57
- data/lib/datadog/core/configuration/components.rb +9 -1
- data/lib/datadog/core/configuration/settings.rb +5 -2
- data/lib/datadog/core/configuration/supported_configurations.rb +8 -0
- data/lib/datadog/core/remote/client/capabilities.rb +10 -4
- data/lib/datadog/core/remote/component.rb +9 -2
- data/lib/datadog/core/remote/worker.rb +8 -0
- data/lib/datadog/di/instrumenter.rb +1 -1
- data/lib/datadog/di/probe_manager.rb +10 -5
- data/lib/datadog/di/proc_responder.rb +1 -1
- data/lib/datadog/open_feature/agentless/configuration_source.rb +246 -0
- data/lib/datadog/open_feature/agentless/parser.rb +43 -0
- data/lib/datadog/open_feature/agentless/response.rb +19 -0
- data/lib/datadog/open_feature/agentless/transport.rb +103 -0
- data/lib/datadog/open_feature/component.rb +57 -1
- data/lib/datadog/open_feature/configuration/agentless_endpoint.rb +90 -0
- data/lib/datadog/open_feature/configuration/source/resolution.rb +22 -0
- data/lib/datadog/open_feature/configuration/source.rb +43 -0
- data/lib/datadog/open_feature/configuration.rb +118 -0
- data/lib/datadog/open_feature/native_evaluator.rb +1 -14
- data/lib/datadog/open_feature/remote.rb +5 -5
- data/lib/datadog/open_feature/transport.rb +5 -0
- data/lib/datadog/opentelemetry/signal_configuration.rb +13 -9
- data/lib/datadog/profiling/collectors/cpu_and_wall_time_worker.rb +2 -2
- data/lib/datadog/profiling/collectors/idle_sampling_helper.rb +5 -7
- data/lib/datadog/profiling/collectors/info.rb +2 -6
- data/lib/datadog/symbol_database/extractor.rb +84 -102
- data/lib/datadog/tracing/component.rb +2 -0
- data/lib/datadog/tracing/configuration/ext.rb +1 -0
- data/lib/datadog/tracing/configuration/settings.rb +6 -0
- data/lib/datadog/tracing/context.rb +24 -2
- data/lib/datadog/tracing/context_provider.rb +9 -5
- data/lib/datadog/tracing/contrib/grape/endpoint.rb +32 -5
- data/lib/datadog/tracing/contrib/sequel/jdbc_connection_string.rb +140 -0
- data/lib/datadog/tracing/contrib/sequel/utils.rb +49 -62
- data/lib/datadog/tracing/metadata/ext.rb +3 -0
- data/lib/datadog/tracing/otel_thread_context.rb +78 -9
- data/lib/datadog/tracing/tracer.rb +15 -2
- data/lib/datadog/tracing/transport/native.rb +18 -26
- data/lib/datadog/tracing/transport/span_events_negotiation.rb +41 -0
- data/lib/datadog/tracing/transport/trace_formatter.rb +13 -3
- data/lib/datadog/tracing/transport/traces.rb +3 -23
- data/lib/datadog/version.rb +1 -1
- metadata +25 -7
- data/lib/datadog/ai_guard/evaluation/content_builder.rb +0 -31
|
@@ -10,7 +10,6 @@ module Datadog
|
|
|
10
10
|
class IdleSamplingHelper
|
|
11
11
|
# @rbs @worker_thread: untyped
|
|
12
12
|
# @rbs @start_stop_mutex: ::Thread::Mutex
|
|
13
|
-
# @rbs @thread_context_collector: Datadog::Profiling::Collectors::ThreadContext?
|
|
14
13
|
|
|
15
14
|
private
|
|
16
15
|
|
|
@@ -18,15 +17,14 @@ module Datadog
|
|
|
18
17
|
|
|
19
18
|
public
|
|
20
19
|
|
|
21
|
-
#: (
|
|
22
|
-
def initialize
|
|
20
|
+
#: () -> void
|
|
21
|
+
def initialize
|
|
23
22
|
@worker_thread = nil
|
|
24
23
|
@start_stop_mutex = Mutex.new
|
|
25
|
-
@thread_context_collector = thread_context_collector
|
|
26
24
|
end
|
|
27
25
|
|
|
28
|
-
#: () ->
|
|
29
|
-
def start
|
|
26
|
+
#: (CpuAndWallTimeWorker cpu_and_wall_time_worker) -> true?
|
|
27
|
+
def start(cpu_and_wall_time_worker)
|
|
30
28
|
@start_stop_mutex.synchronize do
|
|
31
29
|
return if @worker_thread&.alive?
|
|
32
30
|
|
|
@@ -39,7 +37,7 @@ module Datadog
|
|
|
39
37
|
@worker_thread = Thread.new do
|
|
40
38
|
Thread.current.name = self.class.name
|
|
41
39
|
|
|
42
|
-
self.class._native_idle_sampling_loop(self,
|
|
40
|
+
self.class._native_idle_sampling_loop(self, cpu_and_wall_time_worker)
|
|
43
41
|
|
|
44
42
|
Datadog.logger.debug("IdleSamplingHelper thread stopping cleanly")
|
|
45
43
|
rescue Exception => e # rubocop:disable Lint/RescueException
|
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require "set"
|
|
4
|
-
require "time"
|
|
5
3
|
require "libdatadog"
|
|
4
|
+
require_relative "../../core/utils/time"
|
|
6
5
|
|
|
7
6
|
module Datadog
|
|
8
7
|
module Profiling
|
|
@@ -71,10 +70,7 @@ module Datadog
|
|
|
71
70
|
# Instead of trying to figure out real process start time by checking
|
|
72
71
|
# /proc or some other complex/non-portable way, approximate start time
|
|
73
72
|
# by time of requirement of this file.
|
|
74
|
-
|
|
75
|
-
# Note: this does not use Core::Utils::Time.now because this constant
|
|
76
|
-
# gets initialized before a user has a chance to configure the library.
|
|
77
|
-
START_TIME = Time.now.utc.freeze
|
|
73
|
+
START_TIME = Datadog::Core::Utils::Time.now.utc.freeze
|
|
78
74
|
|
|
79
75
|
#: () -> ::Hash[::Symbol, untyped]
|
|
80
76
|
def collect_platform_info
|
|
@@ -43,7 +43,7 @@ module Datadog
|
|
|
43
43
|
# These are expected failures — no logging needed.
|
|
44
44
|
#
|
|
45
45
|
# 2. **Method-level rescues** (`rescue => e` with logging):
|
|
46
|
-
# Catch failures in
|
|
46
|
+
# Catch failures in build_class_method_scopes, find_source_file, etc. Log at debug
|
|
47
47
|
# for post-hoc diagnosis, return nil or empty array. One bad method/module
|
|
48
48
|
# doesn't kill the entire class extraction.
|
|
49
49
|
#
|
|
@@ -133,16 +133,16 @@ module Datadog
|
|
|
133
133
|
return nil unless Module === mod
|
|
134
134
|
mod_name = safe_mod_name(mod)
|
|
135
135
|
return nil unless mod_name
|
|
136
|
-
|
|
137
|
-
return nil unless user_code_module?(mod)
|
|
136
|
+
return nil unless user_code_module_name?(mod)
|
|
138
137
|
|
|
139
138
|
source_file = find_source_file(mod)
|
|
140
139
|
return nil unless source_file
|
|
140
|
+
return nil unless user_code_path?(source_file)
|
|
141
141
|
|
|
142
142
|
inner_scope = if Class === mod
|
|
143
|
-
extract_class_scope(mod)
|
|
143
|
+
extract_class_scope(mod, source_file)
|
|
144
144
|
else
|
|
145
|
-
extract_module_scope(mod)
|
|
145
|
+
extract_module_scope(mod, source_file)
|
|
146
146
|
end
|
|
147
147
|
|
|
148
148
|
wrap_in_file_scope(source_file, [inner_scope])
|
|
@@ -275,10 +275,12 @@ module Datadog
|
|
|
275
275
|
false
|
|
276
276
|
end
|
|
277
277
|
|
|
278
|
-
#
|
|
279
|
-
#
|
|
280
|
-
#
|
|
281
|
-
|
|
278
|
+
# Return whether +mod+ passes the name-based user-code exclusions
|
|
279
|
+
# (named, outside Datadog, not a Ruby root class). Does not resolve
|
|
280
|
+
# a source file.
|
|
281
|
+
# @param mod [Module]
|
|
282
|
+
# @return [Boolean]
|
|
283
|
+
def user_code_module_name?(mod)
|
|
282
284
|
mod_name = safe_mod_name(mod)
|
|
283
285
|
return false unless mod_name
|
|
284
286
|
|
|
@@ -297,6 +299,15 @@ module Datadog
|
|
|
297
299
|
return false if mod.equal?(Object) || mod.equal?(BasicObject) ||
|
|
298
300
|
mod.equal?(Kernel) || mod.equal?(Module) || mod.equal?(Class)
|
|
299
301
|
|
|
302
|
+
true
|
|
303
|
+
end
|
|
304
|
+
|
|
305
|
+
# Check if module is from user code (not gems or stdlib)
|
|
306
|
+
# @param mod [Module] The module to check
|
|
307
|
+
# @return [Boolean] true if user code
|
|
308
|
+
def user_code_module?(mod)
|
|
309
|
+
return false unless user_code_module_name?(mod)
|
|
310
|
+
|
|
300
311
|
source_file = find_source_file(mod)
|
|
301
312
|
return false unless source_file
|
|
302
313
|
|
|
@@ -331,6 +342,25 @@ module Datadog
|
|
|
331
342
|
true
|
|
332
343
|
end
|
|
333
344
|
|
|
345
|
+
# Resolve the UnboundMethod +mod+ itself declares for +method_name+,
|
|
346
|
+
# skipping prepended wrapper modules (e.g. a DI method-logpoint wrapper)
|
|
347
|
+
# that shadow it.
|
|
348
|
+
#
|
|
349
|
+
# @param mod [Module] Module or class expected to declare the method
|
|
350
|
+
# @param method_name [Symbol] Name of the method to resolve
|
|
351
|
+
# @return [UnboundMethod] Method whose owner is +mod+, or the
|
|
352
|
+
# prepend-resolved method when +mod+ owns no method in the super chain
|
|
353
|
+
def declared_instance_method(mod, method_name)
|
|
354
|
+
original_method = mod.instance_method(method_name)
|
|
355
|
+
method = original_method
|
|
356
|
+
until method.owner.equal?(mod)
|
|
357
|
+
super_method = method.super_method
|
|
358
|
+
return original_method unless super_method
|
|
359
|
+
method = super_method
|
|
360
|
+
end
|
|
361
|
+
method
|
|
362
|
+
end
|
|
363
|
+
|
|
334
364
|
# Find source file for a module.
|
|
335
365
|
# Prefers user code paths over gem/stdlib paths. ActiveRecord models have
|
|
336
366
|
# generated methods (autosave callbacks) whose source is in the gem, but
|
|
@@ -349,7 +379,7 @@ module Datadog
|
|
|
349
379
|
|
|
350
380
|
# Try instance methods first
|
|
351
381
|
mod.instance_methods(false).each do |method_name|
|
|
352
|
-
method = mod
|
|
382
|
+
method = declared_instance_method(mod, method_name)
|
|
353
383
|
location = method.source_location
|
|
354
384
|
next unless location
|
|
355
385
|
|
|
@@ -357,10 +387,15 @@ module Datadog
|
|
|
357
387
|
return path if user_code_path?(path)
|
|
358
388
|
|
|
359
389
|
fallback ||= path # steep:ignore
|
|
390
|
+
rescue Exception => e # standard:disable Lint/RescueException
|
|
391
|
+
Datadog::DI.reraise_if_fatal(e)
|
|
392
|
+
@logger.debug { "symdb: error resolving #{safe_mod_name(mod)}##{method_name}: #{e.class}: #{e.message}" }
|
|
360
393
|
end
|
|
361
394
|
|
|
362
395
|
# Try singleton methods
|
|
363
396
|
mod.singleton_methods(false).each do |method_name|
|
|
397
|
+
# Method-logpoint probes prepend instance methods only, so singleton
|
|
398
|
+
# methods are never shadowed by a DI wrapper.
|
|
364
399
|
method = mod.method(method_name)
|
|
365
400
|
location = method.source_location
|
|
366
401
|
next unless location
|
|
@@ -369,6 +404,9 @@ module Datadog
|
|
|
369
404
|
return path if user_code_path?(path)
|
|
370
405
|
|
|
371
406
|
fallback ||= path # steep:ignore
|
|
407
|
+
rescue Exception => e # standard:disable Lint/RescueException
|
|
408
|
+
Datadog::DI.reraise_if_fatal(e)
|
|
409
|
+
@logger.debug { "symdb: error resolving #{safe_mod_name(mod)}.#{method_name}: #{e.class}: #{e.message}" }
|
|
372
410
|
end
|
|
373
411
|
|
|
374
412
|
# Use const_source_location to find where this class/module is declared.
|
|
@@ -460,10 +498,9 @@ module Datadog
|
|
|
460
498
|
# Extract MODULE scope (without file_hash — that belongs on the FILE root scope).
|
|
461
499
|
# Does not include nested classes — nesting is handled by extract_all via FQN splitting.
|
|
462
500
|
# @param mod [Module] The module
|
|
501
|
+
# @param source_file [String] Source file resolved by the caller
|
|
463
502
|
# @return [Scope] The module scope
|
|
464
|
-
def extract_module_scope(mod)
|
|
465
|
-
source_file = find_source_file(mod)
|
|
466
|
-
|
|
503
|
+
def extract_module_scope(mod, source_file)
|
|
467
504
|
Scope.new(
|
|
468
505
|
scope_type: "MODULE",
|
|
469
506
|
name: safe_mod_name(mod),
|
|
@@ -476,11 +513,11 @@ module Datadog
|
|
|
476
513
|
|
|
477
514
|
# Extract CLASS scope
|
|
478
515
|
# @param klass [Class] The class
|
|
516
|
+
# @param source_file [String] Source file resolved by the caller
|
|
479
517
|
# @return [Scope] The class scope
|
|
480
|
-
def extract_class_scope(klass)
|
|
481
|
-
|
|
482
|
-
start_line, end_line =
|
|
483
|
-
source_file = find_source_file(klass)
|
|
518
|
+
def extract_class_scope(klass, source_file)
|
|
519
|
+
method_scopes = build_class_method_scopes(klass)
|
|
520
|
+
start_line, end_line = line_range_from_scopes(method_scopes)
|
|
484
521
|
|
|
485
522
|
Scope.new(
|
|
486
523
|
scope_type: "CLASS",
|
|
@@ -489,36 +526,20 @@ module Datadog
|
|
|
489
526
|
start_line: start_line,
|
|
490
527
|
end_line: end_line,
|
|
491
528
|
language_specifics: build_class_language_specifics(klass),
|
|
492
|
-
scopes:
|
|
529
|
+
scopes: method_scopes,
|
|
493
530
|
symbols: extract_scope_symbols(klass)
|
|
494
531
|
)
|
|
495
532
|
end
|
|
496
533
|
|
|
497
|
-
#
|
|
498
|
-
#
|
|
499
|
-
#
|
|
500
|
-
# @param
|
|
501
|
-
# @param methods [Array<Symbol>] Method names
|
|
534
|
+
# Derive a [start_line, end_line] range from method scopes, ignoring
|
|
535
|
+
# UNKNOWN sentinel values. The end is the max of method end_lines so a
|
|
536
|
+
# multi-line method's last line is not truncated to its start_line.
|
|
537
|
+
# @param method_scopes [Array<Scope>]
|
|
502
538
|
# @return [Array<Integer, Integer>] [start_line, end_line]
|
|
503
|
-
def
|
|
504
|
-
starts =
|
|
505
|
-
ends =
|
|
506
|
-
|
|
507
|
-
method = klass.instance_method(method_name)
|
|
508
|
-
location = method.source_location
|
|
509
|
-
next unless location && location[0]
|
|
510
|
-
starts << location[1]
|
|
511
|
-
_ranges, method_end = extract_targetable_lines(method, location[1])
|
|
512
|
-
ends << method_end
|
|
513
|
-
end
|
|
514
|
-
|
|
515
|
-
return [UNKNOWN_MIN_LINE, UNKNOWN_MAX_LINE] if starts.empty?
|
|
516
|
-
|
|
517
|
-
[starts.min, ends.max]
|
|
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}" }
|
|
521
|
-
[UNKNOWN_MIN_LINE, UNKNOWN_MAX_LINE]
|
|
539
|
+
def line_range_from_scopes(method_scopes)
|
|
540
|
+
starts = method_scopes.map(&:start_line).reject { |line| line == UNKNOWN_MIN_LINE }
|
|
541
|
+
ends = method_scopes.map(&:end_line).reject { |line| line == UNKNOWN_MAX_LINE }
|
|
542
|
+
[starts.min || UNKNOWN_MIN_LINE, ends.max || UNKNOWN_MAX_LINE]
|
|
522
543
|
end
|
|
523
544
|
|
|
524
545
|
# Build language specifics for CLASS
|
|
@@ -566,66 +587,33 @@ module Datadog
|
|
|
566
587
|
{}
|
|
567
588
|
end
|
|
568
589
|
|
|
569
|
-
#
|
|
570
|
-
#
|
|
571
|
-
# @
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
# Get all instance methods (public, protected, private)
|
|
576
|
-
all_instance_methods = klass.instance_methods(false) +
|
|
590
|
+
# Build a METHOD scope for each user-code instance method +klass+
|
|
591
|
+
# declares.
|
|
592
|
+
# @param klass [Class]
|
|
593
|
+
# @return [Array<Scope>]
|
|
594
|
+
def build_class_method_scopes(klass)
|
|
595
|
+
method_names = (klass.instance_methods(false) +
|
|
577
596
|
klass.protected_instance_methods(false) +
|
|
578
|
-
klass.private_instance_methods(false)
|
|
579
|
-
all_instance_methods.uniq!
|
|
597
|
+
klass.private_instance_methods(false)).uniq
|
|
580
598
|
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
599
|
+
Core::Utils::EnumerableCompat.filter_map(method_names) do |method_name|
|
|
600
|
+
method = declared_instance_method(klass, method_name)
|
|
601
|
+
location = method.source_location
|
|
602
|
+
next unless location
|
|
603
|
+
next unless user_code_path?(location[0])
|
|
585
604
|
|
|
586
|
-
|
|
605
|
+
build_instance_method_scope(klass, method_name, method)
|
|
606
|
+
rescue Exception => e # standard:disable Lint/RescueException
|
|
607
|
+
Datadog::DI.reraise_if_fatal(e)
|
|
608
|
+
@logger.debug { "symdb: error resolving #{safe_mod_name(klass)}##{method_name}: #{e.class}: #{e.message}" }
|
|
609
|
+
nil
|
|
610
|
+
end
|
|
587
611
|
rescue Exception => e # standard:disable Lint/RescueException
|
|
588
612
|
Datadog::DI.reraise_if_fatal(e)
|
|
589
613
|
@logger.debug { "symdb: failed to extract methods from #{safe_mod_name(klass)}: #{e.class}: #{e.message}" }
|
|
590
614
|
[]
|
|
591
615
|
end
|
|
592
616
|
|
|
593
|
-
# Extract a single method scope
|
|
594
|
-
# @param klass [Class] The class
|
|
595
|
-
# @param method_name [Symbol] Method name
|
|
596
|
-
# @param method_type [Symbol] :instance or :class
|
|
597
|
-
# @return [Scope, nil] Method scope or nil
|
|
598
|
-
def extract_method_scope(klass, method_name, method_type)
|
|
599
|
-
method = klass.instance_method(method_name)
|
|
600
|
-
location = method.source_location
|
|
601
|
-
|
|
602
|
-
return nil unless location # Skip methods without source location
|
|
603
|
-
|
|
604
|
-
source_file, line = location
|
|
605
|
-
return nil unless user_code_path?(source_file) # Skip gem/stdlib methods
|
|
606
|
-
|
|
607
|
-
targetable_lines, end_line = extract_targetable_lines(method, line)
|
|
608
|
-
|
|
609
|
-
Scope.new(
|
|
610
|
-
scope_type: "METHOD",
|
|
611
|
-
name: method_name.to_s,
|
|
612
|
-
source_file: source_file,
|
|
613
|
-
start_line: line,
|
|
614
|
-
end_line: end_line,
|
|
615
|
-
targetable_lines: targetable_lines,
|
|
616
|
-
language_specifics: {
|
|
617
|
-
visibility: method_visibility(klass, method_name),
|
|
618
|
-
method_type: method_type.to_s,
|
|
619
|
-
arity: method.arity,
|
|
620
|
-
},
|
|
621
|
-
symbols: extract_method_parameters(method)
|
|
622
|
-
)
|
|
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}" }
|
|
626
|
-
nil
|
|
627
|
-
end
|
|
628
|
-
|
|
629
617
|
# Get method visibility
|
|
630
618
|
# @param klass [Class] The class
|
|
631
619
|
# @param method_name [Symbol] Method name
|
|
@@ -808,7 +796,7 @@ module Datadog
|
|
|
808
796
|
# visibilities without an intermediate merged array.
|
|
809
797
|
[mod.instance_methods(false), mod.private_instance_methods(false)].each do |method_names|
|
|
810
798
|
method_names.each do |method_name|
|
|
811
|
-
method = mod
|
|
799
|
+
method = declared_instance_method(mod, method_name)
|
|
812
800
|
loc = method.source_location
|
|
813
801
|
next unless loc
|
|
814
802
|
next unless user_code_path?(loc[0])
|
|
@@ -852,7 +840,7 @@ module Datadog
|
|
|
852
840
|
# live only as long as the tree node holds them; they are released when
|
|
853
841
|
# convert_tree_to_scope finishes building the file's Scope.
|
|
854
842
|
method_infos = Core::Utils::EnumerableCompat.filter_map(method_names) do |name|
|
|
855
|
-
method = mod
|
|
843
|
+
method = declared_instance_method(mod, name)
|
|
856
844
|
# Pass 1 (build_per_file_index) recorded this method under file_path.
|
|
857
845
|
# If the method has been redefined in another file between the two
|
|
858
846
|
# passes (e.g. a class reopened during a Rails reload while extract_all
|
|
@@ -989,13 +977,7 @@ module Datadog
|
|
|
989
977
|
# Recurse into child scopes (nested modules/classes)
|
|
990
978
|
child_scopes = node[:children].values.map { |child| convert_node_to_scope(child) }
|
|
991
979
|
|
|
992
|
-
|
|
993
|
-
# method end. Using max(start_line) would underreport the class's end_line for
|
|
994
|
-
# classes whose last method spans multiple lines.
|
|
995
|
-
starts = method_scopes.map(&:start_line).reject { |l| l == UNKNOWN_MIN_LINE } # steep:ignore
|
|
996
|
-
ends = method_scopes.map(&:end_line).reject { |l| l == UNKNOWN_MAX_LINE } # steep:ignore
|
|
997
|
-
start_line = starts.empty? ? UNKNOWN_MIN_LINE : starts.min
|
|
998
|
-
end_line = ends.empty? ? UNKNOWN_MAX_LINE : ends.max
|
|
980
|
+
start_line, end_line = line_range_from_scopes(method_scopes)
|
|
999
981
|
|
|
1000
982
|
# Extract symbols (constants, class variables) if we have the actual module object
|
|
1001
983
|
symbols = node[:mod] ? extract_scope_symbols(node[:mod]) : []
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require_relative "otel_thread_context"
|
|
3
4
|
require_relative "tracer"
|
|
4
5
|
require_relative "flush"
|
|
5
6
|
require_relative "sync_writer"
|
|
@@ -42,6 +43,7 @@ module Datadog
|
|
|
42
43
|
default_service: settings.service,
|
|
43
44
|
enabled: settings.tracing.enabled,
|
|
44
45
|
logger: logger,
|
|
46
|
+
otel_thread_context: OTelThreadContext.build(settings.tracing),
|
|
45
47
|
trace_flush: trace_flush,
|
|
46
48
|
sampler: sampler_delegator,
|
|
47
49
|
span_sampler: build_span_sampler(settings),
|
|
@@ -13,6 +13,7 @@ module Datadog
|
|
|
13
13
|
ENV_BAGGAGE_TAG_KEYS = "DD_TRACE_BAGGAGE_TAG_KEYS"
|
|
14
14
|
ENV_TRACE_ID_128_BIT_GENERATION_ENABLED = "DD_TRACE_128_BIT_TRACEID_GENERATION_ENABLED"
|
|
15
15
|
ENV_NATIVE_SPAN_EVENTS = "DD_TRACE_NATIVE_SPAN_EVENTS"
|
|
16
|
+
ENV_OTEL_CONTEXT_ENABLED = "DD_TRACE_OTEL_CTX_ENABLED"
|
|
16
17
|
ENV_RESOURCE_RENAMING_ENABLED = "DD_TRACE_RESOURCE_RENAMING_ENABLED"
|
|
17
18
|
ENV_RESOURCE_RENAMING_ALWAYS_SIMPLIFIED_ENDPOINT = "DD_TRACE_RESOURCE_RENAMING_ALWAYS_SIMPLIFIED_ENDPOINT"
|
|
18
19
|
ENV_EXPERIMENTAL_PROPAGATE_PROCESS_TAGS_ENABLED = "DD_EXPERIMENTAL_PROPAGATE_PROCESS_TAGS_ENABLED"
|
|
@@ -255,6 +255,12 @@ module Datadog
|
|
|
255
255
|
o.type :bool
|
|
256
256
|
end
|
|
257
257
|
|
|
258
|
+
option :otel_thread_context_enabled do |o|
|
|
259
|
+
o.env Tracing::Configuration::Ext::ENV_OTEL_CONTEXT_ENABLED
|
|
260
|
+
o.default true
|
|
261
|
+
o.type :bool
|
|
262
|
+
end
|
|
263
|
+
|
|
258
264
|
# Configures an alternative trace transport behavior, where
|
|
259
265
|
# traces can be sent to the agent and backend before all spans
|
|
260
266
|
# have finished.
|
|
@@ -20,9 +20,15 @@ module Datadog
|
|
|
20
20
|
attr_reader \
|
|
21
21
|
:active_trace
|
|
22
22
|
|
|
23
|
+
attr_writer \
|
|
24
|
+
:otel_thread_context
|
|
25
|
+
|
|
23
26
|
def initialize(
|
|
24
|
-
trace: nil
|
|
27
|
+
trace: nil,
|
|
28
|
+
otel_thread_context: nil
|
|
25
29
|
)
|
|
30
|
+
@otel_thread_context = otel_thread_context
|
|
31
|
+
@active_trace = nil
|
|
26
32
|
activate!(trace)
|
|
27
33
|
end
|
|
28
34
|
|
|
@@ -54,14 +60,30 @@ module Datadog
|
|
|
54
60
|
# Creates a copy of the context, when forked.
|
|
55
61
|
def fork_clone
|
|
56
62
|
forked_trace = @active_trace&.fork_clone
|
|
57
|
-
|
|
63
|
+
|
|
64
|
+
self.class.new(
|
|
65
|
+
trace: forked_trace,
|
|
66
|
+
otel_thread_context: @otel_thread_context
|
|
67
|
+
)
|
|
58
68
|
end
|
|
59
69
|
|
|
60
70
|
private
|
|
61
71
|
|
|
62
72
|
def set_active_trace!(trace)
|
|
73
|
+
previous_trace = @active_trace
|
|
74
|
+
|
|
63
75
|
# Don't retain finished traces
|
|
64
76
|
@active_trace = (trace && !trace.finished?) ? trace : nil
|
|
77
|
+
|
|
78
|
+
return @active_trace if previous_trace == @active_trace
|
|
79
|
+
|
|
80
|
+
if @active_trace
|
|
81
|
+
@otel_thread_context&.update_from_trace_op(@active_trace)
|
|
82
|
+
elsif previous_trace
|
|
83
|
+
@otel_thread_context&.clear
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
@active_trace
|
|
65
87
|
end
|
|
66
88
|
end
|
|
67
89
|
end
|
|
@@ -12,12 +12,15 @@ module Datadog
|
|
|
12
12
|
# @see https://ruby-doc.org/core-3.1.2/Thread.html#method-i-5B-5D Thread attributes are fiber-local
|
|
13
13
|
class DefaultContextProvider
|
|
14
14
|
# Initializes the default context provider with a fiber-bound context.
|
|
15
|
-
def initialize
|
|
16
|
-
@
|
|
15
|
+
def initialize(otel_thread_context: nil)
|
|
16
|
+
@otel_thread_context = otel_thread_context
|
|
17
|
+
@context = FiberLocalContext.new(otel_thread_context: otel_thread_context)
|
|
17
18
|
end
|
|
18
19
|
|
|
19
20
|
# Sets the current context.
|
|
20
21
|
def context=(ctx)
|
|
22
|
+
ctx.otel_thread_context = @otel_thread_context if ctx.respond_to?(:otel_thread_context=)
|
|
23
|
+
|
|
21
24
|
@context.local = ctx
|
|
22
25
|
end
|
|
23
26
|
|
|
@@ -48,10 +51,11 @@ module Datadog
|
|
|
48
51
|
class FiberLocalContext
|
|
49
52
|
# To support multiple tracers simultaneously, each {Datadog::Tracing::FiberLocalContext}
|
|
50
53
|
# instance has its own fiber-local variable.
|
|
51
|
-
def initialize
|
|
54
|
+
def initialize(otel_thread_context: nil)
|
|
55
|
+
@otel_thread_context = otel_thread_context
|
|
52
56
|
@key = :"datadog_context_#{FiberLocalContext.next_instance_id}"
|
|
53
57
|
|
|
54
|
-
self.local = Context.new
|
|
58
|
+
self.local = Context.new(otel_thread_context: otel_thread_context)
|
|
55
59
|
end
|
|
56
60
|
|
|
57
61
|
# Override the fiber-local context with a new context.
|
|
@@ -61,7 +65,7 @@ module Datadog
|
|
|
61
65
|
|
|
62
66
|
# Return the fiber-local context.
|
|
63
67
|
def local(storage = Thread.current)
|
|
64
|
-
storage[@key] ||= Context.new
|
|
68
|
+
storage[@key] ||= Context.new(otel_thread_context: @otel_thread_context)
|
|
65
69
|
end
|
|
66
70
|
|
|
67
71
|
# Ensure two instances of {FiberLocalContext} do not conflict.
|
|
@@ -15,6 +15,7 @@ module Datadog
|
|
|
15
15
|
module Endpoint
|
|
16
16
|
KEY_RUN = "datadog_grape_endpoint_run"
|
|
17
17
|
KEY_RENDER = "datadog_grape_endpoint_render"
|
|
18
|
+
FORMAT_SEGMENT = /\(\.:?\w+\)\z/
|
|
18
19
|
|
|
19
20
|
class << self
|
|
20
21
|
def subscribe
|
|
@@ -43,8 +44,8 @@ module Datadog
|
|
|
43
44
|
# collect endpoint details
|
|
44
45
|
endpoint = payload.fetch(:endpoint)
|
|
45
46
|
env = payload.fetch(:env)
|
|
46
|
-
api_view = api_view(endpoint
|
|
47
|
-
request_method = endpoint
|
|
47
|
+
api_view = api_view(endpoint_api(endpoint))
|
|
48
|
+
request_method = endpoint_request_method(endpoint)
|
|
48
49
|
path = endpoint_expand_path(endpoint)
|
|
49
50
|
resource = "#{api_view} #{request_method} #{path}"
|
|
50
51
|
|
|
@@ -65,12 +66,16 @@ module Datadog
|
|
|
65
66
|
span.set_tag(Tracing::Metadata::Ext::TAG_SVC_SRC, Ext::TAG_COMPONENT)
|
|
66
67
|
|
|
67
68
|
if (grape_route = env["grape.routing_args"]) && grape_route[:route_info]
|
|
69
|
+
route_info = grape_route[:route_info]
|
|
70
|
+
# Grape 2.3 through 3.0 return nil from GreedyRoute#path; the
|
|
71
|
+
# path lives at pattern.path in those versions.
|
|
72
|
+
route_path = route_info.path || route_info.pattern&.path
|
|
68
73
|
trace.set_tag(
|
|
69
74
|
Tracing::Metadata::Ext::HTTP::TAG_ROUTE,
|
|
70
75
|
# here we are removing the format from the path:
|
|
71
76
|
# e.g. /path/to/resource(.json) => /path/to/resource
|
|
72
77
|
# e.g. /path/to/resource(.:format) => /path/to/resource
|
|
73
|
-
|
|
78
|
+
route_path&.gsub(FORMAT_SEGMENT, "")
|
|
74
79
|
)
|
|
75
80
|
|
|
76
81
|
trace.set_tag(Tracing::Metadata::Ext::HTTP::TAG_ROUTE_PATH, env["SCRIPT_NAME"])
|
|
@@ -96,8 +101,8 @@ module Datadog
|
|
|
96
101
|
begin
|
|
97
102
|
# collect endpoint details
|
|
98
103
|
endpoint = payload.fetch(:endpoint)
|
|
99
|
-
api_view = api_view(endpoint
|
|
100
|
-
request_method = endpoint
|
|
104
|
+
api_view = api_view(endpoint_api(endpoint))
|
|
105
|
+
request_method = endpoint_request_method(endpoint)
|
|
101
106
|
path = endpoint_expand_path(endpoint)
|
|
102
107
|
|
|
103
108
|
trace.resource = span.resource
|
|
@@ -266,8 +271,25 @@ module Datadog
|
|
|
266
271
|
end
|
|
267
272
|
end
|
|
268
273
|
|
|
274
|
+
# Grape 4 moved three values off the endpoint's public options Hash: the
|
|
275
|
+
# API class became the #api reader (ruby-grape/grape#2778), and the verb
|
|
276
|
+
# and path are read off the route (#2775, #2776). Each reader prefers the
|
|
277
|
+
# options Hash, so Grape 1.x through 3.x resolve as they did before.
|
|
278
|
+
def endpoint_api(endpoint)
|
|
279
|
+
endpoint.options.fetch(:for) { endpoint.api }
|
|
280
|
+
end
|
|
281
|
+
|
|
282
|
+
def endpoint_request_method(endpoint)
|
|
283
|
+
request_methods = endpoint.options[:method]
|
|
284
|
+
return request_methods.first if request_methods
|
|
285
|
+
|
|
286
|
+
endpoint.routes.first&.request_method
|
|
287
|
+
end
|
|
288
|
+
|
|
269
289
|
def endpoint_expand_path(endpoint)
|
|
270
290
|
route_path = endpoint.options[:path]
|
|
291
|
+
return endpoint_route_path(endpoint) if route_path.nil? || route_path.empty?
|
|
292
|
+
|
|
271
293
|
namespace = endpoint.routes.first&.namespace || ""
|
|
272
294
|
|
|
273
295
|
path = (namespace.split("/") + route_path)
|
|
@@ -277,6 +299,11 @@ module Datadog
|
|
|
277
299
|
path
|
|
278
300
|
end
|
|
279
301
|
|
|
302
|
+
# A compiled path already carries the namespace, so it is used whole.
|
|
303
|
+
def endpoint_route_path(endpoint)
|
|
304
|
+
endpoint.routes.first&.path&.sub(FORMAT_SEGMENT, "") || "/"
|
|
305
|
+
end
|
|
306
|
+
|
|
280
307
|
def service_name
|
|
281
308
|
datadog_configuration[:service_name]
|
|
282
309
|
end
|