datadog 2.40.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 (117) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +85 -58
  3. data/ext/datadog_profiling_native_extension/collectors_stack.c +2 -2
  4. data/ext/datadog_profiling_native_extension/native_extension_helpers.rb +3 -3
  5. data/ext/datadog_profiling_native_extension/private_vm_api_access.c +26 -12
  6. data/ext/libdatadog_api/trace_exporter.c +409 -71
  7. data/ext/libdatadog_extconf_helpers.rb +1 -1
  8. data/lib/datadog/ai_guard/api_client.rb +1 -1
  9. data/lib/datadog/ai_guard/evaluation/request.rb +8 -8
  10. data/lib/datadog/ai_guard/evaluation.rb +10 -3
  11. data/lib/datadog/ai_guard/ext.rb +1 -1
  12. data/lib/datadog/appsec/actions_handler/serializable_backtrace.rb +21 -42
  13. data/lib/datadog/appsec/api_security/endpoint_collection/grape_route_serializer.rb +1 -1
  14. data/lib/datadog/appsec/api_security/endpoint_collection/rails_route_serializer.rb +1 -1
  15. data/lib/datadog/appsec/api_security/endpoint_collection/sinatra_route_serializer.rb +1 -1
  16. data/lib/datadog/appsec/configuration.rb +2 -2
  17. data/lib/datadog/appsec/context.rb +1 -1
  18. data/lib/datadog/appsec/contrib/active_record/instrumentation.rb +1 -1
  19. data/lib/datadog/appsec/contrib/aws_lambda/waf_addresses.rb +2 -2
  20. data/lib/datadog/appsec/contrib/devise/configuration.rb +1 -1
  21. data/lib/datadog/appsec/contrib/excon/ssrf_detection_middleware.rb +2 -2
  22. data/lib/datadog/appsec/contrib/faraday/ssrf_detection_middleware.rb +2 -2
  23. data/lib/datadog/appsec/contrib/graphql/gateway/watcher.rb +1 -1
  24. data/lib/datadog/appsec/contrib/rack/ext.rb +2 -2
  25. data/lib/datadog/appsec/contrib/rack/gateway/watcher.rb +2 -2
  26. data/lib/datadog/appsec/contrib/rails/gateway/watcher.rb +2 -2
  27. data/lib/datadog/appsec/contrib/rest_client/request_ssrf_detection_patch.rb +2 -2
  28. data/lib/datadog/appsec/contrib/sinatra/gateway/watcher.rb +2 -2
  29. data/lib/datadog/appsec/processor/rule_loader.rb +12 -12
  30. data/lib/datadog/appsec/response.rb +1 -1
  31. data/lib/datadog/appsec/route_normalizer/route_pattern.rb +3 -3
  32. data/lib/datadog/appsec/security_engine/engine.rb +3 -3
  33. data/lib/datadog/core/configuration/components.rb +2 -2
  34. data/lib/datadog/core/configuration/option_definition.rb +1 -1
  35. data/lib/datadog/core/configuration/options.rb +1 -1
  36. data/lib/datadog/core/diagnostics/environment_logger.rb +36 -1
  37. data/lib/datadog/core/metrics/helpers.rb +1 -1
  38. data/lib/datadog/core/metrics/options.rb +2 -2
  39. data/lib/datadog/core/process_discovery.rb +1 -1
  40. data/lib/datadog/core/remote/client.rb +1 -1
  41. data/lib/datadog/core/remote/configuration/repository.rb +2 -2
  42. data/lib/datadog/core/telemetry/configuration_value.rb +38 -0
  43. data/lib/datadog/core/telemetry/event/app_client_configuration_change.rb +3 -1
  44. data/lib/datadog/core/telemetry/event/app_endpoints_loaded.rb +1 -1
  45. data/lib/datadog/core/telemetry/event/app_started.rb +5 -24
  46. data/lib/datadog/core/telemetry/event/generate_metrics.rb +1 -1
  47. data/lib/datadog/core/telemetry/event/log.rb +2 -2
  48. data/lib/datadog/core/telemetry/metric.rb +2 -2
  49. data/lib/datadog/core/telemetry/request.rb +2 -2
  50. data/lib/datadog/core/transport/http.rb +1 -1
  51. data/lib/datadog/core/transport/transport.rb +1 -1
  52. data/lib/datadog/core/utils/at_fork_monkey_patch.rb +127 -43
  53. data/lib/datadog/data_streams/processor.rb +14 -14
  54. data/lib/datadog/di/el/compiler.rb +23 -0
  55. data/lib/datadog/di/el/expression.rb +8 -5
  56. data/lib/datadog/di/instrumenter.rb +5 -4
  57. data/lib/datadog/di/probe_builder.rb +5 -4
  58. data/lib/datadog/di/probe_notification_builder.rb +2 -2
  59. data/lib/datadog/di/serializer.rb +29 -4
  60. data/lib/datadog/error_tracking/component.rb +1 -1
  61. data/lib/datadog/kit/appsec/events/v2.rb +2 -2
  62. data/lib/datadog/open_feature/exposures/batch_builder.rb +1 -1
  63. data/lib/datadog/open_feature/exposures/event.rb +5 -5
  64. data/lib/datadog/profiling/collectors/info.rb +1 -1
  65. data/lib/datadog/profiling/tasks/exec.rb +1 -1
  66. data/lib/datadog/symbol_database/extractor.rb +6 -6
  67. data/lib/datadog/tracing/configuration/dynamic/option.rb +4 -0
  68. data/lib/datadog/tracing/configuration/dynamic.rb +5 -3
  69. data/lib/datadog/tracing/contrib/action_cable/events.rb +1 -1
  70. data/lib/datadog/tracing/contrib/action_mailer/events.rb +1 -1
  71. data/lib/datadog/tracing/contrib/action_pack/action_controller/instrumentation.rb +2 -2
  72. data/lib/datadog/tracing/contrib/action_view/events.rb +1 -1
  73. data/lib/datadog/tracing/contrib/active_model_serializers/events.rb +1 -1
  74. data/lib/datadog/tracing/contrib/active_record/configuration/resolver.rb +1 -1
  75. data/lib/datadog/tracing/contrib/active_record/events.rb +1 -1
  76. data/lib/datadog/tracing/contrib/active_support/cache/events/cache.rb +2 -2
  77. data/lib/datadog/tracing/contrib/aws/ext.rb +1 -1
  78. data/lib/datadog/tracing/contrib/aws/services.rb +1 -1
  79. data/lib/datadog/tracing/contrib/elasticsearch/quantize.rb +1 -1
  80. data/lib/datadog/tracing/contrib/ext.rb +3 -3
  81. data/lib/datadog/tracing/contrib/grpc/datadog_interceptor/client.rb +1 -1
  82. data/lib/datadog/tracing/contrib/grpc/datadog_interceptor/server.rb +1 -1
  83. data/lib/datadog/tracing/contrib/grpc/distributed/propagation.rb +1 -1
  84. data/lib/datadog/tracing/contrib/kafka/events.rb +1 -1
  85. data/lib/datadog/tracing/contrib/karafka/distributed/propagation.rb +1 -1
  86. data/lib/datadog/tracing/contrib/mongodb/parsers.rb +1 -1
  87. data/lib/datadog/tracing/contrib/opensearch/ext.rb +1 -1
  88. data/lib/datadog/tracing/contrib/opensearch/quantize.rb +1 -1
  89. data/lib/datadog/tracing/contrib/patchable.rb +1 -1
  90. data/lib/datadog/tracing/contrib/patcher.rb +1 -1
  91. data/lib/datadog/tracing/contrib/que/tracer.rb +1 -1
  92. data/lib/datadog/tracing/contrib/racecar/events.rb +1 -1
  93. data/lib/datadog/tracing/contrib/rack/configuration/settings.rb +1 -1
  94. data/lib/datadog/tracing/contrib/rack/header_tagging.rb +1 -1
  95. data/lib/datadog/tracing/contrib/rack/patcher.rb +1 -1
  96. data/lib/datadog/tracing/contrib/rack/trace_proxy_middleware.rb +1 -1
  97. data/lib/datadog/tracing/contrib/redis/configuration/resolver.rb +1 -1
  98. data/lib/datadog/tracing/contrib/redis/quantize.rb +36 -2
  99. data/lib/datadog/tracing/contrib/redis/trace_middleware.rb +9 -3
  100. data/lib/datadog/tracing/contrib/sequel/utils.rb +1 -1
  101. data/lib/datadog/tracing/contrib/sidekiq/distributed/propagation.rb +1 -1
  102. data/lib/datadog/tracing/contrib/sinatra/configuration/settings.rb +1 -1
  103. data/lib/datadog/tracing/contrib/sneakers/tracer.rb +1 -1
  104. data/lib/datadog/tracing/contrib/utils/quantization/hash_formatter.rb +1 -1
  105. data/lib/datadog/tracing/contrib/waterdrop/distributed/propagation.rb +1 -1
  106. data/lib/datadog/tracing/correlation.rb +2 -2
  107. data/lib/datadog/tracing/diagnostics/environment_logger.rb +2 -2
  108. data/lib/datadog/tracing/distributed/baggage.rb +1 -2
  109. data/lib/datadog/tracing/metadata/tagging.rb +1 -1
  110. data/lib/datadog/tracing/remote.rb +2 -2
  111. data/lib/datadog/tracing/span.rb +1 -1
  112. data/lib/datadog/tracing/span_operation.rb +1 -1
  113. data/lib/datadog/tracing/transport/io/traces.rb +1 -1
  114. data/lib/datadog/tracing/transport/native.rb +78 -54
  115. data/lib/datadog/tracing/writer.rb +1 -1
  116. data/lib/datadog/version.rb +1 -1
  117. metadata +6 -5
