datadog 2.33.0 → 2.34.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 (80) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +30 -1
  3. data/ext/datadog_profiling_native_extension/collectors_cpu_and_wall_time_worker.c +20 -0
  4. data/ext/datadog_profiling_native_extension/macos_sampler_thread.h +55 -0
  5. data/lib/datadog/appsec/component.rb +4 -1
  6. data/lib/datadog/appsec/compressed_json.rb +2 -2
  7. data/lib/datadog/appsec/contrib/aws_lambda/waf_addresses.rb +3 -3
  8. data/lib/datadog/appsec/contrib/rack/ext.rb +1 -1
  9. data/lib/datadog/core/configuration/components.rb +8 -1
  10. data/lib/datadog/core/configuration/settings.rb +6 -1
  11. data/lib/datadog/core/configuration/supported_configurations.rb +10 -0
  12. data/lib/datadog/core/environment/ext.rb +4 -0
  13. data/lib/datadog/core/environment/identity.rb +15 -1
  14. data/lib/datadog/core/environment/process.rb +48 -27
  15. data/lib/datadog/core/remote/client/capabilities.rb +11 -2
  16. data/lib/datadog/core/remote/transport/http/config.rb +5 -5
  17. data/lib/datadog/core/telemetry/request.rb +0 -2
  18. data/lib/datadog/core/transport/response.rb +1 -1
  19. data/lib/datadog/core/utils/{base64.rb → base64_codec.rb} +3 -2
  20. data/lib/datadog/core/utils/hash.rb +0 -23
  21. data/lib/datadog/core/utils/spawn_monkey_patch.rb +46 -16
  22. data/lib/datadog/data_streams/pathway_context.rb +3 -3
  23. data/lib/datadog/di/code_tracker.rb +43 -22
  24. data/lib/datadog/di/contrib/active_record.rb +6 -2
  25. data/lib/datadog/di/instrumenter.rb +24 -4
  26. data/lib/datadog/di/probe_notification_builder.rb +1 -1
  27. data/lib/datadog/di/remote.rb +4 -4
  28. data/lib/datadog/di/serializer.rb +5 -5
  29. data/lib/datadog/di/utils.rb +42 -14
  30. data/lib/datadog/opentelemetry/configuration/settings.rb +65 -0
  31. data/lib/datadog/opentelemetry/ext.rb +9 -0
  32. data/lib/datadog/opentelemetry/logs.rb +98 -0
  33. data/lib/datadog/opentelemetry/metrics.rb +10 -46
  34. data/lib/datadog/opentelemetry/sdk/configurator.rb +40 -0
  35. data/lib/datadog/opentelemetry/sdk/logs_exporter.rb +37 -0
  36. data/lib/datadog/opentelemetry/signal_configuration.rb +53 -0
  37. data/lib/datadog/opentelemetry.rb +1 -0
  38. data/lib/datadog/symbol_database/component.rb +409 -0
  39. data/lib/datadog/symbol_database/configuration.rb +2 -2
  40. data/lib/datadog/symbol_database/extractor.rb +29 -1
  41. data/lib/datadog/symbol_database/remote.rb +175 -0
  42. data/lib/datadog/symbol_database/scope_batcher.rb +8 -0
  43. data/lib/datadog/symbol_database/service_version.rb +11 -2
  44. data/lib/datadog/symbol_database/symbol.rb +6 -3
  45. data/lib/datadog/symbol_database/uploader.rb +62 -8
  46. data/lib/datadog/tracing/contrib/action_pack/action_dispatch/instrumentation.rb +8 -0
  47. data/lib/datadog/tracing/contrib/active_record/events/sql.rb +0 -4
  48. data/lib/datadog/tracing/contrib/active_support/cache/events/cache.rb +0 -4
  49. data/lib/datadog/tracing/contrib/active_support/cache/instrumentation.rb +0 -4
  50. data/lib/datadog/tracing/contrib/aws/instrumentation.rb +0 -5
  51. data/lib/datadog/tracing/contrib/dalli/instrumentation.rb +0 -5
  52. data/lib/datadog/tracing/contrib/elasticsearch/patcher.rb +0 -5
  53. data/lib/datadog/tracing/contrib/ethon/easy_patch.rb +0 -5
  54. data/lib/datadog/tracing/contrib/ethon/multi_patch.rb +0 -8
  55. data/lib/datadog/tracing/contrib/excon/middleware.rb +0 -5
  56. data/lib/datadog/tracing/contrib/ext.rb +2 -3
  57. data/lib/datadog/tracing/contrib/faraday/middleware.rb +0 -5
  58. data/lib/datadog/tracing/contrib/grpc/datadog_interceptor/client.rb +0 -5
  59. data/lib/datadog/tracing/contrib/grpc/datadog_interceptor/server.rb +0 -5
  60. data/lib/datadog/tracing/contrib/http/instrumentation.rb +0 -5
  61. data/lib/datadog/tracing/contrib/httpclient/instrumentation.rb +0 -5
  62. data/lib/datadog/tracing/contrib/httprb/instrumentation.rb +0 -5
  63. data/lib/datadog/tracing/contrib/mongodb/subscribers.rb +0 -5
  64. data/lib/datadog/tracing/contrib/mysql2/instrumentation.rb +0 -5
  65. data/lib/datadog/tracing/contrib/opensearch/patcher.rb +0 -5
  66. data/lib/datadog/tracing/contrib/pg/instrumentation.rb +0 -5
  67. data/lib/datadog/tracing/contrib/presto/instrumentation.rb +0 -5
  68. data/lib/datadog/tracing/contrib/racecar/event.rb +0 -5
  69. data/lib/datadog/tracing/contrib/redis/tags.rb +0 -5
  70. data/lib/datadog/tracing/contrib/rest_client/request_patch.rb +0 -5
  71. data/lib/datadog/tracing/contrib/sequel/utils.rb +0 -5
  72. data/lib/datadog/tracing/contrib/trilogy/instrumentation.rb +0 -5
  73. data/lib/datadog/tracing/distributed/datadog_tags_codec.rb +0 -13
  74. data/lib/datadog/tracing/distributed/trace_context.rb +0 -28
  75. data/lib/datadog/tracing/metadata/ext.rb +3 -0
  76. data/lib/datadog/tracing/span_operation.rb +13 -0
  77. data/lib/datadog/tracing/trace_operation.rb +22 -0
  78. data/lib/datadog/tracing/tracer.rb +6 -0
  79. data/lib/datadog/version.rb +1 -1
  80. metadata +12 -5
