datadog 2.39.0 → 2.41.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 (158) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +97 -44
  3. data/ext/datadog_profiling_native_extension/collectors_stack.c +219 -148
  4. data/ext/datadog_profiling_native_extension/collectors_stack.h +16 -2
  5. data/ext/datadog_profiling_native_extension/collectors_thread_context.c +12 -7
  6. data/ext/datadog_profiling_native_extension/crashtracking_runtime_stacks.c +5 -4
  7. data/ext/datadog_profiling_native_extension/datadog_ruby_common.c +19 -11
  8. data/ext/datadog_profiling_native_extension/datadog_ruby_common.h +7 -0
  9. data/ext/datadog_profiling_native_extension/extconf.rb +12 -7
  10. data/ext/datadog_profiling_native_extension/native_extension_helpers.rb +3 -3
  11. data/ext/datadog_profiling_native_extension/private_vm_api_access.c +284 -76
  12. data/ext/datadog_profiling_native_extension/private_vm_api_access.h +16 -10
  13. data/ext/libdatadog_api/datadog_ruby_common.c +19 -11
  14. data/ext/libdatadog_api/datadog_ruby_common.h +7 -0
  15. data/ext/libdatadog_api/init.c +4 -0
  16. data/ext/libdatadog_api/trace_exporter.c +1312 -0
  17. data/ext/libdatadog_api/trace_exporter.h +5 -0
  18. data/ext/libdatadog_extconf_helpers.rb +1 -1
  19. data/lib/datadog/ai_guard/api_client.rb +1 -1
  20. data/lib/datadog/ai_guard/evaluation/request.rb +8 -8
  21. data/lib/datadog/ai_guard/evaluation.rb +11 -3
  22. data/lib/datadog/ai_guard/ext.rb +3 -1
  23. data/lib/datadog/appsec/actions_handler/serializable_backtrace.rb +21 -42
  24. data/lib/datadog/appsec/api_security/endpoint_collection/grape_route_serializer.rb +1 -1
  25. data/lib/datadog/appsec/api_security/endpoint_collection/rails_route_serializer.rb +1 -1
  26. data/lib/datadog/appsec/api_security/endpoint_collection/sinatra_route_serializer.rb +1 -1
  27. data/lib/datadog/appsec/configuration.rb +11 -2
  28. data/lib/datadog/appsec/context.rb +1 -1
  29. data/lib/datadog/appsec/contrib/active_record/instrumentation.rb +1 -1
  30. data/lib/datadog/appsec/contrib/aws_lambda/waf_addresses.rb +2 -2
  31. data/lib/datadog/appsec/contrib/devise/configuration.rb +1 -1
  32. data/lib/datadog/appsec/contrib/devise/patcher.rb +1 -1
  33. data/lib/datadog/appsec/contrib/excon/ssrf_detection_middleware.rb +2 -2
  34. data/lib/datadog/appsec/contrib/faraday/ssrf_detection_middleware.rb +2 -2
  35. data/lib/datadog/appsec/contrib/graphql/gateway/watcher.rb +1 -1
  36. data/lib/datadog/appsec/contrib/rack/ext.rb +2 -2
  37. data/lib/datadog/appsec/contrib/rack/gateway/watcher.rb +2 -2
  38. data/lib/datadog/appsec/contrib/rack/patcher.rb +1 -1
  39. data/lib/datadog/appsec/contrib/rack/request_body_middleware.rb +6 -3
  40. data/lib/datadog/appsec/contrib/rails/gateway/watcher.rb +2 -2
  41. data/lib/datadog/appsec/contrib/rails/request_middleware.rb +2 -1
  42. data/lib/datadog/appsec/contrib/rest_client/request_ssrf_detection_patch.rb +2 -2
  43. data/lib/datadog/appsec/contrib/sinatra/gateway/watcher.rb +2 -2
  44. data/lib/datadog/appsec/contrib/sinatra/request_middleware.rb +2 -1
  45. data/lib/datadog/appsec/processor/rule_loader.rb +12 -12
  46. data/lib/datadog/appsec/response.rb +1 -1
  47. data/lib/datadog/appsec/route_normalizer/route_pattern.rb +3 -3
  48. data/lib/datadog/appsec/security_engine/engine.rb +3 -3
  49. data/lib/datadog/core/configuration/components.rb +2 -2
  50. data/lib/datadog/core/configuration/option_definition.rb +1 -1
  51. data/lib/datadog/core/configuration/options.rb +1 -1
  52. data/lib/datadog/core/configuration/settings.rb +33 -41
  53. data/lib/datadog/core/configuration/supported_configurations.rb +3 -0
  54. data/lib/datadog/core/diagnostics/environment_logger.rb +36 -1
  55. data/lib/datadog/core/environment/gc.rb +1 -1
  56. data/lib/datadog/core/environment/vm_cache.rb +1 -1
  57. data/lib/datadog/core/environment/yjit.rb +2 -2
  58. data/lib/datadog/core/metrics/helpers.rb +1 -1
  59. data/lib/datadog/core/metrics/options.rb +2 -2
  60. data/lib/datadog/core/process_discovery.rb +1 -1
  61. data/lib/datadog/core/remote/client.rb +1 -1
  62. data/lib/datadog/core/remote/configuration/repository.rb +2 -2
  63. data/lib/datadog/core/telemetry/configuration_value.rb +38 -0
  64. data/lib/datadog/core/telemetry/event/app_client_configuration_change.rb +3 -1
  65. data/lib/datadog/core/telemetry/event/app_endpoints_loaded.rb +1 -1
  66. data/lib/datadog/core/telemetry/event/app_started.rb +5 -24
  67. data/lib/datadog/core/telemetry/event/generate_metrics.rb +1 -1
  68. data/lib/datadog/core/telemetry/event/log.rb +2 -2
  69. data/lib/datadog/core/telemetry/event/synth_app_client_configuration_change.rb +1 -1
  70. data/lib/datadog/core/telemetry/metric.rb +2 -2
  71. data/lib/datadog/core/telemetry/request.rb +2 -2
  72. data/lib/datadog/core/transport/http.rb +1 -1
  73. data/lib/datadog/core/transport/transport.rb +1 -1
  74. data/lib/datadog/core/utils/at_fork_monkey_patch.rb +170 -18
  75. data/lib/datadog/core/utils/sequence.rb +3 -6
  76. data/lib/datadog/core/utils/time.rb +16 -54
  77. data/lib/datadog/data_streams/processor.rb +14 -14
  78. data/lib/datadog/di/el/compiler.rb +88 -12
  79. data/lib/datadog/di/el/evaluator.rb +82 -2
  80. data/lib/datadog/di/el/expression.rb +13 -2
  81. data/lib/datadog/di/instrumenter.rb +5 -4
  82. data/lib/datadog/di/probe_builder.rb +7 -6
  83. data/lib/datadog/di/probe_notification_builder.rb +2 -2
  84. data/lib/datadog/di/serializer.rb +29 -4
  85. data/lib/datadog/error_tracking/component.rb +1 -1
  86. data/lib/datadog/kit/appsec/events/v2.rb +2 -2
  87. data/lib/datadog/open_feature/exposures/batch_builder.rb +1 -1
  88. data/lib/datadog/open_feature/exposures/event.rb +5 -5
  89. data/lib/datadog/profiling/collectors/info.rb +1 -1
  90. data/lib/datadog/profiling/collectors/thread_context.rb +6 -13
  91. data/lib/datadog/profiling/component.rb +1 -0
  92. data/lib/datadog/profiling/tasks/exec.rb +1 -1
  93. data/lib/datadog/profiling.rb +4 -0
  94. data/lib/datadog/symbol_database/extractor.rb +6 -6
  95. data/lib/datadog/tracing/component.rb +21 -0
  96. data/lib/datadog/tracing/configuration/dynamic/option.rb +4 -0
  97. data/lib/datadog/tracing/configuration/dynamic.rb +5 -3
  98. data/lib/datadog/tracing/configuration/ext.rb +1 -0
  99. data/lib/datadog/tracing/configuration/settings.rb +16 -0
  100. data/lib/datadog/tracing/contrib/action_cable/events.rb +1 -1
  101. data/lib/datadog/tracing/contrib/action_mailer/events.rb +1 -1
  102. data/lib/datadog/tracing/contrib/action_pack/action_controller/instrumentation.rb +2 -2
  103. data/lib/datadog/tracing/contrib/action_view/events.rb +1 -1
  104. data/lib/datadog/tracing/contrib/active_model_serializers/events.rb +1 -1
  105. data/lib/datadog/tracing/contrib/active_record/configuration/resolver.rb +1 -1
  106. data/lib/datadog/tracing/contrib/active_record/events.rb +1 -1
  107. data/lib/datadog/tracing/contrib/active_support/cache/events/cache.rb +2 -2
  108. data/lib/datadog/tracing/contrib/aws/ext.rb +1 -1
  109. data/lib/datadog/tracing/contrib/aws/services.rb +1 -1
  110. data/lib/datadog/tracing/contrib/elasticsearch/quantize.rb +1 -1
  111. data/lib/datadog/tracing/contrib/ext.rb +3 -3
  112. data/lib/datadog/tracing/contrib/grpc/datadog_interceptor/client.rb +1 -1
  113. data/lib/datadog/tracing/contrib/grpc/datadog_interceptor/server.rb +1 -1
  114. data/lib/datadog/tracing/contrib/grpc/distributed/propagation.rb +1 -1
  115. data/lib/datadog/tracing/contrib/hanami/router_tracing.rb +2 -1
  116. data/lib/datadog/tracing/contrib/kafka/events/connection/request.rb +4 -0
  117. data/lib/datadog/tracing/contrib/kafka/events/produce_operation/send_messages.rb +7 -2
  118. data/lib/datadog/tracing/contrib/kafka/events/producer/deliver_messages.rb +7 -2
  119. data/lib/datadog/tracing/contrib/kafka/events.rb +1 -1
  120. data/lib/datadog/tracing/contrib/karafka/distributed/propagation.rb +1 -1
  121. data/lib/datadog/tracing/contrib/mongodb/parsers.rb +1 -1
  122. data/lib/datadog/tracing/contrib/opensearch/ext.rb +1 -1
  123. data/lib/datadog/tracing/contrib/opensearch/quantize.rb +1 -1
  124. data/lib/datadog/tracing/contrib/patchable.rb +1 -1
  125. data/lib/datadog/tracing/contrib/patcher.rb +1 -1
  126. data/lib/datadog/tracing/contrib/que/tracer.rb +1 -1
  127. data/lib/datadog/tracing/contrib/racecar/events.rb +1 -1
  128. data/lib/datadog/tracing/contrib/rack/configuration/settings.rb +1 -1
  129. data/lib/datadog/tracing/contrib/rack/header_tagging.rb +1 -1
  130. data/lib/datadog/tracing/contrib/rack/patcher.rb +1 -1
  131. data/lib/datadog/tracing/contrib/rack/trace_proxy_middleware.rb +1 -1
  132. data/lib/datadog/tracing/contrib/redis/configuration/resolver.rb +1 -1
  133. data/lib/datadog/tracing/contrib/redis/quantize.rb +36 -2
  134. data/lib/datadog/tracing/contrib/redis/trace_middleware.rb +9 -3
  135. data/lib/datadog/tracing/contrib/sequel/database.rb +1 -5
  136. data/lib/datadog/tracing/contrib/sequel/utils.rb +1 -1
  137. data/lib/datadog/tracing/contrib/sidekiq/distributed/propagation.rb +1 -1
  138. data/lib/datadog/tracing/contrib/sinatra/configuration/settings.rb +1 -1
  139. data/lib/datadog/tracing/contrib/sneakers/tracer.rb +1 -1
  140. data/lib/datadog/tracing/contrib/utils/quantization/hash_formatter.rb +1 -1
  141. data/lib/datadog/tracing/contrib/waterdrop/distributed/propagation.rb +1 -1
  142. data/lib/datadog/tracing/correlation.rb +2 -2
  143. data/lib/datadog/tracing/diagnostics/environment_logger.rb +2 -2
  144. data/lib/datadog/tracing/distributed/baggage.rb +1 -2
  145. data/lib/datadog/tracing/distributed/propagation_policy.rb +5 -0
  146. data/lib/datadog/tracing/metadata/tagging.rb +1 -1
  147. data/lib/datadog/tracing/remote.rb +2 -2
  148. data/lib/datadog/tracing/span.rb +1 -1
  149. data/lib/datadog/tracing/span_operation.rb +1 -1
  150. data/lib/datadog/tracing/sync_writer.rb +8 -3
  151. data/lib/datadog/tracing/tracer.rb +8 -0
  152. data/lib/datadog/tracing/transport/io/traces.rb +1 -1
  153. data/lib/datadog/tracing/transport/native/response.rb +71 -0
  154. data/lib/datadog/tracing/transport/native.rb +387 -0
  155. data/lib/datadog/tracing/transport/trace_formatter.rb +1 -1
  156. data/lib/datadog/tracing/writer.rb +17 -4
  157. data/lib/datadog/version.rb +1 -1
  158. metadata +12 -7