@@ -70,7 +70,7 @@ module Datadog
70
70
  module Patcher
71
71
  PATCHERS = [
72
72
  MiddlewarePatcher,
73
- MiddlewareNamePatcher
73
+ MiddlewareNamePatcher,
74
74
  ].freeze
75
75
 
76
76
  module_function
@@ -27,7 +27,7 @@ module Datadog
27
27
  options = {
28
28
  service: configuration[:web_service_name],
29
29
  start_time: start_time,
30
- type: Tracing::Metadata::Ext::HTTP::TYPE_PROXY
30
+ type: Tracing::Metadata::Ext::HTTP::TYPE_PROXY,
31
31
  }
32
32
 
33
33
  request_span = Tracing.trace(Ext::SPAN_HTTP_PROXY_REQUEST, **options)
@@ -34,7 +34,7 @@ module Datadog
34
34
  host: hash[:host],
35
35
  port: hash[:port],
36
36
  db: hash[:db],
37
- scheme: hash[:scheme]
37
+ scheme: hash[:scheme],
38
38
  }
39
39
  end
40
40
 
@@ -15,6 +15,9 @@ module Datadog
15
15
 
16
16
  AUTH_COMMANDS = %w[AUTH auth].freeze
17
17
 
18
+ CONNECTION_SETUP_COMMANDS = Set.new(%w[HELLO]).freeze
19
+ CLIENT_CONNECTION_SETUP_SUBCOMMANDS = Set.new(%w[SETINFO SETNAME]).freeze
20
+
18
21
  MULTI_VERB_COMMANDS = Set.new(
19
22
  %w[
20
23
  ACL
@@ -40,7 +43,7 @@ module Datadog
40
43
 
41
44
  def format_command_args(command_args)
42
45
  command_args = resolve_command_args(command_args)
43
- return "AUTH ?" if auth_command?(command_args)
46
+ return "AUTH ?" if auth_command?(command_args) || hello_auth_command?(command_args)
44
47
 
45
48
  verb, *args = command_args.map { |x| format_arg(x) }
46
49
  Core::Utils.truncate("#{verb.upcase} #{args.join(" ")}", CMD_MAX_LEN, TOO_LONG_MARK)
@@ -51,6 +54,8 @@ module Datadog
51
54
 
52
55
  return get_verb(command_args.first) if command_args.first.is_a?(Array)
53
56
 
57
+ return "AUTH" if hello_auth_command?(command_args)
58
+
54
59
  verb = command_args.first.to_s.upcase
55
60
  return verb unless MULTI_VERB_COMMANDS.include?(verb) && command_args[1]
56
61
 
@@ -64,6 +69,35 @@ module Datadog
64
69
  AUTH_COMMANDS.include?(verb)
65
70
  end
66
71
 
72
+ # Identifies a RESP3 `HELLO ... AUTH user pass` handshake command, which embeds
73
+ # credentials the same way a standalone `AUTH` command does and must be redacted
74
+ # (and kept visible as its own span) rather than treated as a silent connection
75
+ # bootstrap command.
76
+ def hello_auth_command?(command_args)
77
+ return false unless command_args.is_a?(Array) && !command_args.empty?
78
+
79
+ # `redis-client` always emits this as `["HELLO", "3", "AUTH", username, password]` — check
80
+ # the fixed "AUTH" keyword position only, never the username/password values themselves,
81
+ # since those are untrusted, possibly-binary bulk strings that `#upcase` can raise on.
82
+ return false if command_args.length < 3
83
+ command_args[0].to_s.upcase == "HELLO" && command_args[2].to_s.upcase == "AUTH"
84
+ end
85
+
86
+ # Identifies protocol-level connection bootstrap commands (`HELLO`, `CLIENT SETINFO`,
87
+ # `CLIENT SETNAME`) that `redis-client` sends as part of its per-connection handshake,
88
+ # so they can be excluded from pipeline resource naming. A `HELLO` carrying `AUTH`
89
+ # credentials is excluded from this so authentication remains visible as its own span.
90
+ def connection_setup_command?(command_args)
91
+ command_args = resolve_command_args(command_args)
92
+ return false unless command_args.is_a?(Array) && !command_args.empty?
93
+ return false if hello_auth_command?(command_args)
94
+
95
+ verb = command_args.first.to_s.upcase
96
+ return true if CONNECTION_SETUP_COMMANDS.include?(verb)
97
+
98
+ verb == "CLIENT" && CLIENT_CONNECTION_SETUP_SUBCOMMANDS.include?(command_args[1].to_s.upcase)
99
+ end
100
+
67
101
  # Unwraps command array when Redis is called with the following syntax:
68
102
  # redis.call([:cmd, 'arg1', ...])
69
103
  def resolve_command_args(command_args)
@@ -72,7 +106,7 @@ module Datadog
72
106
  command_args
73
107
  end
74
108
 
75
- private_class_method :auth_command?, :resolve_command_args
109
+ private_class_method :auth_command?, :hello_auth_command?, :resolve_command_args
76
110
  end
77
111
  end
78
112
  end
@@ -37,12 +37,18 @@ module Datadog
37
37
  end
38
38
 
39
39
  def call_pipelined(client, commands, service_name, command_args)
40
+ # `redis-client` sends its per-connection handshake (`HELLO`, `CLIENT SETINFO`/`SETNAME`)
41
+ # as a pipeline through this same code path. Excluding those from resource naming keeps
42
+ # the resource focused on application-issued commands (e.g. a lone `SELECT` prelude).
43
+ traced_commands = commands.reject { |c| Contrib::Redis::Quantize.connection_setup_command?(c) }
44
+ return yield if traced_commands.empty? && !commands.empty?
45
+
40
46
  Tracing.trace(Redis::Ext::SPAN_COMMAND, type: Redis::Ext::TYPE, service: service_name) do |span|
41
47
  span.set_tag(Tracing::Metadata::Ext::TAG_SVC_SRC, Redis::Ext::TAG_COMPONENT)
42
- raw_command = get_pipeline_commands(commands, true)
43
- span.resource = command_args ? raw_command : get_pipeline_commands(commands, false)
48
+ raw_command = get_pipeline_commands(traced_commands, true)
49
+ span.resource = command_args ? raw_command : get_pipeline_commands(traced_commands, false)
44
50
 
45
- span.set_metric Contrib::Redis::Ext::METRIC_PIPELINE_LEN, commands.length
51
+ span.set_metric Contrib::Redis::Ext::METRIC_PIPELINE_LEN, traced_commands.length
46
52
 
47
53
  Contrib::Redis::Tags.set_common_tags(client, span, raw_command)
48
54
 
@@ -84,7 +84,7 @@ module Datadog
84
84
  query: sql,
85
85
  prepared_name: prepared_name,
86
86
  database: db_opts[:database],
87
- host: db_opts[:host]
87
+ host: db_opts[:host],
88
88
  }
89
89
  end
90
90
 
@@ -35,7 +35,7 @@ module Datadog
35
35
  Tracing::Distributed::TraceContext.new(fetcher: Tracing::Distributed::Fetcher),
36
36
  Tracing::Configuration::Ext::Distributed::PROPAGATION_STYLE_BAGGAGE =>
37
37
  Tracing::Distributed::Baggage.new(fetcher: Tracing::Distributed::Fetcher),
38
- Tracing::Configuration::Ext::Distributed::PROPAGATION_STYLE_NONE => Tracing::Distributed::None.new
38
+ Tracing::Configuration::Ext::Distributed::PROPAGATION_STYLE_NONE => Tracing::Distributed::None.new,
39
39
  },