@@ -9,6 +9,10 @@ module Datadog
9
9
  module SymbolDatabase
10
10
  # Extracts symbol metadata from loaded Ruby modules and classes via introspection.
11
11
  #
12
+ # Instance created by Component with injected dependencies (logger, settings).
13
+ # All methods are instance methods accessing @logger, @settings directly —
14
+ # no parameter threading needed.
15
+ #
12
16
  # Uses Ruby's reflection APIs (Module#constants, Class#instance_methods, Method#parameters)
13
17
  # to build hierarchical Scope structures representing code organization.
14
18
  # Filters to user code only (excludes gems, stdlib, test files).
@@ -80,6 +84,11 @@ module Datadog
80
84
  MODULE_SINGLETON_CLASS_PRED = Module.instance_method(:singleton_class?)
81
85
  private_constant :MODULE_SINGLETON_CLASS_PRED
82
86
 
87
+ # Cached UnboundMethod for Module#name — avoids resolving it on every
88
+ # safe_mod_name call. Some classes override .name (e.g. Faker::Travel::Airport),
89
+ # so we bind the original Module#name to get the real module name safely.
90
+ MODULE_NAME = Module.instance_method(:name)
91
+
83
92
  # @param logger [Logger] Logger instance (SymbolDatabase::Logger facade or compatible)
84
93
  # @param settings [Configuration::Settings] Tracer settings
85
94
  def initialize(logger:, settings:)
@@ -150,7 +159,7 @@ module Datadog
150
159
  # @param mod [Module] The module
151
160
  # @return [String, nil] Module name or nil
152
161
  def safe_mod_name(mod)
153
- Module.instance_method(:name).bind(mod).call
162
+ MODULE_NAME.bind(mod).call
154
163
  rescue => e
155
164
  @logger.debug { "symdb: safe_mod_name failed: #{e.class}: #{e.message}" }
156
165
  nil
@@ -605,10 +614,26 @@ module Datadog
605
614
 
606
615
  # ── extract_all helpers ──────────────────────────────────────────────
607
616
 
617
+ # Sleep between chunks of modules processed in collect_extractable_modules so
618
+ # request-handling threads have guaranteed CPU time while extraction is in
619
+ # flight. Unlike Thread.pass (which only offers the GVL among runnable
620
+ # threads and leaves the extractor immediately re-runnable), sleep removes
621
+ # the extractor thread from the runnable set for a fixed duration, capping
622
+ # its CPU share at sleep_work_ratio regardless of GVL scheduling.
623
+ #
624
+ # The cadence is measured in modules that pass the singleton-class fast-path
625
+ # skip — singleton classes are discarded in microseconds and counting them
626
+ # would add wall-clock delay disproportionate to the work being done (e.g.
627
+ # on heavily monkey-patched processes that retain large singleton chains).
628
+ SLEEP_EVERY_N_MODULES = 100
629
+ SLEEP_SECONDS = 0.001
630
+ private_constant :SLEEP_EVERY_N_MODULES, :SLEEP_SECONDS
631
+
608
632
  # Pass 1: Collect all extractable modules with methods grouped by source file.
609
633
  # @return [Hash] { mod_name => { mod:, methods_by_file: { path => [{name:, method:, type:}] } } }
610
634
  def collect_extractable_modules
611
635
  entries = {}
636
+ seen = 0
612
637
 
613
638
  ObjectSpace.each_object(Module) do |mod|
614
639
  # Singleton classes (per-object metaclasses) are never user-code classes.
@@ -620,6 +645,9 @@ module Datadog
620
645
  # processes. Ruby 2.7+ optimized this path; the skip is a no-op there.
621
646
  next if MODULE_SINGLETON_CLASS_PRED.bind(mod).call
622
647
 
648
+ seen += 1
649
+ sleep SLEEP_SECONDS if (seen % SLEEP_EVERY_N_MODULES).zero?
650
+
623
651
  mod_name = safe_mod_name(mod)
624
652
  next unless mod_name
625
653
  next unless user_code_module?(mod)
@@ -0,0 +1,175 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Datadog
4
+ module SymbolDatabase
5
+ # Provides remote configuration integration for symbol database.
6
+ #
7
+ # Responsibilities:
8
+ # - Registers with Core::Remote as a receiver for LIVE_DEBUGGING_SYMBOL_DB product
9
+ # - Processes remote config changes (insert/update/delete)
10
+ # - Calls Component.start_upload when upload_symbols: true
11
+ # - Calls Component.stop_upload when config deleted or upload_symbols: false
12
+ #
13
+ # Flow:
14
+ # 1. Remote config system calls receiver with repository and changes
15
+ # 2. For each change, process_change called
16
+ # 3. parse_config extracts upload_symbols flag
17
+ # 4. enable_upload or disable_upload called on component
18
+ #
19
+ # Created by: Symbol database initialization
20
+ # Accessed by: Core::Remote system when configurations change
21
+ # Requires: Component must exist (accessed via Datadog.send(:components).symbol_database)
22
+ #
23
+ # @api private
24
+ module Remote
25
+ PRODUCT = 'LIVE_DEBUGGING_SYMBOL_DB'
26
+
27
+ class << self
28
+ # Declare products this receiver handles.
29
+ # @return [Array<String>] Product names
30
+ def products
31
+ [PRODUCT]
32
+ end
33
+
34
+ # Declare capabilities for this receiver.
35
+ # @return [Array] Capabilities (none for symbol database)
36
+ def capabilities
37
+ []
38
+ end
39
+
40
+ # Create receivers for remote configuration.
41
+ # @return [Array<Receiver>] Array of receivers
42
+ def receivers(_telemetry)
43
+ receiver do |repository, changes|
44
+ telemetry = lookup_telemetry
45
+ component = begin
46
+ Datadog.send(:components, allow_initialization: false)&.symbol_database
47
+ rescue => e
48
+ Datadog.logger.debug { "symdb: failed to look up component in RC receiver: #{e.class}: #{e.message}" }
49
+ telemetry&.report(e, description: 'symdb: failed to look up component in RC receiver')
50
+ nil
51
+ end
52
+
53
+ if component
54
+ changes.each do |change|
55
+ process_change(component, change, telemetry)
56
+ end
57
+ end
58
+ end
59
+ end
60
+
61
+ # Create a single receiver for the product.
62
+ # @param products [Array<String>] Product names to match
63
+ # @return [Array<Receiver>] Receiver array
64
+ def receiver(products = [PRODUCT], &block)
65
+ matcher = Core::Remote::Dispatcher::Matcher::Product.new(products)
66
+ [Core::Remote::Dispatcher::Receiver.new(matcher, &block)]
67
+ end
68
+
69
+ private
70
+
71
+ # Look up the telemetry component for error reporting. Returns nil if the
72
+ # component tree isn't built yet (very early boot) or the lookup raises.
73
+ # `allow_initialization: false` avoids triggering component-tree construction
74
+ # from inside an RC receiver callback.
75
+ # @return [Core::Telemetry::Component, nil]
76
+ # @api private
77
+ def lookup_telemetry
78
+ Datadog.send(:components, allow_initialization: false)&.telemetry
79
+ rescue
80
+ nil
81
+ end
82
+
83
+ # Process a single configuration change.
84
+ # @param component [Component] Symbol database component
85
+ # @param change [Change] Configuration change (:insert, :update, :delete)
86
+ # @param telemetry [Core::Telemetry::Component, nil] Telemetry for error reporting
87
+ # @return [void]
88
+ # @api private
89
+ def process_change(component, change, telemetry)
90
+ case change.type
91
+ when :insert
92
+ # @type var change: ::Datadog::Core::Remote::Configuration::Repository::Change::Inserted
93
+ enable_upload(component, change.content)
94
+ change.content.applied
95
+ when :update
96
+ # @type var change: ::Datadog::Core::Remote::Configuration::Repository::Change::Updated
97
+ disable_upload(component)
98
+ enable_upload(component, change.content)
99
+ change.content.applied
100
+ when :delete
101
+ # @type var change: ::Datadog::Core::Remote::Configuration::Repository::Change::Deleted
102
+ disable_upload(component)
103
+ change.previous&.applied
104
+ else
105
+ component.logger.debug { "symdb: unrecognized change type: #{change.type}" }
106
+ # Steep cannot narrow `change.content` from a respond_to? check — it sees
107
+ # the Repository::Change union type where `Deleted` lacks `content`.
108
+ change.content.errored("Unrecognized change type: #{change.type}") if change.respond_to?(:content) # steep:ignore NoMethod
109
+ end
110
+ rescue => e
111
+ component.logger.debug { "symdb: error processing remote config change: #{e.class}: #{e.message}" }
112
+ telemetry&.report(e, description: 'symdb: error processing remote config change')
113
+ # Rescue runs regardless of which branch raised — Steep cannot narrow the
114
+ # union type from a respond_to? check.
115
+ content_obj = change.respond_to?(:content) ? change.content : change.previous # steep:ignore NoMethod
116
+ content_obj&.errored(e.to_s)
117
+ end
118
+
119
+ # Enable upload if config has upload_symbols: true.
120
+ # @param component [Component] Symbol database component
121
+ # @param content [Content] Remote config content
122
+ # @return [void]
123
+ # @api private
124
+ def enable_upload(component, content)
125
+ config = parse_config(content, component.logger)
126
+
127
+ unless config
128
+ return
129
+ end
130
+
131
+ if config['upload_symbols']
132
+ component.logger.debug { "symdb: upload enabled via remote config" }
133
+ component.start_upload
134
+ else
135
+ component.logger.debug { "symdb: upload disabled in config" }
136
+ end
137
+ end
138
+
139
+ # Disable upload.
140
+ # @param component [Component] Symbol database component
141
+ # @return [void]
142
+ # @api private
143
+ def disable_upload(component)
144
+ component.logger.debug { "symdb: upload disabled via remote config" }
145
+ component.stop_upload
146
+ end
147
+
148
+ # Parse and validate remote config content.
149
+ # @param content [Content] Remote config content
150
+ # @param logger [SymbolDatabase::Logger] Logger for invalid-config diagnostics
151
+ # @return [Hash, nil] Parsed config or nil if invalid
152
+ # @api private
153
+ #
154
+ # JSON::ParserError is intentionally NOT rescued here — it propagates to
155
+ # process_change's rescue, which logs and reports to telemetry. Catching
156
+ # it locally would swallow the error from telemetry observability.
157
+ def parse_config(content, logger)
158
+ config = JSON.parse(content.data)
159
+
160
+ unless config.is_a?(Hash)
161
+ logger.debug { "symdb: invalid config format: expected Hash, got #{config.class}" }
162
+ return nil
163
+ end
164
+
165
+ unless config.key?('upload_symbols')
166
+ logger.debug { "symdb: missing 'upload_symbols' key in config" }
167
+ return nil
168
+ end
169
+
170
+ config
171
+ end
172
+ end
173
+ end
174
+ end
175
+ end
@@ -91,6 +91,14 @@ module Datadog
91
91
  return
92
92
  end
93
93
 
94
+ # Marked uploaded before perform_upload runs. This is intentional:
95
+ # symdb extraction is a one-shot operation per process — extract_all
96
+ # walks ObjectSpace once and never revisits a module within the same
97
+ # process. The Set deduplicates within a single extraction run, not
98
+ # across upload attempts. Failed uploads are not retried; symbols
99
+ # from a failed batch are lost until the next process restart, by
100
+ # design (matches Python and Go; Java retries via OkHttp, .NET via
101
+ # exponential backoff — Ruby does neither).
94
102
  @uploaded_modules.add(scope.name)
95
103
  @file_count += 1
96
104
 
@@ -16,15 +16,18 @@ module Datadog
16
16
  #
17
17
  # @api private
18
18
  class ServiceVersion
19
- attr_reader :service, :env, :version, :language, :scopes
19
+ attr_reader :service, :env, :version, :language, :scopes, :upload_id, :batch_num, :final
20
20
 
21
21
  # Initialize a new ServiceVersion
22
22
  # @param service [String] Service name (required, from DD_SERVICE)
23
23
  # @param env [String, nil] Environment (from DD_ENV, passed through unchanged)
24
24
  # @param version [String, nil] Version (from DD_VERSION, passed through unchanged)
25
25
  # @param scopes [Array<Scope>] Top-level scopes (required)
26
+ # @param upload_id [String, nil] UUID identifying the logical upload (shared by all batches)
27
+ # @param batch_num [Integer, nil] 1-indexed batch number within the upload
28
+ # @param final [Boolean, nil] true if this is the last batch of the upload
26
29
  # @raise [ArgumentError] if service empty or scopes not an array
27
- def initialize(service:, env:, version:, scopes:)
30
+ def initialize(service:, env:, version:, scopes:, upload_id: nil, batch_num: nil, final: nil)
28
31
  raise ArgumentError, 'service is required' if service.nil? || service.empty?
29
32
  raise ArgumentError, 'scopes must be an array' unless scopes.is_a?(Array)
30
33
 
@@ -33,6 +36,9 @@ module Datadog
33
36
  @version = version
34
37
  @language = 'ruby'
35
38
  @scopes = scopes
39
+ @upload_id = upload_id
40
+ @batch_num = batch_num
41
+ @final = final
36
42
  end
37
43
 
38
44
  # Convert service version to Hash for JSON serialization.
@@ -44,6 +50,9 @@ module Datadog
44
50
  version: version,
45
51
  language: language,
46
52
  scopes: scopes.map(&:to_h),
53
+ upload_id: upload_id,
54
+ batch_num: batch_num,
55
+ final: final,
47
56
  }
48
57
  end
49
58
 
@@ -42,17 +42,20 @@ module Datadog
42
42
  end
43
43
 
44
44
  # Convert symbol to Hash for JSON serialization.
45
- # Removes nil values to reduce payload size.
45
+ # The `type` key is always present per the symdb JSON schema — emit nil
46
+ # when the type cannot be determined (the common case in Ruby since
47
+ # parameters and instance variables carry no declared type).
48
+ # `language_specifics` is omitted when nil to reduce payload size.
46
49
  # @return [Hash] Symbol as hash with symbol keys
47
50
  def to_h
51
+ # @type var h: Hash[::Symbol, untyped]
48
52
  h = {
49
53
  symbol_type: symbol_type,
50
54
  name: name,
51
55
  line: line,
52
56
  type: type,
53
- language_specifics: language_specifics,
54
57
  }
55
- h.compact!
58
+ h[:language_specifics] = language_specifics if language_specifics
56
59
  h
57
60
  end
58
61
 
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'json'
4
+ require 'securerandom'
4
5
  require 'zlib'
5
6
  require 'stringio'
6
7
  require_relative '../core/environment/identity'
@@ -45,6 +46,12 @@ module Datadog
45
46
  agent_settings: agent_settings,
46
47
  logger: @logger,
47
48
  )
49
+
50
+ # Protects the lazy initialization of @upload_pid/@upload_id and the
51
+ # @batch_num increment in next_upload_metadata. ScopeBatcher calls
52
+ # upload_scopes outside its own mutex, so two threads (e.g. a size-
53
+ # triggered flush and the timer/shutdown path) can race here.
54
+ @metadata_mutex = Mutex.new
48
55
  end
49
56
 
50
57
  # Upload a batch of scopes to the agent.
@@ -56,7 +63,8 @@ module Datadog
56
63
  def upload_scopes(scopes)
57
64
  return if scopes.empty?
58
65
 
59
- json_data = build_symbol_payload(scopes)
66
+ upload_id, batch_num = next_upload_metadata
67
+ json_data = build_symbol_payload(scopes, upload_id: upload_id, batch_num: batch_num)
60
68
  compressed_data = Zlib.gzip(json_data)
61
69
 
62
70
  # Emitted unconditionally so the rare oversized case is observable.
@@ -72,7 +80,7 @@ module Datadog
72
80
  return
73
81
  end
74
82
 
75
- perform_http_upload(compressed_data, scopes.size)
83
+ perform_http_upload(compressed_data, scopes.size, upload_id: upload_id, batch_num: batch_num)
76
84
  rescue => e
77
85
  @logger.debug { "symdb: upload failed: #{e.class}: #{e.message}" }
78
86
  @telemetry&.report(e, description: 'symdb: upload failed')
@@ -82,31 +90,44 @@ module Datadog
82
90
 
83
91
  # Build JSON payload from scopes.
84
92
  # @param scopes [Array<Scope>] Scopes to serialize
93
+ # @param upload_id [String] UUID identifying the logical upload
94
+ # @param batch_num [Integer] 1-indexed batch number within the upload
85
95
  # @return [String] JSON string
86
- def build_symbol_payload(scopes)
96
+ def build_symbol_payload(scopes, upload_id:, batch_num:)
87
97
  ServiceVersion.new(
88
98
  service: @settings.service,
89
99
  env: @settings.env,
90
100
  version: @settings.version,
91
101
  scopes: scopes,
102
+ upload_id: upload_id,
103
+ batch_num: batch_num,
104
+ # Always false: the Ruby tracer continuously uploads new code
105
+ # as files are loaded; there is no defined end-of-upload point.
106
+ final: false,
92
107
  ).to_json
93
108
  end
94
109
 
95
110
  # Perform HTTP POST with multipart form-data via transport layer.
96
111
  # @param compressed_data [String] GZIP compressed JSON payload
97
112
  # @param scope_count [Integer] Number of scopes (for logging)
113
+ # @param upload_id [String] UUID identifying the logical upload
114
+ # @param batch_num [Integer] 1-indexed batch number within the upload
98
115
  # @return [void]
99
- def perform_http_upload(compressed_data, scope_count)
100
- form = build_multipart_form(compressed_data)
116
+ def perform_http_upload(compressed_data, scope_count, upload_id:, batch_num:)
117
+ form = build_multipart_form(compressed_data, upload_id: upload_id, batch_num: batch_num)
101
118
  response = @transport.send_symbols(form)
102
119
  handle_response(response, scope_count)
103
120
  end
104
121
 
105
122
  # Build multipart form-data with event metadata and compressed symbols.
106
123
  # @param compressed_data [String] GZIP compressed JSON payload
124
+ # @param upload_id [String] UUID identifying the logical upload
125
+ # @param batch_num [Integer] 1-indexed batch number within the upload
107
126
  # @return [Hash] Form data hash with UploadIO objects
108
- def build_multipart_form(compressed_data)
109
- event_io = StringIO.new(build_event_metadata)
127
+ def build_multipart_form(compressed_data, upload_id:, batch_num:)
128
+ event_io = StringIO.new(
129
+ build_event_metadata(compressed_data.bytesize, upload_id: upload_id, batch_num: batch_num),
130
+ )
110
131
  file_io = StringIO.new(compressed_data)
111
132
 
112
133
  event_upload = Datadog::Core::Vendor::Multipart::Post::UploadIO.new(
@@ -128,17 +149,50 @@ module Datadog
128
149
  end
129
150
 
130
151
  # Build event.json metadata part.
152
+ # @param attachment_size [Integer] Size of the compressed attachment in bytes
153
+ # @param upload_id [String] UUID identifying the logical upload
154
+ # @param batch_num [Integer] 1-indexed batch number within the upload
131
155
  # @return [String] JSON string for event metadata
132
- def build_event_metadata
156
+ def build_event_metadata(attachment_size, upload_id:, batch_num:)
133
157
  JSON.generate(
134
158
  ddsource: 'ruby',
135
159
  service: @settings.service,
160
+ version: @settings.version,
161
+ language: 'ruby',
136
162
  runtimeId: Datadog::Core::Environment::Identity.id,
137
163
  parentId: nil, # Fork tracking deferred for MVP
138
164
  type: 'symdb',
165
+ uploadId: upload_id,
166
+ batchNum: batch_num,
167
+ # Always false: the Ruby tracer continuously uploads new code
168
+ # as files are loaded; there is no defined end-of-upload point.
169
+ final: false,
170
+ attachmentSize: attachment_size,
139
171
  )
140
172
  end
141
173
 
174
+ # Return [upload_id, batch_num] for the current process. upload_id is
175
+ # generated lazily on first use and shared by all batches uploaded from
176
+ # the process. A forked child observes a different Process.pid and
177
+ # therefore gets a fresh upload_id and batch counter.
178
+ # Synchronized: ScopeBatcher releases its mutex before calling
179
+ # upload_scopes, so this can be entered concurrently.
180
+ # @return [Array<(String, Integer)>]
181
+ def next_upload_metadata
182
+ @metadata_mutex.synchronize do
183
+ if @upload_pid != Process.pid
184
+ @upload_pid = Process.pid
185
+ @upload_id = SecureRandom.uuid.freeze
186
+ @batch_num = 0
187
+ end
188
+ @batch_num += 1 # steep:ignore NoMethod
189
+ # @upload_id and @batch_num are non-nil after the branch above; refine for Steep.
190
+ upload_id = @upload_id or raise('unreachable: @upload_id should be set')
191
+ batch_num = @batch_num or raise('unreachable: @batch_num should be set')
192
+ [upload_id, batch_num]
193
+ end
194
+ end
195
+
142
196
  # Handle HTTP response and track metrics.
143
197
  # @param response [Core::Transport::Response] HTTP response from agent
144
198
  # @param scope_count [Integer] Number of scopes uploaded
@@ -11,6 +11,8 @@ module Datadog
11
11
  module Instrumentation
12
12
  SCRIPT_NAME_KEY = 'SCRIPT_NAME'
13
13
  FORMAT_SUFFIX = '(.:format)'
14
+ # NOTE: Rails 8.1.1+ natively provides the same object via 'action_dispatch.route'.
15
+ DATADOG_RAILS_ROUTE_ENV_KEY = 'datadog.action_dispatch.route'
14
16
 
15
17
  module_function
16
18
 
@@ -48,6 +50,8 @@ module Datadog
48
50
  result.each do |_, _, route|
49
51
  next unless Instrumentation.dispatcher_route?(route)
50
52
 
53
+ req.env[DATADOG_RAILS_ROUTE_ENV_KEY] = route
54
+
51
55
  http_route = route.path.spec.to_s
52
56
  http_route.delete_suffix!(FORMAT_SUFFIX)
53
57
 
@@ -66,6 +70,8 @@ module Datadog
66
70
  def find_routes(req)
67
71
  super do |match, parameters, route|
68
72
  if Instrumentation.dispatcher_route?(route)
73
+ req.env[DATADOG_RAILS_ROUTE_ENV_KEY] = route
74
+
69
75
  http_route = route.path.spec.to_s
70
76
  http_route.delete_suffix!(FORMAT_SUFFIX)
71
77
 
@@ -86,6 +92,8 @@ module Datadog
86
92
 
87
93
  super do |route, parameters|
88
94
  if Instrumentation.dispatcher_route?(route)
95
+ req.env[DATADOG_RAILS_ROUTE_ENV_KEY] = route
96
+
89
97
  http_route = route.path.spec.to_s
90
98
  http_route = http_route.delete_suffix(FORMAT_SUFFIX)
91
99
 
@@ -48,10 +48,6 @@ module Datadog
48
48
  span.set_tag(Tracing::Metadata::Ext::TAG_COMPONENT, Ext::TAG_COMPONENT)
49
49
  span.set_tag(Tracing::Metadata::Ext::TAG_OPERATION, Ext::TAG_OPERATION_SQL)
50
50
 
51
- if service_name != Datadog.configuration.service
52
- span.set_tag(Tracing::Contrib::Ext::Metadata::TAG_BASE_SERVICE, Datadog.configuration.service)
53
- end
54
-
55
51
  # Set analytics sample rate
56
52
  if Contrib::Analytics.enabled?(configuration[:analytics_enabled])
57
53
  Contrib::Analytics.set_sample_rate(span, configuration[:analytics_sample_rate])
@@ -82,10 +82,6 @@ module Datadog
82
82
  span.set_tag(Tracing::Metadata::Ext::TAG_COMPONENT, Ext::TAG_COMPONENT)
83
83
  span.set_tag(Tracing::Metadata::Ext::TAG_OPERATION, Ext::TAG_OPERATION_CACHE)
84
84
 
85
- if span.service != Datadog.configuration.service
86
- span.set_tag(Tracing::Contrib::Ext::Metadata::TAG_BASE_SERVICE, Datadog.configuration.service)
87
- end
88
-
89
85
  span.set_tag(Ext::TAG_CACHE_BACKEND, cache_backend(store))
90
86
 
91
87
  span.set_tag('EVENT', event)
@@ -54,10 +54,6 @@ module Datadog
54
54
  span.set_tag(Tracing::Metadata::Ext::TAG_COMPONENT, Ext::TAG_COMPONENT)
55
55
  span.set_tag(Tracing::Metadata::Ext::TAG_OPERATION, Ext::TAG_OPERATION_CACHE)
56
56
 
57
- if span.service != Datadog.configuration.service
58
- span.set_tag(Tracing::Contrib::Ext::Metadata::TAG_BASE_SERVICE, Datadog.configuration.service)
59
- end
60
-
61
57
  span.set_tag(Ext::TAG_CACHE_BACKEND, store) if store
62
58
 
63
59
  set_cache_key(span, key, multi_key) if Datadog.configuration.tracing[:active_support][:cache_key].enabled
@@ -61,11 +61,6 @@ module Datadog
61
61
  )
62
62
  end
63
63
 
64
- # Tag original global service name if not used
65
- if span.service != Datadog.configuration.service
66
- span.set_tag(Tracing::Contrib::Ext::Metadata::TAG_BASE_SERVICE, Datadog.configuration.service)
67
- end
68
-
69
64
  span.set_tag(Tracing::Metadata::Ext::TAG_KIND, Tracing::Metadata::Ext::SpanKind::TAG_CLIENT)
70
65
 
71
66
  span.set_tag(Tracing::Metadata::Ext::TAG_COMPONENT, Ext::TAG_COMPONENT)
@@ -31,11 +31,6 @@ module Datadog
31
31
  )
32
32
  end
33
33
 
34
- # Tag original global service name if not used
35
- if span.service != Datadog.configuration.service
36
- span.set_tag(Tracing::Contrib::Ext::Metadata::TAG_BASE_SERVICE, Datadog.configuration.service)
37
- end
38
-
39
34
  span.set_tag(Tracing::Metadata::Ext::TAG_KIND, Tracing::Metadata::Ext::SpanKind::TAG_CLIENT)
40
35
 
41
36
  span.set_tag(Tracing::Metadata::Ext::TAG_COMPONENT, Ext::TAG_COMPONENT)
@@ -66,11 +66,6 @@ module Datadog
66
66
  )
67
67
  end
68
68
 
69
- # Tag original global service name if not used
70
- if span.service != Datadog.configuration.service
71
- span.set_tag(Tracing::Contrib::Ext::Metadata::TAG_BASE_SERVICE, Datadog.configuration.service)
72
- end
73
-
74
69
  span.type = Datadog::Tracing::Contrib::Elasticsearch::Ext::SPAN_TYPE_QUERY
75
70
 
76
71
  span.set_tag(Tracing::Metadata::Ext::TAG_COMPONENT, Ext::TAG_COMPONENT)
@@ -144,11 +144,6 @@ module Datadog
144
144
  )
145
145
  end
146
146
 
147
- # Tag original global service name if not used
148
- if span.service != Datadog.configuration.service
149
- span.set_tag(Tracing::Contrib::Ext::Metadata::TAG_BASE_SERVICE, Datadog.configuration.service)
150
- end
151
-
152
147
  # Set analytics sample rate
153
148
  Contrib::Analytics.set_sample_rate(span, analytics_sample_rate) if analytics_enabled?
154
149
 
@@ -69,14 +69,6 @@ module Datadog
69
69
 
70
70
  @datadog_multi_span.set_tag(Tracing::Metadata::Ext::TAG_KIND, Tracing::Metadata::Ext::SpanKind::TAG_CLIENT)
71
71
 
72
- # Tag original global service name if not used
73
- if @datadog_multi_span.service != Datadog.configuration.service
74
- @datadog_multi_span.set_tag(
75
- Tracing::Contrib::Ext::Metadata::TAG_BASE_SERVICE,
76
- Datadog.configuration.service
77
- )
78
- end
79
-
80
72
  # Set analytics sample rate
81
73
  Contrib::Analytics.set_sample_rate(@datadog_multi_span, analytics_sample_rate) if analytics_enabled?
82
74
 
@@ -128,11 +128,6 @@ module Datadog
128
128
  )
129
129
  end
130
130
 
131
- # Tag original global service name if not used
132
- if span.service != Datadog.configuration.service
133
- span.set_tag(Tracing::Contrib::Ext::Metadata::TAG_BASE_SERVICE, Datadog.configuration.service)
134
- end
135
-
136
131
  span.set_tag(Tracing::Metadata::Ext::TAG_KIND, Tracing::Metadata::Ext::SpanKind::TAG_CLIENT)
137
132
 
138
133
  span.set_tag(Tracing::Metadata::Ext::TAG_COMPONENT, Ext::TAG_COMPONENT)
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative '../metadata/ext'
4
+
3
5
  module Datadog
4
6
  module Tracing
5
7
  module Contrib
@@ -60,9 +62,6 @@ module Datadog
60
62
 
61
63
  # Value of tag from which peer.service value was remapped from
62
64
  TAG_PEER_SERVICE_REMAP = '_dd.peer.service.remapped_from'
63
-
64
- # Set equal to the global service when contrib span.service is overriden
65
- TAG_BASE_SERVICE = '_dd.base_service'
66
65
  end
67
66
  end
68
67
  end
@@ -56,11 +56,6 @@ module Datadog
56
56
  )
57
57
  end
58
58
 
59
- # Tag original global service name if not used
60
- if span.service != Datadog.configuration.service
61
- span.set_tag(Tracing::Contrib::Ext::Metadata::TAG_BASE_SERVICE, Datadog.configuration.service)
62
- end
63
-
64
59
  span.set_tag(Tracing::Metadata::Ext::TAG_KIND, Tracing::Metadata::Ext::SpanKind::TAG_CLIENT)
65
60
 
66
61
  span.set_tag(Tracing::Metadata::Ext::TAG_COMPONENT, Ext::TAG_COMPONENT)
@@ -59,11 +59,6 @@ module Datadog
59
59
  )
60
60
  end
61
61
 
62
- # Tag original global service name if not used
63
- if span.service != Datadog.configuration.service
64
- span.set_tag(Tracing::Contrib::Ext::Metadata::TAG_BASE_SERVICE, Datadog.configuration.service)
65
- end
66
-
67
62
  span.set_tag(Tracing::Metadata::Ext::TAG_KIND, Tracing::Metadata::Ext::SpanKind::TAG_CLIENT)
68
63
  span.set_tag(Tracing::Metadata::Ext::TAG_COMPONENT, Ext::TAG_COMPONENT)
69
64
  span.set_tag(Tracing::Metadata::Ext::TAG_OPERATION, Ext::TAG_OPERATION_CLIENT)