@@ -346,7 +346,7 @@ module Datadog
346
346
  resource: @resource,
347
347
  service: @service,
348
348
  trace_id: @trace_id,
349
- type: @type
349
+ type: @type,
350
350
  }
351
351
 
352
352
  if stopped?
@@ -46,10 +46,15 @@ module Datadog
46
46
  logger.debug(e)
47
47
  end
48
48
 
49
- # Does nothing.
50
- # The {SyncWriter} does not need to be stopped as it holds no state.
49
+ # Stops the {SyncWriter}.
50
+ # The {SyncWriter} holds no worker thread, but it owns its transport, so
51
+ # on teardown we deterministically release transports that hold native
52
+ # resources (e.g. the native trace exporter's Rust runtime and
53
+ # process-global fork hooks) rather than relying on the GC finalizer.
54
+ # The default HTTP transport has no `#close` and is left untouched, and
55
+ # `#close` is idempotent so repeated `#stop` calls are safe.
51
56
  def stop
52
- # No cleanup to do for the SyncWriter
57
+ @transport.close if @transport.respond_to?(:close)
53
58
  true
54
59
  end
55
60
 
@@ -628,6 +628,10 @@ module Datadog
628
628
  appsec_bit = upstream_tags[Tracing::Metadata::Ext::Distributed::TAG_TRACE_SOURCE].to_i(16) &