40
40
  propagation_style_inject: propagation_style_inject,
41
41
  propagation_style_extract: propagation_style_extract,
@@ -12,7 +12,7 @@ module Datadog
12
12
  # @public_api
13
13
  class Settings < Contrib::Configuration::Settings
14
14
  DEFAULT_HEADERS = {
15
- response: %w[Content-Type X-Request-ID]
15
+ response: %w[Content-Type X-Request-ID],
16
16
  }.freeze
17
17
 
18
18
  option :enabled do |o|
@@ -18,7 +18,7 @@ module Datadog
18
18
  trace_options = {
19
19
  service: configuration[:service_name],
20
20
  type: Tracing::Metadata::Ext::AppTypes::TYPE_WORKER,
21
- on_error: configuration[:on_error]
21
+ on_error: configuration[:on_error],
22
22
  }
23
23
 
24
24
  Tracing.trace(Ext::SPAN_JOB, **trace_options) do |request_span|
@@ -15,7 +15,7 @@ module Datadog
15
15
  DEFAULT_OPTIONS = { # steep:ignore IncompatibleAssignment
16
16
  exclude: EXCLUDE_KEYS,
17
17
  show: SHOW_KEYS,
18
- placeholder: PLACEHOLDER
18
+ placeholder: PLACEHOLDER,
19
19
  }.freeze
