datadog 2.41.0 → 2.43.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 +57 -1
- data/README.md +3 -0
- data/ext/datadog_profiling_native_extension/NativeExtensionDesign.md +12 -24
- data/ext/datadog_profiling_native_extension/collectors_cpu_and_wall_time_worker.c +120 -61
- data/ext/datadog_profiling_native_extension/collectors_stack.c +11 -3
- data/ext/datadog_profiling_native_extension/collectors_thread_context.c +156 -108
- data/ext/datadog_profiling_native_extension/collectors_thread_context.h +7 -3
- data/ext/datadog_profiling_native_extension/datadog_ruby_common.h +0 -10
- data/ext/datadog_profiling_native_extension/extconf.rb +52 -93
- data/ext/datadog_profiling_native_extension/heap_recorder.c +351 -255
- data/ext/datadog_profiling_native_extension/heap_recorder.h +50 -31
- data/ext/datadog_profiling_native_extension/native_extension_helpers.rb +0 -24
- data/ext/datadog_profiling_native_extension/private_vm_api_access.c +518 -442
- data/ext/datadog_profiling_native_extension/private_vm_api_access.h +15 -3
- data/ext/datadog_profiling_native_extension/profiling.c +2 -0
- data/ext/datadog_profiling_native_extension/ruby_helpers.c +1 -79
- data/ext/datadog_profiling_native_extension/ruby_helpers.h +0 -7
- data/ext/datadog_profiling_native_extension/stack_recorder.c +106 -61
- data/ext/datadog_profiling_native_extension/stack_recorder.h +12 -4
- data/ext/datadog_profiling_native_extension/time_helpers.h +2 -1
- data/ext/libdatadog_api/datadog_ruby_common.h +0 -10
- data/ext/libdatadog_api/di.c +10 -0
- data/ext/libdatadog_api/extconf.rb +3 -0
- data/ext/libdatadog_api/init.c +2 -0
- data/ext/libdatadog_api/otel_thread_context.c +247 -0
- data/ext/libdatadog_api/otel_thread_context.h +5 -0
- data/ext/libdatadog_api/trace_exporter.c +642 -22
- data/ext/libdatadog_extconf_helpers.rb +1 -1
- data/lib/datadog/appsec/assets/blocked.html +1 -108
- data/lib/datadog/core/configuration/components.rb +1 -0
- data/lib/datadog/core/crashtracking/component.rb +5 -1
- data/lib/datadog/data_streams/pathway_context.rb +20 -22
- data/lib/datadog/data_streams/processor.rb +31 -0
- data/lib/datadog/di/instrumenter.rb +41 -1
- data/lib/datadog/di/logger.rb +2 -2
- data/lib/datadog/di/probe.rb +9 -1
- data/lib/datadog/di/probe_notification_builder.rb +1 -0
- data/lib/datadog/di/remote.rb +3 -3
- data/lib/datadog/open_feature/evaluation_engine.rb +29 -3
- data/lib/datadog/open_feature/exposures/event.rb +10 -3
- data/lib/datadog/open_feature/ext.rb +19 -0
- data/lib/datadog/open_feature/flag_evaluation/aggregator.rb +236 -80
- data/lib/datadog/open_feature/flag_evaluation/writer.rb +179 -68
- data/lib/datadog/open_feature/hooks/flag_eval_evp_hook.rb +24 -21
- data/lib/datadog/open_feature/native_evaluator.rb +33 -6
- data/lib/datadog/open_feature/noop_evaluator.rb +5 -0
- data/lib/datadog/open_feature/provider.rb +11 -2
- data/lib/datadog/open_feature/transport.rb +5 -0
- data/lib/datadog/opentelemetry/sdk/propagator.rb +1 -1
- data/lib/datadog/opentelemetry/trace.rb +3 -0
- data/lib/datadog/profiling/collectors/cpu_and_wall_time_worker.rb +3 -0
- data/lib/datadog/profiling/collectors/info.rb +2 -6
- data/lib/datadog/profiling/collectors/thread_context.rb +0 -4
- data/lib/datadog/profiling/component.rb +8 -16
- data/lib/datadog/tracing/contrib/active_record/events/sql.rb +1 -0
- 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/distributed/baggage.rb +0 -1
- data/lib/datadog/tracing/distributed/datadog.rb +3 -3
- data/lib/datadog/tracing/distributed/propagation.rb +3 -0
- data/lib/datadog/tracing/distributed/trace_context.rb +14 -271
- data/lib/datadog/tracing/distributed/trace_state/datadog.rb +233 -0
- data/lib/datadog/tracing/distributed/trace_state/ext.rb +44 -0
- data/lib/datadog/tracing/distributed/trace_state/open_telemetry.rb +156 -0
- data/lib/datadog/tracing/distributed/trace_state.rb +121 -0
- data/lib/datadog/tracing/otel_thread_context.rb +34 -0
- data/lib/datadog/tracing/remote.rb +195 -27
- data/lib/datadog/tracing/sampling/rule_sampler.rb +2 -0
- data/lib/datadog/tracing/trace_digest.rb +22 -4
- data/lib/datadog/tracing/trace_operation.rb +22 -10
- data/lib/datadog/tracing/tracer.rb +5 -5
- data/lib/datadog/tracing/transport/native.rb +17 -25
- data/lib/datadog/tracing/transport/span_events_negotiation.rb +41 -0
- data/lib/datadog/tracing/transport/traces.rb +2 -22
- data/lib/datadog/version.rb +1 -1
- metadata +16 -8
- data/lib/datadog/tracing/distributed/datadog_tags_codec.rb +0 -69
|
@@ -33,6 +33,7 @@ module Datadog
|
|
|
33
33
|
# @rbs allocation_counting_enabled: bool
|
|
34
34
|
# @rbs gvl_profiling_enabled: bool
|
|
35
35
|
# @rbs sighandler_sampling_enabled: bool
|
|
36
|
+
# @rbs waiting_for_gvl_threshold_ns: ::Integer
|
|
36
37
|
# @rbs skip_idle_samples_for_testing: false
|
|
37
38
|
# @rbs return: void
|
|
38
39
|
def initialize(
|
|
@@ -45,6 +46,7 @@ module Datadog
|
|
|
45
46
|
gvl_profiling_enabled:,
|
|
46
47
|
sighandler_sampling_enabled:,
|
|
47
48
|
cpu_sampling_interval_ms:,
|
|
49
|
+
waiting_for_gvl_threshold_ns:,
|
|
48
50
|
# **NOTE**: This should only be used for testing; disabling the dynamic sampling rate will increase the
|
|
49
51
|
# profiler overhead!
|
|
50
52
|
dynamic_sampling_rate_enabled: true,
|
|
@@ -78,6 +80,7 @@ module Datadog
|
|
|
78
80
|
sighandler_sampling_enabled: sighandler_sampling_enabled,
|
|
79
81
|
skip_idle_samples_for_testing: skip_idle_samples_for_testing,
|
|
80
82
|
cpu_sampling_interval_ms: cpu_sampling_interval_ms,
|
|
83
|
+
waiting_for_gvl_threshold_ns: waiting_for_gvl_threshold_ns,
|
|
81
84
|
)
|
|
82
85
|
@worker_thread = nil
|
|
83
86
|
@failure_exception = nil
|
|
@@ -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
|
|
@@ -19,7 +19,6 @@ module Datadog
|
|
|
19
19
|
max_frames:,
|
|
20
20
|
tracer:,
|
|
21
21
|
endpoint_collection_enabled:,
|
|
22
|
-
waiting_for_gvl_threshold_ns:,
|
|
23
22
|
otel_context_enabled:,
|
|
24
23
|
native_filenames_enabled:,
|
|
25
24
|
show_classes:
|
|
@@ -31,7 +30,6 @@ module Datadog
|
|
|
31
30
|
max_frames: max_frames,
|
|
32
31
|
tracer_context_key: tracer_context_key,
|
|
33
32
|
endpoint_collection_enabled: endpoint_collection_enabled,
|
|
34
|
-
waiting_for_gvl_threshold_ns: waiting_for_gvl_threshold_ns,
|
|
35
33
|
otel_context_enabled: otel_context_enabled,
|
|
36
34
|
native_filenames_enabled: native_filenames_enabled,
|
|
37
35
|
show_classes: show_classes,
|
|
@@ -44,7 +42,6 @@ module Datadog
|
|
|
44
42
|
max_frames: 400,
|
|
45
43
|
tracer: nil,
|
|
46
44
|
endpoint_collection_enabled: false,
|
|
47
|
-
waiting_for_gvl_threshold_ns: 10_000_000,
|
|
48
45
|
otel_context_enabled: false,
|
|
49
46
|
native_filenames_enabled: true,
|
|
50
47
|
show_classes: false,
|
|
@@ -56,7 +53,6 @@ module Datadog
|
|
|
56
53
|
max_frames: max_frames,
|
|
57
54
|
tracer: tracer,
|
|
58
55
|
endpoint_collection_enabled: endpoint_collection_enabled,
|
|
59
|
-
waiting_for_gvl_threshold_ns: waiting_for_gvl_threshold_ns,
|
|
60
56
|
otel_context_enabled: otel_context_enabled,
|
|
61
57
|
native_filenames_enabled: native_filenames_enabled,
|
|
62
58
|
show_classes: show_classes,
|
|
@@ -65,6 +65,7 @@ module Datadog
|
|
|
65
65
|
gvl_profiling_enabled: enable_gvl_profiling?(settings, logger),
|
|
66
66
|
sighandler_sampling_enabled: settings.profiling.advanced.sighandler_sampling_enabled,
|
|
67
67
|
cpu_sampling_interval_ms: cpu_sampling_interval_ms,
|
|
68
|
+
waiting_for_gvl_threshold_ns: settings.profiling.advanced.waiting_for_gvl_threshold_ns,
|
|
68
69
|
)
|
|
69
70
|
|
|
70
71
|
internal_metadata = {
|
|
@@ -102,7 +103,6 @@ module Datadog
|
|
|
102
103
|
max_frames: settings.profiling.advanced.max_frames,
|
|
103
104
|
tracer: optional_tracer,
|
|
104
105
|
endpoint_collection_enabled: settings.profiling.advanced.endpoint.collection.enabled,
|
|
105
|
-
waiting_for_gvl_threshold_ns: settings.profiling.advanced.waiting_for_gvl_threshold_ns,
|
|
106
106
|
otel_context_enabled: settings.profiling.advanced.preview_otel_context_enabled,
|
|
107
107
|
native_filenames_enabled: settings.profiling.advanced.native_filenames_enabled,
|
|
108
108
|
show_classes: settings.profiling.advanced.experimental_show_classes_enabled,
|
|
@@ -227,11 +227,13 @@ module Datadog
|
|
|
227
227
|
return false
|
|
228
228
|
end
|
|
229
229
|
|
|
230
|
-
# Heap profiling
|
|
231
|
-
# (https://bugs.ruby-lang.org/issues/
|
|
232
|
-
if RubyVersion.is?("
|
|
230
|
+
# Heap profiling tracks live objects using an `ObjectSpace::WeakMap`, which could corrupt its internal
|
|
231
|
+
# state during compaction before these versions (https://bugs.ruby-lang.org/issues/19529).
|
|
232
|
+
if RubyVersion.is?("< 3.1.4") || RubyVersion.is?(">= 3.2", "< 3.2.3")
|
|
233
233
|
logger.warn(
|
|
234
|
-
"
|
|
234
|
+
"Current Ruby version (#{RUBY_VERSION}) cannot support heap profiling due to a VM bug. " \
|
|
235
|
+
"Please upgrade to Ruby >= 3.1.4 or >= 3.2.3 in order to use this feature. " \
|
|
236
|
+
"Heap profiling has been disabled."
|
|
235
237
|
)
|
|
236
238
|
return false
|
|
237
239
|
end
|
|
@@ -252,17 +254,7 @@ module Datadog
|
|
|
252
254
|
private_class_method def self.enable_heap_size_profiling?(settings, heap_profiling_enabled, logger)
|
|
253
255
|
heap_size_profiling_enabled = settings.profiling.advanced.experimental_heap_size_enabled
|
|
254
256
|
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
if RubyVersion.is?(">= 4")
|
|
258
|
-
logger.info(
|
|
259
|
-
"Heap live size profiling is currently incompatible with Ruby 4 and has been disabled. " \
|
|
260
|
-
"Heap live objects is not affected and remains enabled."
|
|
261
|
-
)
|
|
262
|
-
return false
|
|
263
|
-
end
|
|
264
|
-
|
|
265
|
-
true
|
|
257
|
+
heap_profiling_enabled && heap_size_profiling_enabled
|
|
266
258
|
end
|
|
267
259
|
|
|
268
260
|
private_class_method def self.no_signals_workaround_enabled?(settings, logger) # rubocop:disable Metrics/MethodLength
|
|
@@ -46,6 +46,7 @@ module Datadog
|
|
|
46
46
|
span.resource = payload.fetch(:sql)
|
|
47
47
|
span.type = Tracing::Metadata::Ext::SQL::TYPE
|
|
48
48
|
|
|
49
|
+
span.set_tag(Tracing::Metadata::Ext::TAG_KIND, Tracing::Metadata::Ext::SpanKind::TAG_CLIENT)
|
|
49
50
|
span.set_tag(Tracing::Metadata::Ext::TAG_COMPONENT, Ext::TAG_COMPONENT)
|
|
50
51
|
span.set_tag(Tracing::Metadata::Ext::TAG_OPERATION, Ext::TAG_OPERATION_SQL)
|
|
51
52
|
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Datadog
|
|
4
|
+
module Tracing
|
|
5
|
+
module Contrib
|
|
6
|
+
module Sequel
|
|
7
|
+
# Extracts connection metadata from JDBC connection strings.
|
|
8
|
+
module JDBCConnectionString
|
|
9
|
+
MAX_BYTES = 8_192
|
|
10
|
+
|
|
11
|
+
# Matches JDBC connection strings whose subname uses `[transport:]//host...`.
|
|
12
|
+
CONNECTION_STRING_PATTERN =
|
|
13
|
+
%r{\Ajdbc:[a-z][a-z0-9+.-]*:(?:[a-z][a-z0-9+.-]*:)?//(?<subname>.+)\z}im
|
|
14
|
+
|
|
15
|
+
# Extracts the host and port from a string.
|
|
16
|
+
# The host can be a multi-host value, a bracketed IPv6 host, or an ordinary host.
|
|
17
|
+
HOST_AND_PORT_PATTERN =
|
|
18
|
+
/\A(?:\[(?<ipv6_host>[^\[\]]+)\]|(?<host>[^,]+(?:,[^,]+)+|[^:]+))(?::(?<port>\d+))?\z/
|
|
19
|
+
|
|
20
|
+
DATABASE_PROPERTY_PATTERN =
|
|
21
|
+
/(?:\A|[&;])(?:databaseName|database)=(?<value>[^&;]+)/i
|
|
22
|
+
LIBRARIES_PROPERTY_PATTERN =
|
|
23
|
+
/(?:\A|[&;])libraries=,*(?<value>[^,&;]+)/i
|
|
24
|
+
DATABASE_PATH_DELIMITER_PATTERN = %r{[:@\[\]?&=#]}
|
|
25
|
+
|
|
26
|
+
private_constant :MAX_BYTES, :CONNECTION_STRING_PATTERN,
|
|
27
|
+
:DATABASE_PROPERTY_PATTERN, :LIBRARIES_PROPERTY_PATTERN,
|
|
28
|
+
:DATABASE_PATH_DELIMITER_PATTERN, :HOST_AND_PORT_PATTERN
|
|
29
|
+
|
|
30
|
+
class << self
|
|
31
|
+
# The returned Hash guarantees the existence of all keys,
|
|
32
|
+
# but Hash values can be `nil` when not parseable from the connection string.
|
|
33
|
+
#
|
|
34
|
+
# @param connection_string [String, nil] the JDBC connection string to parse
|
|
35
|
+
# @return [Hash{Symbol => String, nil}]
|
|
36
|
+
# - `:host` — host value when the subname uses authority syntax: `//host[:port]`
|
|
37
|
+
# - `:port` — port when the subname uses authority syntax: `//host[:port]`
|
|
38
|
+
# - `:database` — best-effort database name
|
|
39
|
+
def parse(connection_string)
|
|
40
|
+
# @type var result: metadata
|
|
41
|
+
result = {host: nil, port: nil, database: nil}
|
|
42
|
+
return result unless connection_string.is_a?(String) && connection_string.valid_encoding?
|
|
43
|
+
|
|
44
|
+
if connection_string.bytesize > MAX_BYTES
|
|
45
|
+
# Strip userinfo before truncation. Otherwise, an `@` beyond the limit could be
|
|
46
|
+
# discarded and leave a credential prefix looking like a valid authority/host.
|
|
47
|
+
authority_marker = connection_string.index("//")
|
|
48
|
+
if authority_marker
|
|
49
|
+
authority_start = authority_marker + 2
|
|
50
|
+
authority_end = [
|
|
51
|
+
connection_string.index("/", authority_start),
|
|
52
|
+
connection_string.index(";", authority_start),
|
|
53
|
+
connection_string.index("?", authority_start),
|
|
54
|
+
].compact.min || connection_string.length
|
|
55
|
+
userinfo_end = connection_string.rindex("@", authority_end - 1)
|
|
56
|
+
|
|
57
|
+
if userinfo_end && userinfo_end >= authority_start
|
|
58
|
+
connection_string =
|
|
59
|
+
# Steep: https://github.com/soutaro/steep/issues/1219
|
|
60
|
+
connection_string[0...authority_start] + # steep:ignore NoMethod
|
|
61
|
+
connection_string[(userinfo_end + 1)..-1].to_s
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
# Keep one extra byte, then let `chop` safely remove multi-byte unicode characters.
|
|
66
|
+
if connection_string.bytesize > MAX_BYTES
|
|
67
|
+
connection_string = connection_string.byteslice(0, MAX_BYTES + 1).chop
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
match = CONNECTION_STRING_PATTERN.match(connection_string)
|
|
72
|
+
return result unless match
|
|
73
|
+
|
|
74
|
+
# We start with: `host[:port][/database][;properties][?query]`.
|
|
75
|
+
subname = match[:subname]
|
|
76
|
+
|
|
77
|
+
# Extract `query` from the end, leaving `host[:port][/database][;properties]`.
|
|
78
|
+
subname, query_separator, query = subname.partition("?")
|
|
79
|
+
query = nil if query_separator.empty?
|
|
80
|
+
|
|
81
|
+
# Extract `properties` next, leaving `host[:port][/database]`.
|
|
82
|
+
subname, properties_separator, properties = subname.partition(";")
|
|
83
|
+
properties = nil if properties_separator.empty?
|
|
84
|
+
|
|
85
|
+
# Separate the authority (`host[:port]`) from the optional database path.
|
|
86
|
+
authority, path = subname.split("/", 2)
|
|
87
|
+
return result if authority.nil? || authority.empty?
|
|
88
|
+
|
|
89
|
+
host, port = host_and_port_from_authority(authority)
|
|
90
|
+
return result unless host
|
|
91
|
+
|
|
92
|
+
database = database_from_path(path) ||
|
|
93
|
+
database_from_properties(properties) || database_from_properties(query)
|
|
94
|
+
|
|
95
|
+
{host: host, port: port, database: database}
|
|
96
|
+
rescue Encoding::CompatibilityError, ArgumentError
|
|
97
|
+
result
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
private
|
|
101
|
+
|
|
102
|
+
def database_from_path(path)
|
|
103
|
+
return if path.nil? || path.empty?
|
|
104
|
+
|
|
105
|
+
database = path.split(DATABASE_PATH_DELIMITER_PATTERN, 2).first
|
|
106
|
+
return if database.nil? || database.empty?
|
|
107
|
+
|
|
108
|
+
database
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
def host_and_port_from_authority(authority)
|
|
112
|
+
# Discard optional `userinfo@`, retaining only `host[:port]`.
|
|
113
|
+
authority = authority.rpartition("@").last
|
|
114
|
+
return if authority.empty?
|
|
115
|
+
|
|
116
|
+
match = HOST_AND_PORT_PATTERN.match(authority)
|
|
117
|
+
return unless match
|
|
118
|
+
|
|
119
|
+
# Only one of `ipv6_host` or `host` will be populated.
|
|
120
|
+
host = match[:ipv6_host] || match[:host]
|
|
121
|
+
port = match[:port]
|
|
122
|
+
|
|
123
|
+
[host, port]
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
def database_from_properties(properties)
|
|
127
|
+
return unless properties
|
|
128
|
+
|
|
129
|
+
database = DATABASE_PROPERTY_PATTERN.match(properties)
|
|
130
|
+
return database[:value] if database
|
|
131
|
+
|
|
132
|
+
libraries = LIBRARIES_PROPERTY_PATTERN.match(properties)
|
|
133
|
+
libraries && libraries[:value]
|
|
134
|
+
end
|
|
135
|
+
end
|
|
136
|
+
end
|
|
137
|
+
end
|
|
138
|
+
end
|
|
139
|
+
end
|
|
140
|
+
end
|
|
@@ -1,10 +1,9 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require "uri"
|
|
4
|
-
|
|
5
3
|
require_relative "../../metadata/ext"
|
|
6
4
|
require_relative "../utils/database"
|
|
7
5
|
require_relative "ext"
|
|
6
|
+
require_relative "jdbc_connection_string"
|
|
8
7
|
require_relative "../ext"
|
|
9
8
|
require_relative "../span_attribute_schema"
|
|
10
9
|
|
|
@@ -14,11 +13,6 @@ module Datadog
|
|
|
14
13
|
module Sequel
|
|
15
14
|
# General purpose functions for Sequel
|
|
16
15
|
module Utils
|
|
17
|
-
JDBC_URI_PATTERN = %r{\Ajdbc:(?<vendor>[a-z][a-z0-9+.-]*):(?<location>//[^\r\n]*)\z}i
|
|
18
|
-
DATABASE_PROPERTY_PATTERN =
|
|
19
|
-
/(?:\A|[&;])(?<key>databaseName|database|libraries)=(?<value>[^&;]+)/i
|
|
20
|
-
private_constant :JDBC_URI_PATTERN, :DATABASE_PROPERTY_PATTERN
|
|
21
|
-
|
|
22
16
|
class << self
|
|
23
17
|
# Ruby database connector library
|
|
24
18
|
#
|
|
@@ -42,34 +36,6 @@ module Datadog
|
|
|
42
36
|
Contrib::Utils::Database.normalize_vendor(database.database_type.to_s)
|
|
43
37
|
end
|
|
44
38
|
|
|
45
|
-
# Parses URI-style JDBC connection strings, extracting host, port, and
|
|
46
|
-
# (best-effort) database name. Unsupported or ambiguous forms return empty
|
|
47
|
-
# metadata rather than potentially incorrect tags.
|
|
48
|
-
def parse_jdbc_uri(uri)
|
|
49
|
-
result = {host: nil, port: nil, database: nil}
|
|
50
|
-
return result unless uri.is_a?(String) && uri.valid_encoding?
|
|
51
|
-
|
|
52
|
-
match = JDBC_URI_PATTERN.match(uri)
|
|
53
|
-
return result unless match
|
|
54
|
-
|
|
55
|
-
vendor = match[:vendor].downcase
|
|
56
|
-
location, properties = match[:location].split(";", 2)
|
|
57
|
-
|
|
58
|
-
# Several JDBC vendors append properties with semicolons, outside the URI
|
|
59
|
-
# grammar. Parse the URI-compatible location separately from those properties.
|
|
60
|
-
parsed = URI.parse("#{vendor}:#{location}")
|
|
61
|
-
|
|
62
|
-
host = parsed.hostname
|
|
63
|
-
port = parsed.port
|
|
64
|
-
|
|
65
|
-
database = database_from_path(parsed.path) ||
|
|
66
|
-
database_from_properties(properties) || database_from_properties(parsed.query)
|
|
67
|
-
|
|
68
|
-
{host: host, port: port&.to_s, database: database}
|
|
69
|
-
rescue URI::InvalidURIError, Encoding::CompatibilityError, ArgumentError
|
|
70
|
-
result
|
|
71
|
-
end
|
|
72
|
-
|
|
73
39
|
def parse_opts(sql, opts, db_opts, dataset = nil)
|
|
74
40
|
# Prepared statements don't provide their sql query in the +sql+ parameter.
|
|
75
41
|
if !sql.is_a?(String) && dataset&.respond_to?(:prepared_sql) &&
|
|
@@ -121,25 +87,38 @@ module Datadog
|
|
|
121
87
|
Contrib::Analytics.set_sample_rate(span, analytics_sample_rate) if analytics_enabled?
|
|
122
88
|
end
|
|
123
89
|
|
|
124
|
-
# Resolves the connection host/port/database for a Sequel::Database.
|
|
125
|
-
#
|
|
126
|
-
#
|
|
90
|
+
# Resolves the connection host/port/database for a Sequel::Database. For Sequel's
|
|
91
|
+
# JDBC adapter (used on JRuby), metadata is parsed from the JDBC connection string
|
|
92
|
+
# regardless of whether opts[:host] is set.
|
|
127
93
|
def connection_metadata(db)
|
|
128
94
|
opts = db.opts || {}
|
|
129
95
|
host = opts[:host]
|
|
130
96
|
port = opts[:port]
|
|
131
97
|
database = opts[:database]
|
|
132
98
|
|
|
133
|
-
# A JDBC
|
|
134
|
-
#
|
|
135
|
-
|
|
136
|
-
is_jdbc =
|
|
99
|
+
# A JDBC connection string (in :uri, :url, or :database) can carry credentials, so
|
|
100
|
+
# always emit only parsed metadata -- never the raw connection string.
|
|
101
|
+
connection_string = opts[:uri] || opts[:url] || opts[:database]
|
|
102
|
+
is_jdbc = connection_string.is_a?(String) &&
|
|
103
|
+
connection_string.byteslice(0, 5)&.casecmp("jdbc:") == 0
|
|
137
104
|
if is_jdbc
|
|
138
|
-
parsed =
|
|
105
|
+
parsed = JDBCConnectionString.parse(connection_string)
|
|
106
|
+
|
|
107
|
+
# JNDI/DataSource-managed connections keep only a lookup name in opts (e.g.
|
|
108
|
+
# "jdbc:jndi:..."), so nothing can be parsed from it. Recover the endpoint from the
|
|
109
|
+
# live connection's JDBC metadata, the same way Sequel resolves JNDI. This stays a
|
|
110
|
+
# fallback rather than the primary source: it requires a connection checkout and
|
|
111
|
+
# some drivers report no connection string, whereas the opts value is free and
|
|
112
|
+
# already present for direct connections (and non-JDBC adapters have no such
|
|
113
|
+
# metadata at all).
|
|
114
|
+
if parsed[:host].nil? && parsed[:port].nil? && parsed[:database].nil?
|
|
115
|
+
fallback_metadata = jdbc_metadata_from_connection(db)
|
|
116
|
+
parsed = fallback_metadata if fallback_metadata
|
|
117
|
+
end
|
|
139
118
|
|
|
140
|
-
# Sequel's JDBC adapter connects with the
|
|
119
|
+
# Sequel's JDBC adapter connects with the connection string and ignores separate
|
|
141
120
|
# :host/:port options, unlike native adapters where those options take precedence.
|
|
142
|
-
if
|
|
121
|
+
if parsed[:host] || parsed[:port] || parsed[:database]
|
|
143
122
|
host = parsed[:host]
|
|
144
123
|
port = parsed[:port]
|
|
145
124
|
end
|
|
@@ -151,24 +130,32 @@ module Datadog
|
|
|
151
130
|
|
|
152
131
|
private
|
|
153
132
|
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
133
|
+
# Resolves host/port/database from the live connection's JDBC metadata
|
|
134
|
+
# (java.sql.DatabaseMetaData#getURL), for JNDI/DataSource connections whose opts hold
|
|
135
|
+
# only a lookup name. Returns the parsed metadata, or nil when it can't be resolved.
|
|
136
|
+
#
|
|
137
|
+
# Only *parsed, credential-free* metadata is memoized -- the raw connection string is
|
|
138
|
+
# used transiently and never stored or logged. A completed lookup that yields no usable
|
|
139
|
+
# connection string is a permanent property of the connection, so it is cached to avoid
|
|
140
|
+
# re-checking out a connection on every query. A raised error is treated as transient
|
|
141
|
+
# (pool checkout timeout, dropped connection, ...) and left uncached, so a later query
|
|
142
|
+
# can retry once connectivity recovers.
|
|
143
|
+
def jdbc_metadata_from_connection(db)
|
|
144
|
+
return db.instance_variable_get(:@datadog_jdbc_metadata) if db.instance_variable_defined?(:@datadog_jdbc_metadata)
|
|
145
|
+
|
|
146
|
+
connection_string =
|
|
147
|
+
begin
|
|
148
|
+
db.synchronize do |conn|
|
|
149
|
+
conn.get_meta_data.get_url if conn.respond_to?(:get_meta_data)
|
|
150
|
+
end
|
|
151
|
+
rescue => e
|
|
152
|
+
Datadog.logger.debug { "Sequel: unable to resolve JDBC connection metadata (#{e.class})" }
|
|
153
|
+
return nil
|
|
154
|
+
end
|
|
168
155
|
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
156
|
+
metadata = connection_string && JDBCConnectionString.parse(connection_string)
|
|
157
|
+
db.instance_variable_set(:@datadog_jdbc_metadata, metadata)
|
|
158
|
+
metadata
|
|
172
159
|
end
|
|
173
160
|
|
|
174
161
|
def datadog_configuration
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
require_relative "../metadata/ext"
|
|
4
4
|
require_relative "../trace_digest"
|
|
5
|
-
require_relative "
|
|
5
|
+
require_relative "trace_state/datadog"
|
|
6
6
|
require_relative "../utils"
|
|
7
7
|
require_relative "helpers"
|
|
8
8
|
|
|
@@ -129,7 +129,7 @@ module Datadog
|
|
|
129
129
|
def inject_tags!(tags, data)
|
|
130
130
|
return set_tags_propagation_error(reason: "disabled") if tags_disabled?
|
|
131
131
|
|
|
132
|
-
encoded_tags =
|
|
132
|
+
encoded_tags = TraceState::Datadog.encode(tags)
|
|
133
133
|
|
|
134
134
|
return set_tags_propagation_error(reason: "inject_max_size") if tags_too_large?(encoded_tags, scenario: "inject")
|
|
135
135
|
|
|
@@ -155,7 +155,7 @@ module Datadog
|
|
|
155
155
|
return set_tags_propagation_error(reason: "disabled") if tags_disabled?
|
|
156
156
|
return set_tags_propagation_error(reason: "extract_max_size") if tags_too_large?(tags, scenario: "extract")
|
|
157
157
|
|
|
158
|
-
tags_hash =
|
|
158
|
+
tags_hash = TraceState::Datadog.decode(tags)
|
|
159
159
|
# Only extract keys with the expected Datadog prefix
|
|
160
160
|
tags_hash.select! do |key, _|
|
|
161
161
|
key.start_with?(Tracing::Metadata::Ext::Distributed::TAGS_PREFIX) && key != EXCLUDED_TAG
|
|
@@ -146,6 +146,9 @@ module Datadog
|
|
|
146
146
|
span_id: parent_id,
|
|
147
147
|
trace_state: tracecontext_digest.trace_state,
|
|
148
148
|
trace_state_unknown_fields: tracecontext_digest.trace_state_unknown_fields,
|
|
149
|
+
trace_otel_random_value: tracecontext_digest.trace_otel_random_value,
|
|
150
|
+
trace_otel_threshold: tracecontext_digest.trace_otel_threshold,
|
|
151
|
+
trace_otel_unknown_fields: tracecontext_digest.trace_otel_unknown_fields,
|
|
149
152
|
trace_distributed_tags: distributed_tags
|
|
150
153
|
)
|
|
151
154
|
end
|