datadog 2.31.0 → 2.32.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 (173) hide show
  1. checksums.yaml +4 -4
  2. data/ext/datadog_profiling_native_extension/collectors_cpu_and_wall_time_worker.c +17 -7
  3. data/ext/datadog_profiling_native_extension/collectors_thread_context.c +11 -4
  4. data/ext/datadog_profiling_native_extension/collectors_thread_context.h +6 -0
  5. data/ext/datadog_profiling_native_extension/extconf.rb +5 -4
  6. data/ext/datadog_profiling_native_extension/http_transport.c +10 -5
  7. data/ext/libdatadog_api/di.c +48 -0
  8. data/ext/libdatadog_api/extconf.rb +7 -4
  9. data/ext/libdatadog_extconf_helpers.rb +37 -0
  10. data/lib/datadog/ai_guard/configuration.rb +105 -2
  11. data/lib/datadog/ai_guard/evaluation.rb +1 -0
  12. data/lib/datadog/ai_guard/ext.rb +1 -0
  13. data/lib/datadog/appsec/autoload.rb +1 -1
  14. data/lib/datadog/appsec/component.rb +1 -1
  15. data/lib/datadog/appsec/configuration.rb +414 -1
  16. data/lib/datadog/appsec/contrib/devise/patches/signin_tracking_patch.rb +2 -1
  17. data/lib/datadog/appsec/contrib/rack/gateway/request.rb +1 -1
  18. data/lib/datadog/appsec/contrib/rails/patcher.rb +2 -2
  19. data/lib/datadog/appsec/metrics/telemetry.rb +13 -1
  20. data/lib/datadog/appsec/security_engine/runner.rb +1 -1
  21. data/lib/datadog/appsec/trace_keeper.rb +18 -6
  22. data/lib/datadog/appsec/utils/http/url_encoded.rb +2 -2
  23. data/lib/datadog/core/configuration/components.rb +1 -1
  24. data/lib/datadog/core/configuration/settings.rb +3 -0
  25. data/lib/datadog/core/configuration/supported_configurations.rb +2 -0
  26. data/lib/datadog/core/configuration.rb +1 -1
  27. data/lib/datadog/core/contrib/rails/utils.rb +1 -1
  28. data/lib/datadog/core/crashtracking/component.rb +3 -3
  29. data/lib/datadog/core/diagnostics/environment_logger.rb +3 -1
  30. data/lib/datadog/core/environment/container.rb +2 -2
  31. data/lib/datadog/core/feature_flags.rb +1 -1
  32. data/lib/datadog/core/metrics/client.rb +5 -5
  33. data/lib/datadog/core/remote/client.rb +1 -1
  34. data/lib/datadog/core/remote/component.rb +2 -2
  35. data/lib/datadog/core/runtime/metrics.rb +1 -1
  36. data/lib/datadog/core/telemetry/emitter.rb +1 -1
  37. data/lib/datadog/core/telemetry/event/app_started.rb +2 -2
  38. data/lib/datadog/core/transport/http.rb +2 -0
  39. data/lib/datadog/core/utils.rb +1 -1
  40. data/lib/datadog/core/workers/async.rb +1 -1
  41. data/lib/datadog/core.rb +1 -1
  42. data/lib/datadog/data_streams/configuration.rb +40 -1
  43. data/lib/datadog/data_streams/pathway_context.rb +1 -1
  44. data/lib/datadog/data_streams/processor.rb +1 -1
  45. data/lib/datadog/data_streams.rb +1 -1
  46. data/lib/datadog/di/base.rb +8 -5
  47. data/lib/datadog/di/code_tracker.rb +179 -1
  48. data/lib/datadog/di/component.rb +1 -1
  49. data/lib/datadog/di/configuration.rb +235 -2
  50. data/lib/datadog/di/instrumenter.rb +46 -26
  51. data/lib/datadog/di/probe_builder.rb +1 -1
  52. data/lib/datadog/di/probe_file_loader.rb +2 -2
  53. data/lib/datadog/di/probe_manager.rb +6 -6
  54. data/lib/datadog/di/probe_notification_builder.rb +1 -1
  55. data/lib/datadog/di/probe_notifier_worker.rb +2 -2
  56. data/lib/datadog/di/remote.rb +6 -6
  57. data/lib/datadog/di/serializer.rb +1 -1
  58. data/lib/datadog/di/transport/input.rb +3 -3
  59. data/lib/datadog/error_tracking/configuration.rb +55 -2
  60. data/lib/datadog/kit/enable_core_dumps.rb +1 -1
  61. data/lib/datadog/open_feature/component.rb +18 -1
  62. data/lib/datadog/open_feature/evaluation_engine.rb +3 -3
  63. data/lib/datadog/open_feature/exposures/reporter.rb +1 -1
  64. data/lib/datadog/open_feature/exposures/worker.rb +1 -1
  65. data/lib/datadog/open_feature/hooks/flag_eval_hook.rb +49 -0
  66. data/lib/datadog/open_feature/metrics/flag_eval_metrics.rb +149 -0
  67. data/lib/datadog/open_feature/provider.rb +19 -1
  68. data/lib/datadog/open_feature/remote.rb +1 -1
  69. data/lib/datadog/open_feature/transport.rb +1 -1
  70. data/lib/datadog/opentelemetry/metrics.rb +3 -3
  71. data/lib/datadog/opentelemetry/sdk/configurator.rb +1 -1
  72. data/lib/datadog/opentelemetry/sdk/metrics_exporter.rb +1 -1
  73. data/lib/datadog/profiling/collectors/code_provenance.rb +35 -9
  74. data/lib/datadog/profiling/collectors/cpu_and_wall_time_worker.rb +31 -2
  75. data/lib/datadog/profiling/collectors/idle_sampling_helper.rb +8 -2
  76. data/lib/datadog/profiling/collectors/info.rb +16 -3
  77. data/lib/datadog/profiling/component.rb +3 -5
  78. data/lib/datadog/profiling/exporter.rb +37 -12
  79. data/lib/datadog/profiling/ext.rb +0 -2
  80. data/lib/datadog/profiling/flush.rb +21 -12
  81. data/lib/datadog/profiling/http_transport.rb +12 -1
  82. data/lib/datadog/profiling/load_native_extension.rb +1 -1
  83. data/lib/datadog/profiling/profiler.rb +13 -1
  84. data/lib/datadog/profiling/scheduler.rb +2 -2
  85. data/lib/datadog/profiling/tasks/exec.rb +8 -3
  86. data/lib/datadog/profiling/tasks/help.rb +1 -0
  87. data/lib/datadog/profiling/tasks/setup.rb +2 -2
  88. data/lib/datadog/single_step_instrument.rb +1 -1
  89. data/lib/datadog/symbol_database/configuration.rb +65 -0
  90. data/lib/datadog/symbol_database/extractor.rb +915 -0
  91. data/lib/datadog/symbol_database/file_hash.rb +46 -0
  92. data/lib/datadog/symbol_database/logger.rb +43 -0
  93. data/lib/datadog/symbol_database/scope.rb +98 -0
  94. data/lib/datadog/symbol_database/service_version.rb +57 -0
  95. data/lib/datadog/symbol_database/symbol.rb +66 -0
  96. data/lib/datadog/symbol_database/transport/http/endpoint.rb +28 -0
  97. data/lib/datadog/symbol_database/transport/http.rb +45 -0
  98. data/lib/datadog/symbol_database/transport.rb +54 -0
  99. data/lib/datadog/symbol_database/uploader.rb +166 -0
  100. data/lib/datadog/symbol_database.rb +49 -0
  101. data/lib/datadog/tracing/buffer.rb +3 -3
  102. data/lib/datadog/tracing/configuration/settings.rb +1 -1
  103. data/lib/datadog/tracing/contrib/action_pack/action_controller/instrumentation.rb +5 -3
  104. data/lib/datadog/tracing/contrib/action_view/events/render_template.rb +1 -1
  105. data/lib/datadog/tracing/contrib/active_job/events/discard.rb +1 -1
  106. data/lib/datadog/tracing/contrib/active_job/events/enqueue.rb +1 -1
  107. data/lib/datadog/tracing/contrib/active_job/events/enqueue_at.rb +1 -1
  108. data/lib/datadog/tracing/contrib/active_job/events/enqueue_retry.rb +1 -1
  109. data/lib/datadog/tracing/contrib/active_job/events/perform.rb +1 -1
  110. data/lib/datadog/tracing/contrib/active_job/events/retry_stopped.rb +1 -1
  111. data/lib/datadog/tracing/contrib/active_model_serializers/events/render.rb +1 -1
  112. data/lib/datadog/tracing/contrib/active_model_serializers/events/serialize.rb +1 -1
  113. data/lib/datadog/tracing/contrib/active_record/configuration/resolver.rb +2 -2
  114. data/lib/datadog/tracing/contrib/active_record/events/instantiation.rb +1 -1
  115. data/lib/datadog/tracing/contrib/active_record/events/sql.rb +1 -1
  116. data/lib/datadog/tracing/contrib/active_record/utils.rb +1 -1
  117. data/lib/datadog/tracing/contrib/active_support/cache/events/cache.rb +1 -1
  118. data/lib/datadog/tracing/contrib/active_support/notifications/subscription.rb +2 -2
  119. data/lib/datadog/tracing/contrib/aws/instrumentation.rb +1 -1
  120. data/lib/datadog/tracing/contrib/component.rb +1 -1
  121. data/lib/datadog/tracing/contrib/configuration/resolver.rb +7 -4
  122. data/lib/datadog/tracing/contrib/dalli/quantize.rb +1 -1
  123. data/lib/datadog/tracing/contrib/elasticsearch/patcher.rb +1 -1
  124. data/lib/datadog/tracing/contrib/excon/middleware.rb +2 -2
  125. data/lib/datadog/tracing/contrib/extensions.rb +9 -0
  126. data/lib/datadog/tracing/contrib/faraday/middleware.rb +2 -2
  127. data/lib/datadog/tracing/contrib/grape/endpoint.rb +5 -5
  128. data/lib/datadog/tracing/contrib/grpc/datadog_interceptor/client.rb +2 -2
  129. data/lib/datadog/tracing/contrib/grpc/datadog_interceptor/server.rb +2 -2
  130. data/lib/datadog/tracing/contrib/http/instrumentation.rb +2 -2
  131. data/lib/datadog/tracing/contrib/httpclient/instrumentation.rb +6 -2
  132. data/lib/datadog/tracing/contrib/httprb/instrumentation.rb +2 -2
  133. data/lib/datadog/tracing/contrib/kafka/instrumentation/consumer.rb +2 -2
  134. data/lib/datadog/tracing/contrib/kafka/instrumentation/producer.rb +2 -2
  135. data/lib/datadog/tracing/contrib/karafka/patcher.rb +1 -1
  136. data/lib/datadog/tracing/contrib/mongodb/subscribers.rb +3 -3
  137. data/lib/datadog/tracing/contrib/opensearch/patcher.rb +1 -1
  138. data/lib/datadog/tracing/contrib/presto/instrumentation.rb +3 -3
  139. data/lib/datadog/tracing/contrib/rack/patcher.rb +1 -1
  140. data/lib/datadog/tracing/contrib/rack/request_queue.rb +1 -1
  141. data/lib/datadog/tracing/contrib/rails/log_injection.rb +1 -1
  142. data/lib/datadog/tracing/contrib/rails/runner.rb +1 -1
  143. data/lib/datadog/tracing/contrib/rake/instrumentation.rb +2 -2
  144. data/lib/datadog/tracing/contrib/redis/quantize.rb +1 -1
  145. data/lib/datadog/tracing/contrib/redis/tags.rb +1 -1
  146. data/lib/datadog/tracing/contrib/sidekiq/utils.rb +1 -1
  147. data/lib/datadog/tracing/contrib/stripe/request.rb +1 -1
  148. data/lib/datadog/tracing/contrib.rb +8 -0
  149. data/lib/datadog/tracing/diagnostics/environment_logger.rb +3 -1
  150. data/lib/datadog/tracing/distributed/baggage.rb +59 -5
  151. data/lib/datadog/tracing/distributed/datadog.rb +11 -11
  152. data/lib/datadog/tracing/distributed/datadog_tags_codec.rb +1 -1
  153. data/lib/datadog/tracing/distributed/propagation.rb +2 -2
  154. data/lib/datadog/tracing/distributed/trace_context.rb +74 -32
  155. data/lib/datadog/tracing/event.rb +1 -1
  156. data/lib/datadog/tracing/metadata/tagging.rb +2 -2
  157. data/lib/datadog/tracing/pipeline.rb +1 -1
  158. data/lib/datadog/tracing/remote.rb +1 -1
  159. data/lib/datadog/tracing/sampling/rule.rb +1 -1
  160. data/lib/datadog/tracing/sampling/rule_sampler.rb +2 -2
  161. data/lib/datadog/tracing/sampling/span/rule_parser.rb +2 -2
  162. data/lib/datadog/tracing/span_operation.rb +3 -3
  163. data/lib/datadog/tracing/trace_operation.rb +4 -4
  164. data/lib/datadog/tracing/tracer.rb +5 -5
  165. data/lib/datadog/tracing/transport/io/client.rb +1 -1
  166. data/lib/datadog/tracing/workers.rb +2 -1
  167. data/lib/datadog/version.rb +1 -1
  168. metadata +18 -9
  169. data/lib/datadog/ai_guard/configuration/settings.rb +0 -113
  170. data/lib/datadog/appsec/configuration/settings.rb +0 -423
  171. data/lib/datadog/data_streams/configuration/settings.rb +0 -49
  172. data/lib/datadog/di/configuration/settings.rb +0 -243
  173. data/lib/datadog/error_tracking/configuration/settings.rb +0 -63
@@ -0,0 +1,46 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'digest/sha1'
4
+
5
+ module Datadog
6
+ module SymbolDatabase
7
+ # Computes Git-style SHA-1 hashes of Ruby source files for backend commit inference.
8
+ #
9
+ # Uses Git's blob hash algorithm: SHA1("blob <size>\0<content>")
10
+ # Hashes enable the backend to correlate runtime code with Git repository history,
11
+ # identifying which commit is actually deployed.
12
+ #
13
+ # Called by: Extractor (when building MODULE scopes)
14
+ # Stores result in: Scope's language_specifics[:file_hash]
15
+ # Returns: 40-character hex string or nil if file unreadable
16
+ #
17
+ # @api private
18
+ module FileHash
19
+ module_function
20
+
21
+ # Compute Git-style SHA-1 hash of a file.
22
+ # Uses Git's blob hash algorithm: SHA1("blob <size>\0<content>")
23
+ # Returns nil on any error (file not found, permission denied, etc.)
24
+ #
25
+ # @param file_path [String] Path to the file
26
+ # @param logger [#debug] Logger for error reporting
27
+ # @return [String, nil] 40-character hex-encoded SHA-1 hash, or nil if error
28
+ def compute(file_path, logger:)
29
+ return nil unless file_path
30
+ return nil unless File.exist?(file_path)
31
+
32
+ content = File.read(file_path, mode: 'rb')
33
+ size = content.bytesize
34
+ git_blob = "blob #{size}\0#{content}"
35
+
36
+ # SHA-1 is required here to match Git's blob hash format for commit inference.
37
+ # This is not a security vulnerability - we're computing file content hashes
38
+ # to match against Git objects, not using SHA-1 for authentication/integrity.
39
+ Digest::SHA1.hexdigest(git_blob) # nosemgrep: ruby.lang.security.weak-hashes-sha1.weak-hashes-sha1
40
+ rescue => e
41
+ logger.debug { "symdb: file hash failed for #{file_path}: #{e.class}: #{e.message}" }
42
+ nil
43
+ end
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,43 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'forwardable'
4
+
5
+ module Datadog
6
+ module SymbolDatabase
7
+ # Logger facade that adds a config-gated +trace+ method.
8
+ #
9
+ # Wraps any logger (customer-provided or default) and delegates
10
+ # standard methods. The +trace+ method is a sub-debug level that
11
+ # is a no-op unless DD_TRACE_DEBUG is set, avoiding overhead for
12
+ # high-frequency log sites (per-module filtering, dedup checks).
13
+ #
14
+ # @api private
15
+ class Logger
16
+ extend Forwardable
17
+
18
+ # @param settings [Configuration::Settings] Tracer settings (reads trace_logging flag)
19
+ # @param target [::Logger] Underlying logger to delegate to
20
+ def initialize(settings, target)
21
+ @settings = settings
22
+ @target = target
23
+ end
24
+
25
+ attr_reader :settings
26
+
27
+ # Only debug and warn are delegated by design — symbol database
28
+ # extraction logs only at debug (high-volume diagnostics) and warn
29
+ # (user-actionable problems). Adding info/error would invite
30
+ # log-level drift; explicit additions can be made if needed.
31
+ def_delegators :@target, :debug, :warn
32
+
33
+ # Log at trace level (sub-debug). No-op unless DD_TRACE_DEBUG is set.
34
+ # @yield Block that returns the log message string
35
+ # @return [void]
36
+ def trace(&block)
37
+ if settings.symbol_database.internal.trace_logging
38
+ debug(&block)
39
+ end
40
+ end
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,98 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'json'
4
+
5
+ module Datadog
6
+ module SymbolDatabase
7
+ # Represents a scope in the hierarchical symbol structure (FILE → MODULE/CLASS → METHOD).
8
+ #
9
+ # Scopes form a tree structure representing Ruby code organization. Each scope contains:
10
+ # - Metadata: name, source file, line range, scope type (MODULE/CLASS/METHOD/etc.)
11
+ # - Symbols: Variables, constants, parameters defined in this scope
12
+ # - Nested scopes: Child scopes (e.g., methods within a class)
13
+ #
14
+ # Created by: Extractor (during symbol extraction)
15
+ # Used by: ScopeBatcher (batching), ServiceVersion (wrapping for upload)
16
+ # Serialized to: JSON via to_h/to_json for upload to agent
17
+ #
18
+ # @api private
19
+ class Scope
20
+ attr_reader :scope_type, :name, :source_file, :start_line, :end_line,
21
+ # Ranges of executable lines [{start:, end:}]. Three states:
22
+ # - nil: not computed (source unreadable, native/C-extension method)
23
+ # - []: computed but no executable lines found (comments/whitespace only)
24
+ # - non-empty: computed, contains executable line ranges
25
+ # nil and [] both serialize as injectible_lines?: false on METHOD
26
+ # scopes. Key is absent on non-METHOD scopes.
27
+ :injectible_lines,
28
+ :language_specifics, :symbols, :scopes
29
+
30
+ # Initialize a new Scope
31
+ # @param scope_type [String] Type of scope (FILE, MODULE, CLASS, METHOD)
32
+ # @param name [String, nil] Name of the scope (class name, method name, etc.)
33
+ # @param source_file [String, nil] Path to source file
34
+ # @param start_line [Integer, nil] Starting line number (UNKNOWN_MIN_LINE for unknown)
35
+ # @param end_line [Integer, nil] Ending line number (UNKNOWN_MAX_LINE for entire file)
36
+ # @param injectible_lines [Array<Hash>, nil] Ranges of executable lines [{start:, end:}]
37
+ # @param language_specifics [Hash, nil] Ruby-specific metadata
38
+ # @param symbols [Array<Symbol>, nil] Symbols defined in this scope
39
+ # @param scopes [Array<Scope>, nil] Nested child scopes
40
+ def initialize(
41
+ scope_type:,
42
+ name: nil,
43
+ source_file: nil,
44
+ start_line: nil,
45
+ end_line: nil,
46
+ injectible_lines: nil,
47
+ language_specifics: nil,
48
+ symbols: nil,
49
+ scopes: nil
50
+ )
51
+ @scope_type = scope_type
52
+ @name = name
53
+ @source_file = source_file
54
+ @start_line = start_line
55
+ @end_line = end_line
56
+ @injectible_lines = injectible_lines
57
+ @language_specifics = language_specifics || {}
58
+ @symbols = symbols || []
59
+ @scopes = scopes || []
60
+ end
61
+
62
+ # @return [Boolean] true when injectible_lines is non-nil and non-empty
63
+ def injectible_lines?
64
+ !injectible_lines.nil? && !injectible_lines.empty?
65
+ end
66
+
67
+ # Convert scope to Hash for JSON serialization.
68
+ # Removes nil values to reduce payload size.
69
+ # @return [Hash] Scope as hash with symbol keys
70
+ def to_h
71
+ h = {
72
+ scope_type: scope_type,
73
+ name: name,
74
+ source_file: source_file,
75
+ start_line: start_line,
76
+ end_line: end_line,
77
+ language_specifics: language_specifics.empty? ? nil : language_specifics,
78
+ symbols: symbols.empty? ? nil : symbols.map(&:to_h),
79
+ scopes: scopes.empty? ? nil : scopes.map(&:to_h),
80
+ }
81
+ h.compact!
82
+ # Injectable lines only on METHOD scopes (per spec — not on CLASS/MODULE/FILE).
83
+ # Always emit has_injectible_lines (even when false) on METHOD scopes.
84
+ if scope_type == 'METHOD'
85
+ h[:has_injectible_lines] = injectible_lines? # steep:ignore ArgumentTypeMismatch
86
+ h[:injectible_lines] = injectible_lines if injectible_lines && !injectible_lines.empty?
87
+ end
88
+ h
89
+ end
90
+
91
+ # Serialize scope to JSON.
92
+ # @return [String] JSON string representation
93
+ def to_json(_state = nil)
94
+ JSON.generate(to_h)
95
+ end
96
+ end
97
+ end
98
+ end
@@ -0,0 +1,57 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'json'
4
+
5
+ module Datadog
6
+ module SymbolDatabase
7
+ # Top-level container wrapping scopes for upload to the agent.
8
+ #
9
+ # ServiceVersion is the root object serialized to JSON for symbol database uploads.
10
+ # Contains service metadata (name, env, version) and all extracted scopes.
11
+ # The language field identifies the tracer.
12
+ #
13
+ # Created by: Uploader (wraps scopes array before serialization)
14
+ # Contains: Array of top-level Scope objects (FILE scopes)
15
+ # Serialized to: JSON via to_json, then GZIP compressed for upload
16
+ #
17
+ # @api private
18
+ class ServiceVersion
19
+ attr_reader :service, :env, :version, :language, :scopes
20
+
21
+ # Initialize a new ServiceVersion
22
+ # @param service [String] Service name (required, from DD_SERVICE)
23
+ # @param env [String] Environment (from DD_ENV, defaults to "none")
24
+ # @param version [String] Version (from DD_VERSION, defaults to "none")
25
+ # @param scopes [Array<Scope>] Top-level scopes (required)
26
+ # @raise [ArgumentError] if service empty or scopes not an array
27
+ def initialize(service:, env:, version:, scopes:)
28
+ raise ArgumentError, 'service is required' if service.nil? || service.empty?
29
+ raise ArgumentError, 'scopes must be an array' unless scopes.is_a?(Array)
30
+
31
+ @service = service
32
+ @env = env.to_s.empty? ? 'none' : env.to_s
33
+ @version = version.to_s.empty? ? 'none' : version.to_s
34
+ @language = 'ruby'
35
+ @scopes = scopes
36
+ end
37
+
38
+ # Convert service version to Hash for JSON serialization.
39
+ # @return [Hash] ServiceVersion as hash with symbol keys
40
+ def to_h
41
+ {
42
+ service: service,
43
+ env: env,
44
+ version: version,
45
+ language: language,
46
+ scopes: scopes.map(&:to_h),
47
+ }
48
+ end
49
+
50
+ # Serialize service version to JSON.
51
+ # @return [String] JSON string representation
52
+ def to_json(_state = nil)
53
+ JSON.generate(to_h)
54
+ end
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,66 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'json'
4
+
5
+ module Datadog
6
+ module SymbolDatabase
7
+ # Represents a symbol (variable, parameter, field, constant) within a scope.
8
+ #
9
+ # Symbols are the actual identifiers extracted from Ruby code:
10
+ # - Instance variables (@var) - FIELD type
11
+ # - Class variables (@@var) - STATIC_FIELD type
12
+ # - Constants (CONST) - STATIC_FIELD type
13
+ # - Method parameters (arg) - ARG type
14
+ # - Local variables (var) - LOCAL type (not yet implemented)
15
+ #
16
+ # Created by: Extractor (during class/method introspection)
17
+ # Contained in: Scope objects (symbols array)
18
+ # Serialized to: JSON via to_h/to_json
19
+ #
20
+ # @api private
21
+ class Symbol
22
+ attr_reader :symbol_type, :name, :line, :type, :language_specifics
23
+
24
+ # Initialize a new Symbol
25
+ # @param symbol_type [String] Type: FIELD, STATIC_FIELD, ARG, LOCAL
26
+ # @param name [String] Symbol name (variable name, parameter name)
27
+ # @param line [Integer] Line number (UNKNOWN_MIN_LINE for entire scope, UNKNOWN_MAX_LINE for method-level only)
28
+ # @param type [String, nil] Type annotation (optional, Ruby is dynamic)
29
+ # @param language_specifics [Hash, nil] Symbol-specific metadata
30
+ def initialize(
31
+ symbol_type:,
32
+ name:,
33
+ line:,
34
+ type: nil,
35
+ language_specifics: nil
36
+ )
37
+ @symbol_type = symbol_type
38
+ @name = name
39
+ @line = line
40
+ @type = type
41
+ @language_specifics = language_specifics
42
+ end
43
+
44
+ # Convert symbol to Hash for JSON serialization.
45
+ # Removes nil values to reduce payload size.
46
+ # @return [Hash] Symbol as hash with symbol keys
47
+ def to_h
48
+ h = {
49
+ symbol_type: symbol_type,
50
+ name: name,
51
+ line: line,
52
+ type: type,
53
+ language_specifics: language_specifics,
54
+ }
55
+ h.compact!
56
+ h
57
+ end
58
+
59
+ # Serialize symbol to JSON.
60
+ # @return [String] JSON string representation
61
+ def to_json(_state = nil)
62
+ JSON.generate(to_h)
63
+ end
64
+ end
65
+ end
66
+ end
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../../../core/transport/http/api/endpoint'
4
+
5
+ module Datadog
6
+ module SymbolDatabase
7
+ module Transport
8
+ module HTTP
9
+ module API
10
+ # POST endpoint for symbol database uploads. Multipart form-data
11
+ # is triggered by setting `env.form` on the request (handled in
12
+ # `Core::Transport::HTTP::Adapters::Net`); the multipart library
13
+ # sets Content-Type itself.
14
+ class Endpoint < Datadog::Core::Transport::HTTP::API::Endpoint
15
+ attr_reader :encoder
16
+
17
+ # @param path [String] URL path for the endpoint
18
+ # @param encoder [#encode] Encoder for request data
19
+ def initialize(path, encoder)
20
+ super(:post, path)
21
+ @encoder = encoder
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,45 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../../core/encoding'
4
+ require_relative '../../core/transport/http'
5
+ require_relative 'http/endpoint'
6
+ require_relative '../transport'
7
+
8
+ module Datadog
9
+ module SymbolDatabase
10
+ module Transport
11
+ # Namespace for HTTP transport components
12
+ module HTTP
13
+ # POST endpoint for the agent's symbol database intake.
14
+ # Multipart form-data is dispatched via `env.form` from the
15
+ # `Symbols::Client` subclass.
16
+ SYMBOLS_ENDPOINT = API::Endpoint.new(
17
+ '/symdb/v1/input',
18
+ Datadog::Core::Encoding::JSONEncoder,
19
+ )
20
+
21
+ # Builds a transport for the symbols upload endpoint.
22
+ # @param agent_settings [Core::Configuration::AgentSettingsResolver::AgentSettings]
23
+ # Agent connection settings (host, port, timeout, etc.)
24
+ # @param logger [Logger] Logger instance
25
+ # @param headers [Hash, nil] Optional additional headers
26
+ # @return [Symbols::Transport] Transport for the symbols endpoint
27
+ def self.symbols(
28
+ agent_settings:,
29
+ logger:,
30
+ headers: nil
31
+ )
32
+ Core::Transport::HTTP.build(
33
+ logger: logger,
34
+ agent_settings: agent_settings,
35
+ headers: headers,
36
+ ) do |transport|
37
+ transport.api 'symbols', SYMBOLS_ENDPOINT, default: true
38
+
39
+ yield(transport) if block_given?
40
+ end.to_transport(SymbolDatabase::Transport::Symbols::Transport)
41
+ end
42
+ end
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,54 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../core/transport/request'
4
+ require_relative '../core/transport/transport'
5
+ require_relative '../core/transport/http/client'
6
+ require_relative '../core/transport/http/env'
7
+
8
+ module Datadog
9
+ module SymbolDatabase
10
+ module Transport
11
+ # Transport classes for the symbols upload endpoint.
12
+ # Mirrors the per-purpose split used by DI (`DI::Transport::Input::*`).
13
+ module Symbols
14
+ # Request wrapper carrying the multipart form for a symbols upload.
15
+ class Request < Core::Transport::Request
16
+ attr_reader :form
17
+
18
+ # @param form [Hash] Multipart form data with UploadIO objects
19
+ def initialize(form)
20
+ @form = form
21
+ # Multipart upload — no parcel; the Net adapter reads form data
22
+ # off the env directly.
23
+ super(nil)
24
+ end
25
+ end
26
+
27
+ # HTTP client for symbol database uploads.
28
+ # Extends Core::Transport::HTTP::Client to set `env.form`, which the
29
+ # Net adapter (lib/datadog/core/transport/http/adapters/net.rb) detects
30
+ # and dispatches as multipart/form-data.
31
+ class Client < Core::Transport::HTTP::Client
32
+ # @param request [Request] Symbols upload request
33
+ # @return [Core::Transport::HTTP::Env] Env with form data set
34
+ def build_env(request)
35
+ Core::Transport::HTTP::Env.new(request, form: request.form)
36
+ end
37
+ end
38
+
39
+ # Transport for the symbols upload endpoint.
40
+ class Transport < Core::Transport::Transport
41
+ self.http_client_class = Client
42
+
43
+ # Send a symbols upload to the agent.
44
+ # @param form [Hash] Multipart form data with UploadIO objects
45
+ # @return [Core::Transport::Response] Response from agent
46
+ def send_symbols(form)
47
+ request = Request.new(form)
48
+ client.send_request(:symbols, request)
49
+ end
50
+ end
51
+ end
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,166 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'json'
4
+ require 'zlib'
5
+ require 'stringio'
6
+ require_relative '../core/environment/identity'
7
+ require_relative '../core/vendor/multipart-post/multipart/post/composite_read_io'
8
+ require_relative 'service_version'
9
+ require_relative 'transport/http'
10
+
11
+ module Datadog
12
+ module SymbolDatabase
13
+ # Uploads symbol database payloads to the Datadog agent via HTTP multipart.
14
+ #
15
+ # Handles the complete upload process:
16
+ # 1. Wraps scopes in ServiceVersion (adds service/env/version metadata)
17
+ # 2. Serializes to JSON
18
+ # 3. Compresses with GZIP (always, ~40:1 ratio expected)
19
+ # 4. Builds multipart form: event.json (metadata) + symbols_{pid}.json.gz (data)
20
+ # 5. POSTs to agent at /symdb/v1/input via Core::Transport::HTTP
21
+ # No retries — single attempt. Any failure is logged at debug and discarded.
22
+ #
23
+ # Uses Core::Transport::HTTP infrastructure (consistent with DI, Profiling, DataStreams).
24
+ # Headers: DD-API-KEY, Datadog-Container-ID, Datadog-Entity-ID (automatic from transport)
25
+ #
26
+ # Called by: ScopeBatcher.perform_upload (when batch ready)
27
+ # Calls: Transport::HTTP for network, Zlib for compression
28
+ #
29
+ # @api private
30
+ class Uploader
31
+ MAX_PAYLOAD_SIZE = 50 * 1024 * 1024 # 50MB
32
+
33
+ # Initialize uploader.
34
+ # @param settings [Configuration::Settings] Tracer settings (for service, env, version metadata)
35
+ # @param agent_settings [Configuration::AgentSettings] Agent connection settings
36
+ # @param logger [Logger] Logger instance
37
+ # @param telemetry [Telemetry, nil] Optional telemetry component for error reporting
38
+ def initialize(settings:, agent_settings:, logger:, telemetry: nil)
39
+ @settings = settings
40
+ @agent_settings = agent_settings
41
+ @logger = logger
42
+ @telemetry = telemetry
43
+
44
+ @transport = Transport::HTTP.symbols(
45
+ agent_settings: agent_settings,
46
+ logger: @logger,
47
+ )
48
+ end
49
+
50
+ # Upload a batch of scopes to the agent.
51
+ # Wraps in ServiceVersion, serializes to JSON, compresses with GZIP,
52
+ # builds multipart form, and POSTs to /symdb/v1/input via transport.
53
+ # No retries — single attempt, matching Python behavior.
54
+ # @param scopes [Array<Scope>] Scopes to upload
55
+ # @return [void]
56
+ def upload_scopes(scopes)
57
+ return if scopes.empty?
58
+
59
+ json_data = build_symbol_payload(scopes)
60
+ compressed_data = Zlib.gzip(json_data)
61
+
62
+ # Symbols for very large applications (>50MB after gzip) are dropped:
63
+ # the upload is skipped and the customer sees no autocomplete /
64
+ # symbol probe results for those classes. Java handles the same case
65
+ # by splitting the payload across multiple requests; we have not
66
+ # implemented splitting here. Deferred for a post-MVP follow-up.
67
+ if compressed_data.bytesize > MAX_PAYLOAD_SIZE
68
+ @logger.debug { "symdb: payload too large: #{compressed_data.bytesize}/#{MAX_PAYLOAD_SIZE} bytes, skipping" }
69
+ return
70
+ end
71
+
72
+ perform_http_upload(compressed_data, scopes.size)
73
+ rescue => e
74
+ @logger.debug { "symdb: upload failed: #{e.class}: #{e.message}" }
75
+ @telemetry&.report(e, description: 'symdb: upload failed')
76
+ end
77
+
78
+ private
79
+
80
+ # Build JSON payload from scopes.
81
+ # @param scopes [Array<Scope>] Scopes to serialize
82
+ # @return [String] JSON string
83
+ def build_symbol_payload(scopes)
84
+ ServiceVersion.new(
85
+ service: @settings.service,
86
+ env: @settings.env,
87
+ version: @settings.version,
88
+ scopes: scopes,
89
+ ).to_json
90
+ end
91
+
92
+ # Perform HTTP POST with multipart form-data via transport layer.
93
+ # @param compressed_data [String] GZIP compressed JSON payload
94
+ # @param scope_count [Integer] Number of scopes (for logging)
95
+ # @return [void]
96
+ def perform_http_upload(compressed_data, scope_count)
97
+ form = build_multipart_form(compressed_data)
98
+ response = @transport.send_symbols(form)
99
+ handle_response(response, scope_count)
100
+ end
101
+
102
+ # Build multipart form-data with event metadata and compressed symbols.
103
+ # @param compressed_data [String] GZIP compressed JSON payload
104
+ # @return [Hash] Form data hash with UploadIO objects
105
+ def build_multipart_form(compressed_data)
106
+ event_io = StringIO.new(build_event_metadata)
107
+ file_io = StringIO.new(compressed_data)
108
+
109
+ event_upload = Datadog::Core::Vendor::Multipart::Post::UploadIO.new(
110
+ event_io,
111
+ 'application/json',
112
+ 'event.json',
113
+ )
114
+
115
+ file_upload = Datadog::Core::Vendor::Multipart::Post::UploadIO.new(
116
+ file_io,
117
+ 'application/gzip',
118
+ "symbols_#{Process.pid}.json.gz",
119
+ )
120
+
121
+ {
122
+ 'event' => event_upload,
123
+ 'file' => file_upload,
124
+ }
125
+ end
126
+
127
+ # Build event.json metadata part.
128
+ # @return [String] JSON string for event metadata
129
+ def build_event_metadata
130
+ JSON.generate(
131
+ ddsource: 'ruby',
132
+ service: @settings.service,
133
+ runtimeId: Datadog::Core::Environment::Identity.id,
134
+ parentId: nil, # Fork tracking deferred for MVP
135
+ type: 'symdb',
136
+ )
137
+ end
138
+
139
+ # Handle HTTP response and track metrics.
140
+ # @param response [Core::Transport::Response] HTTP response from agent
141
+ # @param scope_count [Integer] Number of scopes uploaded
142
+ # @return [Boolean] true if successful, false otherwise
143
+ def handle_response(response, scope_count)
144
+ if response.internal_error?
145
+ @logger.debug { "symdb: upload failed: #{response.error.class}: #{response.error}" } # steep:ignore NoMethod
146
+ return false
147
+ end
148
+
149
+ case response.code
150
+ when 200..299
151
+ @logger.debug { "symdb: uploaded #{scope_count} scopes successfully" }
152
+ true
153
+ when 429
154
+ @logger.debug { "symdb: upload rejected: rate limited (429)" }
155
+ false
156
+ when 500..599
157
+ @logger.debug { "symdb: upload rejected: server error (#{response.code})" }
158
+ false
159
+ else
160
+ @logger.debug { "symdb: upload rejected: #{response.code}" }
161
+ false
162
+ end
163
+ end
164
+ end
165
+ end
166
+ end
@@ -0,0 +1,49 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Datadog
4
+ # Namespace for Datadog symbol database upload.
5
+ #
6
+ # @api private
7
+ module SymbolDatabase
8
+ # Sentinel value for unknown or unavailable minimum line number.
9
+ #
10
+ # Used for:
11
+ # 1. start_line when exact line cannot be determined (e.g., modules without methods)
12
+ # 2. Symbol line numbers for FIELD, STATIC_FIELD, ARG symbols to indicate
13
+ # the symbol is available throughout the entire enclosing scope
14
+ #
15
+ # Backend behavior: line=0 means symbol completes in every line of the scope
16
+ #
17
+ # Reference: Symbol Database Backend RFC, section "Edge Cases"
18
+ # - "We use 0 for FIELD, STATIC_FIELD and ARG. It means that the symbol
19
+ # will be completed in every line of the enclosing scope (CLASS or METHOD)."
20
+ #
21
+ # @see https://www.postgresql.org/docs/current/datatype-numeric.html
22
+ UNKNOWN_MIN_LINE = 0
23
+
24
+ # Sentinel value for unknown or unavailable maximum line number.
25
+ #
26
+ # Used for:
27
+ # 1. end_line when exact boundaries cannot be determined (e.g., modules, classes
28
+ # without methods, fallback when introspection fails)
29
+ # 2. LOCAL symbol line numbers when exact line is unknown (future feature)
30
+ #
31
+ # Value: 2147483647 (PostgreSQL signed INT_MAX, 2^31 - 1)
32
+ #
33
+ # Backend behavior:
34
+ # - For scopes: indicates "entire file" or "unknown end"
35
+ # - For LOCAL symbols (future): included in method probe completions but excluded
36
+ # from line probe completions
37
+ #
38
+ # Protocol specification:
39
+ # - "If the symbols of the scope should be available to all lines in the
40
+ # source_file of the scope, use start_line = 0 and end_line = 2147483647
41
+ # (maximum signed integer, postgres int max)."
42
+ # - "For LOCAL symbols, we use 2147483647 (signed int max) to avoid completing
43
+ # the symbol for line probes, but keep it in the method for method probe completions."
44
+ #
45
+ # Reference: Symbol Database Backend RFC, section "Scope" and "Edge Cases"
46
+ # @see https://www.postgresql.org/docs/current/datatype-numeric.html
47
+ UNKNOWN_MAX_LINE = 2147483647
48
+ end
49
+ end
@@ -56,7 +56,7 @@ module Datadog
56
56
  @buffer_spans += trace.length
57
57
  rescue => e
58
58
  Datadog.logger.debug(
59
- "Failed to measure queue accept. Cause: #{e.class}: #{e} Source: #{Array(e.backtrace).first}"
59
+ "Failed to measure queue accept. Cause: #{e.class}: #{e.message} Source: #{Array(e.backtrace).first}"
60
60
  )
61
61
  end
62
62
 
@@ -66,7 +66,7 @@ module Datadog
66
66
  @buffer_spans -= trace.length
67
67
  rescue => e
68
68
  Datadog.logger.debug(
69
- "Failed to measure queue drop. Cause: #{e.class}: #{e} Source: #{Array(e.backtrace).first}"
69
+ "Failed to measure queue drop. Cause: #{e.class}: #{e.message} Source: #{Array(e.backtrace).first}"
70
70
  )
71
71
  end
72
72
 
@@ -91,7 +91,7 @@ module Datadog
91
91
  @buffer_spans = 0
92
92
  rescue => e
93
93
  Datadog.logger.debug(
94
- "Failed to measure queue. Cause: #{e.class}: #{e} Source: #{Array(e.backtrace).first}"
94
+ "Failed to measure queue. Cause: #{e.class}: #{e.message} Source: #{Array(e.backtrace).first}"
95
95
  )
96
96
  end
97
97
  end