20
20
 
21
21
  module_function
@@ -34,7 +34,7 @@ module Datadog
34
34
  Tracing::Distributed::TraceContext.new(fetcher: Tracing::Distributed::Fetcher),
35
35
  Tracing::Configuration::Ext::Distributed::PROPAGATION_STYLE_BAGGAGE =>
36
36
  Tracing::Distributed::Baggage.new(fetcher: Tracing::Distributed::Fetcher),
37
- Tracing::Configuration::Ext::Distributed::PROPAGATION_STYLE_NONE => Tracing::Distributed::None.new
37
+ Tracing::Configuration::Ext::Distributed::PROPAGATION_STYLE_NONE => Tracing::Distributed::None.new,
38
38
  },
39
39
  propagation_style_inject: propagation_style_inject,
40
40
  propagation_style_extract: propagation_style_extract,
@@ -51,9 +51,9 @@ module Datadog
51
51
  service: service.to_s,
52
52
  version: version.to_s,
53
53
  trace_id: trace_id.to_s,
54
- span_id: span_id.to_s
54
+ span_id: span_id.to_s,
55
55
  },
56
- ddsource: Core::Logging::Ext::DD_SOURCE
56
+ ddsource: Core::Logging::Ext::DD_SOURCE,
57
57
  }
58
58
  end
59
59
 
@@ -47,7 +47,7 @@ module Datadog
47
47
 
48
48
  def collect_errors!(responses)
49
49
  {
50
- agent_error: agent_error(responses)
50
+ agent_error: agent_error(responses),
51
51
  }
52
52
  end
53
53
 
@@ -114,7 +114,7 @@ module Datadog
114
114
  {
115
115
  name: rule.matcher.name,
116
116
  service: rule.matcher.service,
117
- sample_rate: rule.sampler.sample_rate(nil)
117
+ sample_rate: rule.sampler.sample_rate(nil),
118
118
  }
119
119
  end.compact
120
120
  end
@@ -231,8 +231,7 @@ module Datadog
231
231
  tags = {}
232
232
 
233
233
  baggage_tag_keys.each do |key, _|
234
- # Steep: https://github.com/soutaro/steep/issues/2031
235
- value = baggage[key] # steep:ignore ArgumentTypeMismatch
234
+ value = baggage[key]
236
235
  next if value.nil? || value.empty?
237
236
 
238
237
  tags["baggage.#{key}"] = value
@@ -21,7 +21,7 @@ module Datadog
21
21
  Ext::Distributed::TAG_ORIGIN => true,
22
22
  Core::Environment::Ext::TAG_VERSION => true,
23
23
  Ext::HTTP::TAG_STATUS_CODE => true,