629
629
  Datadog::AppSec::Ext::PRODUCT_BIT
630
630
  return appsec_enabled if appsec_bit != 0
631
+
632
+ ai_guard_bit = upstream_tags[Tracing::Metadata::Ext::Distributed::TAG_TRACE_SOURCE].to_i(16) &
633
+ Datadog::AIGuard::Ext::PRODUCT_BIT
634
+ return ai_guard_enabled if ai_guard_bit != 0
631
635
  end
632
636
 
633
637
  false
@@ -642,6 +646,10 @@ module Datadog
642
646
  @appsec_enabled ||= Datadog.configuration.appsec.enabled
643
647
  end
644
648
 
649
+ def ai_guard_enabled
650
+ @ai_guard_enabled ||= Datadog.configuration.respond_to?(:ai_guard) && Datadog.configuration.ai_guard.enabled
651
+ end
652
+
645
653
  # Due to APM Tracing (the product) and Tracing (the transport) being intertwined, we cannot completely disabled APM
646
654
  # without also disabling the tracer. When setting `@apm_tracing_enabled` to `false`, it does not disable the tracer,
647
655
  # but rather only sends heartbeat traces (1 per minutes), so that the service is considered alive in the backend.
@@ -27,7 +27,7 @@ module Datadog
27
27
  ENCODED_IDS = [
28
28
  :trace_id,
29
29
  :span_id,
30
- :parent_id
30
+ :parent_id,
31
31
  ].freeze
32
32
 
33
33
  # Encodes a list of traces
@@ -0,0 +1,71 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "json"
4
+
5
+ module Datadog
6
+ module Tracing
7
+ module Transport
8
+ module Native
9
+ # Response from the native trace exporter.
10
+ #
11
+ # Constructed by the C extension after a send completes (or fails).
12
+ # Implements the same predicate interface as the HTTP transport's
13
+ # response so callers can treat both uniformly.
14
+ class Response
15
+ SERVICE_RATE_KEY = "rate_by_service"
16
+
17
+ attr_reader :trace_count, :payload
18
+
19
+ def initialize(ok:, internal_error: false, server_error: false, client_error: false,
20
+ not_found: false, unsupported: false, trace_count: 0, payload: nil)
21
+ @ok = ok
22
+ @internal_error = internal_error
23
+ @server_error = server_error
24
+ @client_error = client_error
25
+ @not_found = not_found
26
+ @unsupported = unsupported
27
+ @trace_count = trace_count
28
+ @payload = payload
29
+ end
30
+
31
+ def ok?
32
+ @ok
33
+ end
34
+
35
+ def internal_error?
36
+ @internal_error
37
+ end
38
+
39
+ def server_error?
40
+ @server_error
41
+ end
42
+
43
+ def client_error?
44
+ @client_error
45
+ end
46
+
47
+ def not_found?
48
+ @not_found
49
+ end
50
+
51
+ def unsupported?
52
+ @unsupported
53
+ end
54
+
55
+ # Parse the agent's JSON response body and extract the
56
+ # +rate_by_service+ map. Returns +nil+ when the payload
57
+ # is absent or does not contain sampling rates.
58
+ def service_rates
59
+ payload = @payload
60
+ return nil if payload.nil? || payload.empty?
61
+
62
+ parsed = JSON.parse(payload)
63
+ parsed[SERVICE_RATE_KEY] if parsed.is_a?(Hash)
64
+ rescue JSON::ParserError
65
+ nil
66
+ end
67
+ end
68
+ end
69
+ end
70
+ end
71
+ end
@@ -0,0 +1,387 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "json"
4
+ require_relative "trace_formatter"
5
+ require_relative "statistics"
6
+
7
+ module Datadog
8
+ module Tracing
9
+ module Transport
10
+ # Transport backed by the native trace exporter (Rust via C FFI).
11
+ #
12
+ # Converts Ruby Span objects directly to Rust spans and delegates
13
+ # serialization and sending to the Rust data pipeline, which handles
14
+ # stats computation, msgpack encoding, and HTTP transport with retry
15
+ # logic.
16
+ #
17
+ # Implements the same +send_traces+ / +stats+ interface as
18
+ # {Datadog::Tracing::Transport::Traces::Transport} so it can be used
19
+ # as a drop-in replacement via the +Writer+'s +:transport+ option.
20
+ module Native
21
+ # Returns +nil+ when the native extension is available, or a +String+
22
+ # describing why it is not.
23
+ UNSUPPORTED_REASON = begin
24
+ require "datadog/core"
25
+ Datadog::Core::LIBDATADOG_API_FAILURE
26
+ rescue => e
27
+ e.message
28
+ end
29
+
30
+ def self.supported?
31
+ UNSUPPORTED_REASON.nil?
32
+ end
33
+
34
+ # +TraceExporter+ and +TracerSpan+ are defined by the C extension in
35
+ # +ext/libdatadog_api/trace_exporter.c+ and become available after the
36
+ # native extension is loaded. +Response+ is a plain Ruby class (see
37
+ # +native/response.rb+); the C side only resolves and instantiates it.
38
+ #
39
+ # The hierarchy is:
40
+ # Datadog::Tracing::Transport::Native::TraceExporter (C)
41
+ # Datadog::Tracing::Transport::Native::TracerSpan (C)
42
+ # Datadog::Tracing::Transport::Native::Response (Ruby)
43
+
44
+ # Drop-in transport that delegates to the native trace exporter.
45
+ class Transport
46
+ include Statistics
47
+
48
+ attr_reader :logger
49
+
50
+ # @param agent_settings [Datadog::Core::Configuration::AgentSettingsResolver::AgentSettings]
51
+ # Agent connection settings (provides +#url+).
52
+ # @param logger [Logger]
53
+ def initialize(agent_settings:, logger:)
54
+ unless Native.supported?
55
+ raise "Native transport is not supported: #{UNSUPPORTED_REASON}"
56
+ end
57
+
58
+ @logger = logger
59
+
60
+ # Guards the one-shot warning about span fields the native exporter
61
+ # does not yet convert (see #warn_unsupported_fields!).
62
+ @unsupported_fields_warned = false
63
+
64
+ # Serializes native sends and is held across a fork. See the
65
+ # fork-safety note below.
66
+ @send_mutex = Mutex.new
67
+
68
+ # Serializes concurrent fork-hook lifecycles. Unlike @send_mutex,
69
+ # this is held across the fork through the matching completion hook.
70
+ @fork_mutex = Mutex.new
71
+
72
+ url = agent_settings.url
73
+ tracer_version = tracer_version_string
74
+ language = Core::Environment::Ext::LANG
75
+ language_version = Core::Environment::Ext::LANG_VERSION
76
+ language_interpreter = Core::Environment::Ext::LANG_INTERPRETER
77
+ hostname = begin
78
+ Core::Environment::Socket.hostname
79
+ rescue
80
+ nil
81
+ end
82
+ env = Datadog.configuration.env
83
+ service = Datadog.configuration.service
84
+ version = Datadog.configuration.version
85
+
86
+ exporter = Native::TraceExporter._native_new(
87
+ url: url,
88
+ tracer_version: tracer_version,
89
+ language: language,
90
+ language_version: language_version,
91
+ language_interpreter: language_interpreter,
92
+ hostname: hostname,
93
+ env: env,
94
+ service: service,
95
+ version: version
96
+ )
97
+ @exporter = exporter
98
+
99
+ # Fork safety: the native exporter owns a long-lived tokio runtime
100
+ # with background worker threads. Around a fork we must quiesce the
101
+ # runtime before forking and restore it afterwards in both the parent
102
+ # and the child (where the inherited runtime is dead).
103
+ #
104
+ # A libdatadog Rust call must NOT be interrupted by `fork()`. A
105
+ # native send releases the GVL during the Rust
106
+ # `ddog_trace_exporter_send_trace_chunks` call, so if another thread
107
+ # forks while a (typically the writer/`AsyncTransport`) thread is
108
+ # mid-send, the child would inherit a half-completed send and
109
+ # Rust-internal locks, deadlocking or crashing. `_native_before_fork`
110
+ # also tears down and replaces the runtime, so it must not run while
111
+ # a send is still using that runtime.
112
+ #
113
+ # `@fork_mutex` serializes concurrent forks for this exporter. The
114
+ # `:before` hook locks it first, then calls `_native_before_fork`,
115
+ # and finally locks `@send_mutex`, which serializes sends and is
116
+ # held across the fork. Sends and #close only acquire @send_mutex,
117
+ # so there is no lock-order inversion.
118
+ #
119
+ # `_native_before_fork` must run before locking `@send_mutex` to
120
+ # pause the runtime before waiting for an in-flight send to drain.
121
+ # The hook first calls
122
+ # `_native_before_fork` to pause the runtime, THEN locks the mutex.
123
+ # This ordering minimizes the serialized section and is safe to run
124
+ # concurrently with an in-flight send: `block_on` (used by
125
+ # `send_trace_chunks`) only briefly locks the runtime mutex to clone
126
+ # the `Arc<Runtime>` (or build a throwaway `current_thread` runtime
127
+ # if it has been taken) and then drives the send on its own clone
128
+ # holding no mutex, while `_native_before_fork` just `take()`s the
129
+ # shared `Arc` and pauses workers -- so neither deadlocks the other.
130
+ # Locking the mutex AFTER `_native_before_fork` still guarantees the
131
+ # drain: it BLOCKS until any in-flight send finishes (bounded by the
132
+ # exporter's request timeout), which drops the send's last `Arc` and
133
+ # fully shuts the runtime down, and prevents new sends from starting.
134
+ # The lock is released in the `:parent` and `:child` hooks. In
135
+ # practice only one writer thread sends, so serializing adds no real
136
+ # contention.
137
+ #
138
+ # Note: the `:before` block runs before genuine forks (web-server
139
+ # workers, `Process.daemon`) that go through `Process._fork`. It
140
+ # does NOT run for `system`/backtick/`IO.popen`/`Process.spawn`,
141
+ # which spawn via `posix_spawn`/`vfork`+`exec` and replace the
142
+ # process image rather than carrying over the parent's work. Keeping
143
+ # it cheap is still worthwhile since real forks can be frequent.
144
+ #
145
+ # The hooks below are process-global and are never auto-removed by
146
+ # the fork machinery, so each transport must deregister its own
147
+ # hooks when it goes away (see #close and the finalizer): otherwise
148
+ # the closures keep the exporter -- and its runtime threads -- alive
149
+ # forever, and every later fork runs them against every
150
+ # historically-created exporter. The closures intentionally close
151
+ # over only locals (NOT `self`), so the Transport stays GC-eligible and
152
+ # its finalizer can fire even when #close was not called explicitly.
153
+ send_mutex = @send_mutex
154
+ fork_mutex = @fork_mutex
155
+ before_hook = proc do
156
+ fork_mutex.lock
157
+
158
+ begin
159
+ # Pause the runtime first (safe to run concurrently with an
160
+ # in-flight send), then wait for it to drain by acquiring the
161
+ # mutex. The lock must happen even if `_native_before_fork` raises, so the
162
+ # in-flight send has returned before the fork. Held across the
163
+ # fork; released in :parent/:child.
164
+ exporter._native_before_fork
165
+ rescue => e
166
+ Datadog.logger.warn { "Native transport before-fork preparation failed; traces may not be sent to Datadog: #{e.class}: #{e.message}" }
167
+ ensure
168
+ send_mutex.lock
169
+ end
170
+ end
171
+ parent_hook = proc do
172
+ exporter._native_after_fork_in_parent if fork_mutex.owned?
173
+ rescue => e
174
+ Datadog.logger.warn { "Native transport after-fork reset failed; traces may not be sent to Datadog: #{e.class}: #{e.message}" }
175
+ ensure
176
+ # `:before`'s ensure always locks the mutex on the forking thread
177
+ # before any fork outcome, so it is normally owned here; the guard
178
+ # only covers `:before` being interrupted mid-lock (e.g. a kill
179
+ # between `_native_before_fork` and the lock). Released even if the
180
+ # native call raised, so a failure can't leave the mutex locked.
181
+ send_mutex.unlock if send_mutex.owned?
182
+ fork_mutex.unlock if fork_mutex.owned?
183
+ end
184
+ child_hook = proc do
185
+ exporter._native_after_fork_in_child if fork_mutex.owned?
186
+ rescue => e
187
+ Datadog.logger.warn { "Native transport after-fork reset failed; traces may not be sent to Datadog: #{e.class}: #{e.message}" }
188
+ ensure
189
+ # See the :parent hook: normally owned (the forking thread is the
190
+ # lone survivor in the child); the guard only covers `:before`
191
+ # being interrupted mid-lock.
192
+ send_mutex.unlock if send_mutex.owned?
193
+ fork_mutex.unlock if fork_mutex.owned?
194
+ end
195
+
196
+ @fork_hooks = Core::Utils::AtForkMonkeyPatch.at_fork_blocks(
197
+ before: before_hook,
198
+ parent: parent_hook,
199
+ child: child_hook
200
+ )
201
+ remove_fork_hooks = self.class.send(:fork_hooks_remover, @fork_hooks)
202
+
203
+ # Fallback so a dropped (un-#close'd) transport doesn't leak its
204
+ # global fork hooks (and, through them, the exporter/runtime). The
205
+ # removal proc is built by a class method capturing ONLY the
206
+ # stage->block handles, never `self` -- a finalizer that closed over
207
+ # the object it is attached to would keep that object reachable and
208
+ # never run. A fork that already copied these callbacks still owns
209
+ # them through its matching completion stage.
210
+ ObjectSpace.define_finalizer(
211
+ self,
212
+ remove_fork_hooks
213
+ )
214
+ end
215
+
216
+ # Deregister this transport's process-global fork hooks and release the
217
+ # native exporter so its runtime can shut down. Idempotent: safe to
218
+ # call multiple times and safe to call after the finalizer has run.
219
+ def close
220
+ fork_hooks = @send_mutex.synchronize do
221
+ hooks = @fork_hooks
222
+ return if hooks.nil?
223
+
224
+ @fork_hooks = nil
225
+ @exporter = nil
226
+ hooks
227
+ end
228
+
229
+ fork_hooks.each do |stage, block|
230
+ Core::Utils::AtForkMonkeyPatch.remove_at_fork(stage, block)
231
+ end
232
+
233
+ # The finalizer only exists to deregister the hooks for a transport
234
+ # dropped without #close. We have just done that, so remove it;
235
+ # otherwise its closed-over hook blocks keep the exporter alive until
236
+ # this transport is itself collected.
237
+ ObjectSpace.undefine_finalizer(self)
238
+
239
+ nil
240
+ end
241
+
242
+ # Builds the proc that deregisters a transport's fork hooks.
243
+ #
244
+ # Defined as a class method so the proc closes over ONLY the
245
+ # stage->block handles passed in and never over a Transport instance
246
+ # (which would keep the instance reachable and prevent the finalizer
247
+ # from ever running). Removing the hooks unpins the exporter captured
248
+ # by those closures, allowing it (and its runtime) to be freed.
249
+ def self.fork_hooks_remover(fork_hooks)
250
+ proc do
251
+ fork_hooks.each do |stage, block|
252
+ Core::Utils::AtForkMonkeyPatch.remove_at_fork(stage, block)
253
+ end
254
+ end
255
+ end
256
+ private_class_method :fork_hooks_remover
257
+
258
+ # Send a list of traces to the agent.
259
+ #
260
+ # Each trace is a {Datadog::Tracing::TraceSegment} whose +#spans+
261
+ # returns an +Array+ of {Datadog::Tracing::Span}.
262
+ #
263
+ # @param traces [Array<Datadog::Tracing::TraceSegment>]
264
+ # @return [Array<Response>] one response per batch sent
265
+ def send_traces(traces)
266
+ return [] if traces.empty?
267
+
268
+ # Apply trace-level tags to root spans (same as the HTTP transport)
269
+ traces.each { |trace| TraceFormatter.format!(trace) }
270
+
271
+ # Build the Array<Array<Span>> structure expected by the C extension.
272
+ # Each trace segment becomes one inner array (one trace chunk).
273
+ chunks = traces.map(&:spans)
274
+
275
+ # Span events and span links are not yet converted and would be
276
+ # dropped. Warn (once) so the loss is visible.
277
+ warn_unsupported_fields!(chunks)
278
+
279
+ # Serialize the native send and hold the mutex across it so a
280
+ # concurrent fork's :before hook blocks until this send drains
281
+ # (and `_native_before_fork` cannot tear down the runtime mid-send).
282
+ # `Mutex#synchronize` releases on exception / `rb_jump_tag` via its
283
+ # ensure, so interrupt propagation stays correct.
284
+ responses = @send_mutex.synchronize do
285
+ # Resolve the exporter under the same lock used by #close, so
286
+ # close either drains this send or prevents it from starting.
287
+ exporter = @exporter
288
+ raise "Native transport has been closed" if exporter.nil?
289
+
290
+ exporter._native_send_traces(chunks)
291
+ end
292
+
293
+ # Update statistics from the response
294
+ responses.each { |response| update_stats_from_response!(response) }
295
+
296
+ responses
297
+ rescue => e
298
+ logger.debug { "Native transport error: #{e.class} #{e.message}" }
299
+ update_stats_from_exception!(e)
300
+ [InternalErrorResponse.new(e)]
301
+ end
302
+
303
+ private
304
+
305
+ # Warn, at most once per transport, when a batch contains span fields
306
+ # the native exporter does not yet convert (span events and span
307
+ # links). These are silently dropped by the native path; full support
308
+ # is tracked separately. The check is cheap: the fields are already-
309
+ # materialized collections on each Span.
310
+ def warn_unsupported_fields!(chunks)
311
+ return if @unsupported_fields_warned
312
+
313
+ unsupported = []
314
+ chunks.each do |spans|
315
+ spans.each do |span|
316
+ unsupported << "span events" if span.events.any?
317
+ unsupported << "span links" if span.links.any?
318
+ end
319
+ end
320
+ return if unsupported.empty?
321
+
322
+ @unsupported_fields_warned = true
323
+ fields = unsupported.uniq.join(", ")
324
+ logger.warn do
325
+ "Native transport does not yet support: #{fields}. This data will not be sent to Datadog. " \
326
+ "Unset DD_EXPERIMENTAL_NATIVE_TRANSPORT_ENABLED to use the default transport if you rely on these."
327
+ end
328
+ end
329
+
330
+ def tracer_version_string
331
+ defined?(Datadog::VERSION::STRING) ? Datadog::VERSION::STRING : "unknown"
332
+ end
333
+ end
334
+
335
+ # Response for internal errors (exceptions raised before reaching
336
+ # the native transport).
337
+ class InternalErrorResponse
338
+ attr_reader :error
339
+
340
+ def initialize(error)
341
+ @error = error
342
+ end
343
+
344
+ def ok?
345
+ false
346
+ end
347
+
348
+ def internal_error?
349
+ true
350
+ end
351
+
352
+ def server_error?
353
+ false
354
+ end
355
+
356
+ def client_error?
357
+ false
358
+ end
359
+
360
+ def not_found?
361
+ false
362
+ end
363
+
364
+ def unsupported?
365
+ false
366
+ end
367
+
368
+ def payload
369
+ nil
370
+ end
371
+
372
+ def trace_count
373
+ 0
374
+ end
375
+
376
+ def service_rates
377
+ nil
378
+ end
379
+
380
+ def inspect
381
+ "#<#{self.class} error=#{error.inspect}>"
382
+ end
383
+ end
384
+ end
385
+ end
386
+ end
387
+ end
@@ -213,7 +213,7 @@ module Datadog
213
213
  def tag_apm_tracing_disabled!
214
214
  return if trace.apm_tracing_enabled
215
215
 
216
- root_span.set_tag(Tracing::Metadata::Ext::TAG_APM_ENABLED, 0)
216
+ trace.spans.each { |span| span.set_metric(Tracing::Metadata::Ext::TAG_APM_ENABLED, 0) }
217
217
  end
218
218
 
219
219
  def tag_git_repository_url!
@@ -130,7 +130,7 @@ module Datadog
130
130
  def stats
131
131
  {
132
132
  traces_flushed: @traces_flushed,
133
- transport: @transport.stats
133
+ transport: @transport.stats,
134
134
  }
135
135
  end
136
136
 
@@ -173,10 +173,23 @@ module Datadog
173
173
  def stop_worker
174
174
  @stopped = true
175
175
 
176
- return if @worker.nil?
176
+ if @worker
177
+ @worker.stop
178
+ @worker = nil
179
+ end
177
180
 
178
- @worker.stop
179
- @worker = nil
181
+ # Stopping a writer is permanent: once `@stopped` is set, `#start`
182
+ # refuses to spin up a new worker, so this writer (and the transport it
183
+ # owns) is being discarded for good. Forks do not stop the writer
184
+ # (`#start` re-creates the worker for the new pid while reusing the same
185
+ # transport), so this only runs on real teardown/reconfiguration.
186
+ #
187
+ # Deterministically release transports that hold native resources
188
+ # (e.g. the native trace exporter's Rust runtime and process-global
189
+ # fork hooks) instead of waiting on the GC finalizer. The default HTTP
190
+ # transport has no `#close` and is left untouched. `#close` is
191
+ # idempotent, so repeated `#stop` calls are safe.
192
+ @transport.close if @transport.respond_to?(:close)
180
193
 
181
194
  true
182
195
  end
@@ -3,7 +3,7 @@
3
3
  module Datadog
4
4
  module VERSION
5
5
  MAJOR = 2
6
- MINOR = 39
6
+ MINOR = 41
7
7
  PATCH = 0
8
8
  PRE = nil
9
9
  BUILD = nil
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: datadog
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.39.0
4
+ version: 2.41.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Datadog, Inc.
@@ -32,7 +32,7 @@ dependencies:
32
32
  version: '3.5'
33
33
  - - ">="
34
34
  - !ruby/object:Gem::Version
35
- version: 3.5.3
35
+ version: 3.5.4
36
36
  type: :runtime
37
37
  prerelease: false
38
38
  version_requirements: !ruby/object:Gem::Requirement
@@ -42,7 +42,7 @@ dependencies:
42
42
  version: '3.5'
43
43
  - - ">="
44
44
  - !ruby/object:Gem::Version
45
- version: 3.5.3
45
+ version: 3.5.4
46
46
  - !ruby/object:Gem::Dependency
47
47
  name: libddwaf
48
48
  requirement: !ruby/object:Gem::Requirement
@@ -63,14 +63,14 @@ dependencies:
63
63
  requirements:
64
64
  - - "~>"
65
65
  - !ruby/object:Gem::Version
66
- version: 36.0.0.1.0
66
+ version: 40.0.0.1.0
67
67
  type: :runtime
68
68
  prerelease: false
69
69
  version_requirements: !ruby/object:Gem::Requirement
70
70
  requirements:
71
71
  - - "~>"
72
72
  - !ruby/object:Gem::Version
73
- version: 36.0.0.1.0
73
+ version: 40.0.0.1.0
74
74
  - !ruby/object:Gem::Dependency
75
75
  name: logger
76
76
  requirement: !ruby/object:Gem::Requirement
@@ -183,6 +183,8 @@ files:
183
183
  - ext/libdatadog_api/library_config.h
184
184
  - ext/libdatadog_api/process_discovery.c
185
185
  - ext/libdatadog_api/process_discovery.h
186
+ - ext/libdatadog_api/trace_exporter.c
187
+ - ext/libdatadog_api/trace_exporter.h
186
188
  - ext/libdatadog_extconf_helpers.rb
187
189
  - lib/datadog.rb
188
190
  - lib/datadog/ai_guard.rb
@@ -430,6 +432,7 @@ files:
430
432
  - lib/datadog/core/tag_builder.rb
431
433
  - lib/datadog/core/tag_normalizer.rb
432
434
  - lib/datadog/core/telemetry/component.rb
435
+ - lib/datadog/core/telemetry/configuration_value.rb
433
436
  - lib/datadog/core/telemetry/emitter.rb
434
437
  - lib/datadog/core/telemetry/event.rb
435
438
  - lib/datadog/core/telemetry/event/app_client_configuration_change.rb
@@ -1147,6 +1150,8 @@ files:
1147
1150
  - lib/datadog/tracing/transport/io/client.rb
1148
1151
  - lib/datadog/tracing/transport/io/response.rb
1149
1152
  - lib/datadog/tracing/transport/io/traces.rb
1153
+ - lib/datadog/tracing/transport/native.rb
1154
+ - lib/datadog/tracing/transport/native/response.rb
1150
1155
  - lib/datadog/tracing/transport/serializable_trace.rb
1151
1156
  - lib/datadog/tracing/transport/statistics.rb
1152
1157
  - lib/datadog/tracing/transport/trace_formatter.rb
@@ -1161,8 +1166,8 @@ licenses:
1161
1166
  - Apache-2.0
1162
1167
  metadata:
1163
1168
  allowed_push_host: https://rubygems.org
1164
- changelog_uri: https://github.com/DataDog/dd-trace-rb/blob/v2.39.0/CHANGELOG.md
1165
- source_code_uri: https://github.com/DataDog/dd-trace-rb/tree/v2.39.0
1169
+ changelog_uri: https://github.com/DataDog/dd-trace-rb/blob/v2.41.0/CHANGELOG.md
1170
+ source_code_uri: https://github.com/DataDog/dd-trace-rb/tree/v2.41.0
1166
1171
  post_install_message: 'JRuby support in the datadog gem is deprecated. Details: https://dtdg.co/jruby-deprecation'
1167
1172
  rdoc_options: []
1168
1173
  require_paths: