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.
Files changed (78) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +57 -1
  3. data/README.md +3 -0
  4. data/ext/datadog_profiling_native_extension/NativeExtensionDesign.md +12 -24
  5. data/ext/datadog_profiling_native_extension/collectors_cpu_and_wall_time_worker.c +120 -61
  6. data/ext/datadog_profiling_native_extension/collectors_stack.c +11 -3
  7. data/ext/datadog_profiling_native_extension/collectors_thread_context.c +156 -108
  8. data/ext/datadog_profiling_native_extension/collectors_thread_context.h +7 -3
  9. data/ext/datadog_profiling_native_extension/datadog_ruby_common.h +0 -10
  10. data/ext/datadog_profiling_native_extension/extconf.rb +52 -93
  11. data/ext/datadog_profiling_native_extension/heap_recorder.c +351 -255
  12. data/ext/datadog_profiling_native_extension/heap_recorder.h +50 -31
  13. data/ext/datadog_profiling_native_extension/native_extension_helpers.rb +0 -24
  14. data/ext/datadog_profiling_native_extension/private_vm_api_access.c +518 -442
  15. data/ext/datadog_profiling_native_extension/private_vm_api_access.h +15 -3
  16. data/ext/datadog_profiling_native_extension/profiling.c +2 -0
  17. data/ext/datadog_profiling_native_extension/ruby_helpers.c +1 -79
  18. data/ext/datadog_profiling_native_extension/ruby_helpers.h +0 -7
  19. data/ext/datadog_profiling_native_extension/stack_recorder.c +106 -61
  20. data/ext/datadog_profiling_native_extension/stack_recorder.h +12 -4
  21. data/ext/datadog_profiling_native_extension/time_helpers.h +2 -1
  22. data/ext/libdatadog_api/datadog_ruby_common.h +0 -10
  23. data/ext/libdatadog_api/di.c +10 -0
  24. data/ext/libdatadog_api/extconf.rb +3 -0
  25. data/ext/libdatadog_api/init.c +2 -0
  26. data/ext/libdatadog_api/otel_thread_context.c +247 -0
  27. data/ext/libdatadog_api/otel_thread_context.h +5 -0
  28. data/ext/libdatadog_api/trace_exporter.c +642 -22
  29. data/ext/libdatadog_extconf_helpers.rb +1 -1
  30. data/lib/datadog/appsec/assets/blocked.html +1 -108
  31. data/lib/datadog/core/configuration/components.rb +1 -0
  32. data/lib/datadog/core/crashtracking/component.rb +5 -1
  33. data/lib/datadog/data_streams/pathway_context.rb +20 -22
  34. data/lib/datadog/data_streams/processor.rb +31 -0
  35. data/lib/datadog/di/instrumenter.rb +41 -1
  36. data/lib/datadog/di/logger.rb +2 -2
  37. data/lib/datadog/di/probe.rb +9 -1
  38. data/lib/datadog/di/probe_notification_builder.rb +1 -0
  39. data/lib/datadog/di/remote.rb +3 -3
  40. data/lib/datadog/open_feature/evaluation_engine.rb +29 -3
  41. data/lib/datadog/open_feature/exposures/event.rb +10 -3
  42. data/lib/datadog/open_feature/ext.rb +19 -0
  43. data/lib/datadog/open_feature/flag_evaluation/aggregator.rb +236 -80
  44. data/lib/datadog/open_feature/flag_evaluation/writer.rb +179 -68
  45. data/lib/datadog/open_feature/hooks/flag_eval_evp_hook.rb +24 -21
  46. data/lib/datadog/open_feature/native_evaluator.rb +33 -6
  47. data/lib/datadog/open_feature/noop_evaluator.rb +5 -0
  48. data/lib/datadog/open_feature/provider.rb +11 -2
  49. data/lib/datadog/open_feature/transport.rb +5 -0
  50. data/lib/datadog/opentelemetry/sdk/propagator.rb +1 -1
  51. data/lib/datadog/opentelemetry/trace.rb +3 -0
  52. data/lib/datadog/profiling/collectors/cpu_and_wall_time_worker.rb +3 -0
  53. data/lib/datadog/profiling/collectors/info.rb +2 -6
  54. data/lib/datadog/profiling/collectors/thread_context.rb +0 -4
  55. data/lib/datadog/profiling/component.rb +8 -16
  56. data/lib/datadog/tracing/contrib/active_record/events/sql.rb +1 -0
  57. data/lib/datadog/tracing/contrib/sequel/jdbc_connection_string.rb +140 -0
  58. data/lib/datadog/tracing/contrib/sequel/utils.rb +49 -62
  59. data/lib/datadog/tracing/distributed/baggage.rb +0 -1
  60. data/lib/datadog/tracing/distributed/datadog.rb +3 -3
  61. data/lib/datadog/tracing/distributed/propagation.rb +3 -0
  62. data/lib/datadog/tracing/distributed/trace_context.rb +14 -271
  63. data/lib/datadog/tracing/distributed/trace_state/datadog.rb +233 -0
  64. data/lib/datadog/tracing/distributed/trace_state/ext.rb +44 -0
  65. data/lib/datadog/tracing/distributed/trace_state/open_telemetry.rb +156 -0
  66. data/lib/datadog/tracing/distributed/trace_state.rb +121 -0
  67. data/lib/datadog/tracing/otel_thread_context.rb +34 -0
  68. data/lib/datadog/tracing/remote.rb +195 -27
  69. data/lib/datadog/tracing/sampling/rule_sampler.rb +2 -0
  70. data/lib/datadog/tracing/trace_digest.rb +22 -4
  71. data/lib/datadog/tracing/trace_operation.rb +22 -10
  72. data/lib/datadog/tracing/tracer.rb +5 -5
  73. data/lib/datadog/tracing/transport/native.rb +17 -25
  74. data/lib/datadog/tracing/transport/span_events_negotiation.rb +41 -0
  75. data/lib/datadog/tracing/transport/traces.rb +2 -22
  76. data/lib/datadog/version.rb +1 -1
  77. metadata +16 -8
  78. 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 relies on `ObjectSpace._id2ref`, which was removed on Ruby 4.1
231
- # (https://bugs.ruby-lang.org/issues/22135).
232
- if RubyVersion.is?(">= 4.1")
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
- "Heap profiling is currently incompatible with Ruby 4.1+ and has been disabled."
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
- return false unless heap_profiling_enabled && heap_size_profiling_enabled
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. When the
125
- # connection string is a JDBC URL (Sequel's JDBC adapter, used on JRuby), the
126
- # host/port/database are parsed from it regardless of whether opts[:host] is set.
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 URL (in :uri, :url, or :database) can carry credentials, so always parse
134
- # it and emit only the parsed database name -- never the raw connection string.
135
- conn = opts[:uri] || opts[:url] || opts[:database]
136
- is_jdbc = conn.is_a?(String) && conn.byteslice(0, 5)&.casecmp("jdbc:") == 0
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 = parse_jdbc_uri(conn)
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 URL and ignores separate
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 !parsed[:host].nil? || !parsed[:port].nil? || !parsed[:database].nil?
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
- def database_from_path(path)
155
- return unless path&.start_with?("/")
156
-
157
- database = path[1..-1]
158
- return if database.empty? || database.include?("/")
159
-
160
- database
161
- end
162
-
163
- def database_from_properties(properties)
164
- return unless properties
165
-
166
- match = DATABASE_PROPERTY_PATTERN.match(properties)
167
- return unless match
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
- database = match[:value]
170
- database = database.split(",", 2).first if match[:key].casecmp("libraries").zero?
171
- database
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
@@ -3,7 +3,6 @@
3
3
  require_relative "../ext"
4
4
  require_relative "../metadata/ext"
5
5
  require_relative "../trace_digest"
6
- require_relative "datadog_tags_codec"
7
6
  require_relative "../utils"
8
7
  require_relative "helpers"
9
8
  require "uri"
@@ -2,7 +2,7 @@
2
2
 
3
3
  require_relative "../metadata/ext"
4
4
  require_relative "../trace_digest"
5
- require_relative "datadog_tags_codec"
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 = DatadogTagsCodec.encode(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 = DatadogTagsCodec.decode(tags)
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