24
- Ext::NET::TAG_HOSTNAME => true
24
+ Ext::NET::TAG_HOSTNAME => true,
25
25
  }.freeze
26
26
 
27
27
  # Return the tag with the given key, nil if it doesn't exist.
@@ -37,9 +37,9 @@ module Datadog
37
37
  # Guard for RBS/Steep
38
38
  raise "option is a #{option.class}, expected Option" unless option.is_a?(Configuration::Dynamic::Option)
39
39
 
40
- option.call(value)
40
+ telemetry_value = option.call(value)
41
41
 
42
- [env_var, value]
42
+ [env_var, telemetry_value]
43
43
  end
44
44
 
45
45
  if (di_enabled = lib_config["dynamic_instrumentation_enabled"]) != nil # rubocop:disable Style/NonNilCheck
@@ -157,7 +157,7 @@ module Datadog
157
157
  span_id: @id,
158
158
  trace_id: @trace_id,
159
159
  type: @type,
160
- span_links: @links.map(&:to_hash)
160
+ span_links: @links.map(&:to_hash),
161
161
  }
162
162
 
163
163
  if stopped?
@@ -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?
@@ -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
@@ -65,6 +65,10 @@ module Datadog
65
65
  # fork-safety note below.
66
66
  @send_mutex = Mutex.new
67
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
+
68
72
  url = agent_settings.url
69
73
  tracer_version = tracer_version_string
70
74
  language = Core::Environment::Ext::LANG
@@ -106,8 +110,15 @@ module Datadog
106
110
  # also tears down and replaces the runtime, so it must not run while
107
111
  # a send is still using that runtime.
108
112
  #
109
- # `@send_mutex` serializes sends (see #send_traces) and is held
110
- # across the fork. The `:before` hook first calls
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
111
122
  # `_native_before_fork` to pause the runtime, THEN locks the mutex.
112
123
  # This ordering minimizes the serialized section and is safe to run
113
124
  # concurrently with an in-flight send: `block_on` (used by
@@ -136,25 +147,29 @@ module Datadog
136
147
  # hooks when it goes away (see #close and the finalizer): otherwise
137
148
  # the closures keep the exporter -- and its runtime threads -- alive
138
149
  # forever, and every later fork runs them against every
139
- # historically-created exporter. The closures intentionally capture
140
- # only the `exporter`/`send_mutex` locals (NOT `self`), so the
141
- # Transport stays GC-eligible and its finalizer can fire even when
142
- # #close was not called explicitly.
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.
143
153
  send_mutex = @send_mutex
144
- before_hook = Core::Utils::AtForkMonkeyPatch.at_fork(:before) do
145
- # Pause the runtime first (safe to run concurrently with an
146
- # in-flight send), then drain by locking the mutex. The lock must
147
- # happen even if `_native_before_fork` raises, so the in-flight
148
- # send has returned (dropping the runtime's last `Arc`) before the
149
- # fork. Held across the fork; released in :parent/:child.
150
- exporter._native_before_fork
151
- rescue => e
152
- Datadog.logger.warn { "Native transport before-fork preparation failed; traces may not be sent to Datadog: #{e.class}: #{e.message}" }
153
- ensure
154
- send_mutex.lock
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
155
170
  end
156
- parent_hook = Core::Utils::AtForkMonkeyPatch.at_fork(:parent) do
157
- exporter._native_after_fork_in_parent
171
+ parent_hook = proc do
172
+ exporter._native_after_fork_in_parent if fork_mutex.owned?
158
173
  rescue => e
159
174
  Datadog.logger.warn { "Native transport after-fork reset failed; traces may not be sent to Datadog: #{e.class}: #{e.message}" }
160
175
  ensure
@@ -164,9 +179,10 @@ module Datadog
164
179
  # between `_native_before_fork` and the lock). Released even if the
165
180
  # native call raised, so a failure can't leave the mutex locked.
166
181
  send_mutex.unlock if send_mutex.owned?
182
+ fork_mutex.unlock if fork_mutex.owned?
167
183
  end
168
- child_hook = Core::Utils::AtForkMonkeyPatch.at_fork(:child) do
169
- exporter._native_after_fork_in_child
184
+ child_hook = proc do
185
+ exporter._native_after_fork_in_child if fork_mutex.owned?
170
186
  rescue => e
171
187
  Datadog.logger.warn { "Native transport after-fork reset failed; traces may not be sent to Datadog: #{e.class}: #{e.message}" }
172
188
  ensure
@@ -174,61 +190,70 @@ module Datadog
174
190
  # lone survivor in the child); the guard only covers `:before`
175
191
  # being interrupted mid-lock.
176
192
  send_mutex.unlock if send_mutex.owned?
193
+ fork_mutex.unlock if fork_mutex.owned?
177
194
  end
178
195
 
179
- @fork_hooks = {before: before_hook, parent: parent_hook, child: child_hook}
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)
180
202
 
181
203
  # Fallback so a dropped (un-#close'd) transport doesn't leak its
182
204
  # global fork hooks (and, through them, the exporter/runtime). The
183
- # finalizer proc is built by a class method capturing ONLY the
205
+ # removal proc is built by a class method capturing ONLY the
184
206
  # stage->block handles, never `self` -- a finalizer that closed over
185
207
  # the object it is attached to would keep that object reachable and
186
- # never run.
187
- ObjectSpace.define_finalizer(self, self.class.send(:finalizer_for, @fork_hooks))
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
+ )
188
214
  end
189
215
 
190
216
  # Deregister this transport's process-global fork hooks and release the
191
217
  # native exporter so its runtime can shut down. Idempotent: safe to
192
218
  # call multiple times and safe to call after the finalizer has run.
193
219
  def close
194
- fork_hooks = @fork_hooks
195
- @fork_hooks = nil
196
- return if fork_hooks.nil?
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
197
228
 
198
229
  fork_hooks.each do |stage, block|
199
230
  Core::Utils::AtForkMonkeyPatch.remove_at_fork(stage, block)
200
231
  end
201
232
 
202
- # Drop our reference to the exporter so that, once the hooks above no
203
- # longer pin it, it can be collected and its runtime shut down
204
- # cleanly in this (parent) process.
205
- @exporter = nil
206
-
207
233
  # The finalizer only exists to deregister the hooks for a transport
208
234
  # dropped without #close. We have just done that, so remove it;
209
- # otherwise its captured hook blocks keep the exporter (and its
210
- # runtime threads) alive until this still-reachable transport is
211
- # itself collected.
235
+ # otherwise its closed-over hook blocks keep the exporter alive until
236
+ # this transport is itself collected.
212
237
  ObjectSpace.undefine_finalizer(self)
213
238
 
214
239
  nil
215
240
  end
216
241
 
217
- # Builds the finalizer proc for a transport's fork hooks.
242
+ # Builds the proc that deregisters a transport's fork hooks.
218
243
  #
219
244
  # Defined as a class method so the proc closes over ONLY the
220
245
  # stage->block handles passed in and never over a Transport instance
221
246
  # (which would keep the instance reachable and prevent the finalizer
222
247
  # from ever running). Removing the hooks unpins the exporter captured
223
248
  # by those closures, allowing it (and its runtime) to be freed.
224
- def self.finalizer_for(fork_hooks)
249
+ def self.fork_hooks_remover(fork_hooks)
225
250
  proc do
226
251
  fork_hooks.each do |stage, block|
227
252
  Core::Utils::AtForkMonkeyPatch.remove_at_fork(stage, block)
228
253
  end
229
254
  end
230
255
  end
231
- private_class_method :finalizer_for
256
+ private_class_method :fork_hooks_remover
232
257
 
233
258
  # Send a list of traces to the agent.
234
259
  #
@@ -240,12 +265,6 @@ module Datadog
240
265
  def send_traces(traces)
241
266
  return [] if traces.empty?
242
267
 
243
- # A closed transport has released its exporter; there is nothing to
244
- # send through. Raising here is caught below and surfaced as an
245
- # InternalErrorResponse, matching the other failure paths.
246
- exporter = @exporter
247
- raise "Native transport has been closed" if exporter.nil?
248
-
249
268
  # Apply trace-level tags to root spans (same as the HTTP transport)
250
269
  traces.each { |trace| TraceFormatter.format!(trace) }
251
270
 
@@ -253,9 +272,8 @@ module Datadog
253
272
  # Each trace segment becomes one inner array (one trace chunk).
254
273
  chunks = traces.map(&:spans)
255
274
 
256
- # The native exporter only serializes scalar fields plus meta and
257
- # metrics; span events, span links, and meta_struct are not yet
258
- # converted and would be dropped. Warn (once) so the loss is visible.
275
+ # Span events and span links are not yet converted and would be
276
+ # dropped. Warn (once) so the loss is visible.
259
277
  warn_unsupported_fields!(chunks)
260
278
 
261
279
  # Serialize the native send and hold the mutex across it so a
@@ -263,7 +281,14 @@ module Datadog
263
281
  # (and `_native_before_fork` cannot tear down the runtime mid-send).
264
282
  # `Mutex#synchronize` releases on exception / `rb_jump_tag` via its
265
283
  # ensure, so interrupt propagation stays correct.
266
- responses = @send_mutex.synchronize { exporter._native_send_traces(chunks) }
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
267
292
 
268
293
  # Update statistics from the response
269
294
  responses.each { |response| update_stats_from_response!(response) }
@@ -278,10 +303,10 @@ module Datadog
278
303
  private
279
304
 
280
305
  # Warn, at most once per transport, when a batch contains span fields
281
- # the native exporter does not yet convert (span events, span links,
282
- # meta_struct). These are silently dropped by the native path; full
283
- # support is tracked separately. The check is cheap: the fields are
284
- # already-materialized collections on each Span.
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.
285
310
  def warn_unsupported_fields!(chunks)
286
311
  return if @unsupported_fields_warned
287
312
 
@@ -290,7 +315,6 @@ module Datadog
290
315
  spans.each do |span|
291
316
  unsupported << "span events" if span.events.any?
292
317
  unsupported << "span links" if span.links.any?
293
- unsupported << "meta_struct" unless span.metastruct.to_h.empty?
294
318
  end
295
319
  end
296
320
  return if unsupported.empty?
@@ -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
 
@@ -3,7 +3,7 @@
3
3
  module Datadog
4
4
  module VERSION
5
5
  MAJOR = 2
6
- MINOR = 40
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.40.0
4
+ version: 2.41.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Datadog, Inc.
@@ -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
@@ -432,6 +432,7 @@ files:
432
432
  - lib/datadog/core/tag_builder.rb
433
433
  - lib/datadog/core/tag_normalizer.rb
434
434
  - lib/datadog/core/telemetry/component.rb
435
+ - lib/datadog/core/telemetry/configuration_value.rb
435
436
  - lib/datadog/core/telemetry/emitter.rb
436
437
  - lib/datadog/core/telemetry/event.rb
437
438
  - lib/datadog/core/telemetry/event/app_client_configuration_change.rb
@@ -1165,8 +1166,8 @@ licenses:
1165
1166
  - Apache-2.0
1166
1167
  metadata:
1167
1168
  allowed_push_host: https://rubygems.org
1168
- changelog_uri: https://github.com/DataDog/dd-trace-rb/blob/v2.40.0/CHANGELOG.md
1169
- source_code_uri: https://github.com/DataDog/dd-trace-rb/tree/v2.40.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
1170
1171
  post_install_message: 'JRuby support in the datadog gem is deprecated. Details: https://dtdg.co/jruby-deprecation'
1171
1172
  rdoc_options: []
1172
1173
  require_paths: