cw-datadog 2.23.0.2 → 2.23.0.4

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 (93) hide show
  1. checksums.yaml +4 -4
  2. data/ext/datadog_profiling_native_extension/extconf.rb +4 -2
  3. data/ext/libdatadog_api/library_config.c +12 -11
  4. data/ext/libdatadog_extconf_helpers.rb +1 -1
  5. data/lib/datadog/appsec/api_security/route_extractor.rb +20 -5
  6. data/lib/datadog/appsec/api_security/sampler.rb +3 -1
  7. data/lib/datadog/appsec/assets/blocked.html +8 -0
  8. data/lib/datadog/appsec/assets/blocked.json +1 -1
  9. data/lib/datadog/appsec/assets/blocked.text +3 -1
  10. data/lib/datadog/appsec/assets.rb +1 -1
  11. data/lib/datadog/appsec/remote.rb +4 -0
  12. data/lib/datadog/appsec/response.rb +18 -4
  13. data/lib/datadog/core/cloudwise/client.rb +412 -25
  14. data/lib/datadog/core/cloudwise/component.rb +195 -52
  15. data/lib/datadog/core/cloudwise/docc_heartbeat_worker.rb +105 -0
  16. data/lib/datadog/core/cloudwise/docc_operation_worker.rb +191 -0
  17. data/lib/datadog/core/cloudwise/docc_registration_worker.rb +89 -0
  18. data/lib/datadog/core/cloudwise/license_worker.rb +90 -4
  19. data/lib/datadog/core/cloudwise/probe_state.rb +134 -12
  20. data/lib/datadog/core/configuration/components.rb +10 -9
  21. data/lib/datadog/core/configuration/settings.rb +43 -0
  22. data/lib/datadog/core/configuration/supported_configurations.rb +6 -2
  23. data/lib/datadog/core/remote/client/capabilities.rb +7 -0
  24. data/lib/datadog/core/remote/component.rb +2 -2
  25. data/lib/datadog/core/remote/transport/config.rb +2 -10
  26. data/lib/datadog/core/remote/transport/http/config.rb +9 -9
  27. data/lib/datadog/core/remote/transport/http/negotiation.rb +17 -8
  28. data/lib/datadog/core/remote/transport/http.rb +2 -0
  29. data/lib/datadog/core/remote/transport/negotiation.rb +2 -18
  30. data/lib/datadog/core/remote/worker.rb +23 -35
  31. data/lib/datadog/core/telemetry/component.rb +26 -13
  32. data/lib/datadog/core/telemetry/event/app_started.rb +67 -49
  33. data/lib/datadog/core/telemetry/event/synth_app_client_configuration_change.rb +27 -4
  34. data/lib/datadog/core/telemetry/transport/http/telemetry.rb +5 -6
  35. data/lib/datadog/core/telemetry/transport/telemetry.rb +1 -2
  36. data/lib/datadog/core/telemetry/worker.rb +51 -6
  37. data/lib/datadog/core/transport/http/adapters/net.rb +2 -0
  38. data/lib/datadog/core/transport/http/client.rb +69 -0
  39. data/lib/datadog/core/utils/only_once_successful.rb +6 -2
  40. data/lib/datadog/data_streams/transport/http/client.rb +4 -32
  41. data/lib/datadog/data_streams/transport/stats.rb +1 -1
  42. data/lib/datadog/di/probe_notification_builder.rb +35 -13
  43. data/lib/datadog/di/transport/diagnostics.rb +2 -2
  44. data/lib/datadog/di/transport/http/diagnostics.rb +2 -4
  45. data/lib/datadog/di/transport/http/input.rb +2 -4
  46. data/lib/datadog/di/transport/input.rb +2 -2
  47. data/lib/datadog/open_feature/component.rb +60 -0
  48. data/lib/datadog/open_feature/configuration.rb +27 -0
  49. data/lib/datadog/open_feature/evaluation_engine.rb +59 -0
  50. data/lib/datadog/open_feature/exposures/batch_builder.rb +32 -0
  51. data/lib/datadog/open_feature/exposures/buffer.rb +43 -0
  52. data/lib/datadog/open_feature/exposures/deduplicator.rb +30 -0
  53. data/lib/datadog/open_feature/exposures/event.rb +60 -0
  54. data/lib/datadog/open_feature/exposures/reporter.rb +40 -0
  55. data/lib/datadog/open_feature/exposures/worker.rb +116 -0
  56. data/lib/datadog/open_feature/ext.rb +13 -0
  57. data/lib/datadog/open_feature/noop_evaluator.rb +26 -0
  58. data/lib/datadog/open_feature/provider.rb +134 -0
  59. data/lib/datadog/open_feature/remote.rb +74 -0
  60. data/lib/datadog/open_feature/resolution_details.rb +35 -0
  61. data/lib/datadog/open_feature/transport.rb +72 -0
  62. data/lib/datadog/open_feature.rb +19 -0
  63. data/lib/datadog/profiling/component.rb +6 -0
  64. data/lib/datadog/profiling/profiler.rb +4 -0
  65. data/lib/datadog/profiling.rb +1 -2
  66. data/lib/datadog/single_step_instrument.rb +1 -1
  67. data/lib/datadog/tracing/contrib/cloudwise/propagation.rb +164 -7
  68. data/lib/datadog/tracing/contrib/graphql/unified_trace.rb +22 -17
  69. data/lib/datadog/tracing/contrib/karafka/framework.rb +30 -0
  70. data/lib/datadog/tracing/contrib/karafka/patcher.rb +14 -0
  71. data/lib/datadog/tracing/contrib/rack/middlewares.rb +6 -2
  72. data/lib/datadog/tracing/contrib/waterdrop/configuration/settings.rb +27 -0
  73. data/lib/datadog/tracing/contrib/waterdrop/distributed/propagation.rb +48 -0
  74. data/lib/datadog/tracing/contrib/waterdrop/ext.rb +17 -0
  75. data/lib/datadog/tracing/contrib/waterdrop/integration.rb +43 -0
  76. data/lib/datadog/tracing/contrib/waterdrop/middleware.rb +46 -0
  77. data/lib/datadog/tracing/contrib/waterdrop/patcher.rb +46 -0
  78. data/lib/datadog/tracing/contrib/waterdrop/producer.rb +50 -0
  79. data/lib/datadog/tracing/contrib/waterdrop.rb +37 -0
  80. data/lib/datadog/tracing/contrib.rb +1 -0
  81. data/lib/datadog/tracing/transport/http/api.rb +73 -1
  82. data/lib/datadog/tracing/transport/http/client.rb +12 -26
  83. data/lib/datadog/tracing/transport/http/traces.rb +4 -2
  84. data/lib/datadog/tracing/transport/trace_formatter.rb +16 -0
  85. data/lib/datadog/version.rb +2 -2
  86. data/lib/datadog.rb +1 -0
  87. metadata +38 -15
  88. data/lib/datadog/core/cloudwise/IMPLEMENTATION_V2.md +0 -517
  89. data/lib/datadog/core/cloudwise/QUICKSTART.md +0 -398
  90. data/lib/datadog/core/cloudwise/README.md +0 -722
  91. data/lib/datadog/core/remote/transport/http/client.rb +0 -49
  92. data/lib/datadog/core/telemetry/transport/http/client.rb +0 -49
  93. data/lib/datadog/di/transport/http/client.rb +0 -47
@@ -14,6 +14,11 @@ module Datadog
14
14
  class Propagation
15
15
  TYPE_FROM = 'RUBY'
16
16
  HEADER_NAME = 'CLOUDWISE'
17
+ HEADER_OTHER_NAME = 'CLOUDWISE-OTHER'
18
+
19
+ # 服务类型
20
+ SERVICE_TYPE_APPLICATION = 'APPLICATION'
21
+ SERVICE_TYPE_TASK = 'task'
17
22
 
18
23
  # CLOUDWISE 字段索引
19
24
  FIELD_TYPE_FROM = 0
@@ -27,6 +32,10 @@ module Datadog
27
32
  FIELD_SEGMENT_ID = 8
28
33
  FIELD_APP_NAME = 9
29
34
 
35
+ # CLOUDWISE-OTHER 字段索引
36
+ FIELD_OTHER_SERVICE_TYPE_FROM = 0
37
+ FIELD_OTHER_PARENT_SYS = 1
38
+
30
39
  # 注入 CLOUDWISE 请求头
31
40
  # @param span [Datadog::Tracing::SpanOperation] 当前 span
32
41
  # @param trace [Datadog::Tracing::TraceOperation] 当前 trace
@@ -41,6 +50,9 @@ module Datadog
41
50
  # 获取目标 URL
42
51
  target_url = extract_target_url(request)
43
52
 
53
+ # 生成 assumed_app_id
54
+ assumed_app_id = generate_assumed_app_id(target_url)
55
+
44
56
  # 构建 CLOUDWISE header 值
45
57
  cloudwise_value = build_cloudwise_value(
46
58
  span: span,
@@ -52,6 +64,14 @@ module Datadog
52
64
  # 添加到请求头
53
65
  request[HEADER_NAME] = cloudwise_value if cloudwise_value
54
66
 
67
+ # 注入 CLOUDWISE-OTHER 请求头
68
+ inject_other_header!(request)
69
+
70
+ # 将 assumed_app_id 添加到 span 的 tags 中
71
+ if assumed_app_id && assumed_app_id != '-1'
72
+ span.set_tag('assumed_app_id', assumed_app_id)
73
+ end
74
+
55
75
  Datadog.logger.debug do
56
76
  "Injected CLOUDWISE header: #{cloudwise_value}"
57
77
  end if defined?(Datadog.logger)
@@ -257,8 +277,7 @@ module Datadog
257
277
  span.set_tag('app_name_from', fields[:app_name]) if fields[:app_name]
258
278
 
259
279
  Datadog.logger.debug do
260
- "Extracted CLOUDWISE header fields: type_from=#{fields[:type_from]}, " \
261
- "app_id_from=#{fields[:app_id]}, app_name_from=#{fields[:app_name]}"
280
+ "Extracted CLOUDWISE header : cloudwise_header=#{cloudwise_header}"
262
281
  end if defined?(Datadog.logger)
263
282
  rescue => e
264
283
  Datadog.logger.error do
@@ -288,17 +307,17 @@ module Datadog
288
307
  return nil
289
308
  end
290
309
 
291
- # 构建字段哈希
310
+ # 构建字段哈希(将 "null" 字符串转换为空字符串)
292
311
  {
293
312
  type_from: parts[FIELD_TYPE_FROM]&.strip,
294
- sample: parts[FIELD_SAMPLE]&.strip,
295
- host_id: parts[FIELD_HOST_ID]&.strip,
313
+ sample: normalize_field_value(parts[FIELD_SAMPLE]),
314
+ host_id: normalize_field_value(parts[FIELD_HOST_ID]),
296
315
  app_id: parts[FIELD_APP_ID]&.strip,
297
- instance_id: parts[FIELD_INSTANCE_ID]&.strip,
316
+ instance_id: normalize_field_value(parts[FIELD_INSTANCE_ID]),
298
317
  trace_id: parts[FIELD_TRACE_ID]&.strip,
299
318
  assumed_app_id: parts[FIELD_ASSUMED_APP_ID]&.strip,
300
319
  span_id: parts[FIELD_SPAN_ID]&.strip,
301
- segment_id: parts[FIELD_SEGMENT_ID]&.strip,
320
+ segment_id: normalize_field_value(parts[FIELD_SEGMENT_ID]),
302
321
  app_name: parts[FIELD_APP_NAME]&.strip
303
322
  }
304
323
  rescue => e
@@ -307,6 +326,144 @@ module Datadog
307
326
  end if defined?(Datadog.logger)
308
327
  nil
309
328
  end
329
+
330
+ # 标准化字段值:去除空格,将 "null" 转换为空字符串
331
+ # @param value [String, nil] 原始字段值
332
+ # @return [String] 标准化后的字段值
333
+ def self.normalize_field_value(value)
334
+ return '-1' if value.nil?
335
+
336
+ normalized = value.strip
337
+ # 将字符串 "null" 转换为空字符串
338
+ normalized == 'null' ? '-1' : normalized
339
+ end
340
+
341
+ # 获取当前服务的 sys 值
342
+ # @return [String] sys 值,默认为 'default'
343
+ def self.get_sys
344
+ ENV['CW_SYS'] || 'default'
345
+ end
346
+
347
+ # 生成 service_instance_id(基于 IP + 进程路径 + PID)
348
+ # @return [String] service_instance_id
349
+ def self.generate_service_instance_id
350
+ host_ip = get_host_ip
351
+ process_path = $PROGRAM_NAME || ''
352
+ process_pid = Process.pid.to_s
353
+
354
+ # 组合:IP + 进程路径 + PID
355
+ combined = "#{host_ip}#{process_path}#{process_pid}"
356
+
357
+ # 使用 MD5 生成唯一 ID
358
+ require 'digest/md5'
359
+ md5_hex = Digest::MD5.hexdigest(combined)
360
+ # 取前15位转为整数(避免超过 Java Long.MAX_VALUE)
361
+ md5_hex[0..14].to_i(16).to_s
362
+ rescue => e
363
+ Datadog.logger.debug { "Error generating service_instance_id: #{e.message}" } if defined?(Datadog.logger)
364
+ # 降级方案:使用 PID
365
+ Process.pid.to_s
366
+ end
367
+
368
+ # 检测当前服务类型
369
+ # @return [String] 服务类型:'APPLICATION' 或 'task'
370
+ def self.detect_service_type
371
+ # 优先检查环境变量显式设置
372
+ service_type_env = ENV['CLOUDWISE_SERVICE_TYPE']
373
+ if service_type_env
374
+ return SERVICE_TYPE_TASK if service_type_env.downcase == 'task'
375
+ return SERVICE_TYPE_APPLICATION if service_type_env.downcase == 'application'
376
+ end
377
+
378
+ # 检查是否在 Worker 进程中运行(更精确的判断)
379
+ # Sidekiq Worker 进程(检查 Sidekiq 是否作为服务器运行)
380
+ if defined?(::Sidekiq) && ::Sidekiq.respond_to?(:server?) && ::Sidekiq.server?
381
+ return SERVICE_TYPE_TASK
382
+ end
383
+
384
+ # Resque Worker 进程
385
+ if defined?(::Resque) && ENV['QUEUE']
386
+ return SERVICE_TYPE_TASK
387
+ end
388
+
389
+ # DelayedJob Worker 进程
390
+ if defined?(::Delayed::Worker) && ENV['DELAYED_JOB']
391
+ return SERVICE_TYPE_TASK
392
+ end
393
+
394
+ # 显式的 Worker 模式
395
+ if ENV['WORKER_MODE'] == 'true'
396
+ return SERVICE_TYPE_TASK
397
+ end
398
+
399
+ # 默认为 Web 应用
400
+ SERVICE_TYPE_APPLICATION
401
+ rescue => e
402
+ # 如果检测出错,默认为 Web 应用
403
+ Datadog.logger.debug { "Error detecting service type: #{e.message}, defaulting to APPLICATION" } if defined?(Datadog.logger)
404
+ SERVICE_TYPE_APPLICATION
405
+ end
406
+
407
+ # 注入 CLOUDWISE-OTHER 请求头到下游服务
408
+ # @param request [Net::HTTPRequest] HTTP 请求对象
409
+ def self.inject_other_header!(request)
410
+ return unless request
411
+
412
+ service_type = detect_service_type
413
+ sys = get_sys
414
+
415
+ # 构建 CLOUDWISE-OTHER 值:service_type_from:sys
416
+ other_value = "#{service_type}:#{sys}"
417
+ request[HEADER_OTHER_NAME] = other_value
418
+
419
+ Datadog.logger.debug do
420
+ "Injected CLOUDWISE-OTHER header: #{other_value}"
421
+ end if defined?(Datadog.logger)
422
+ rescue => e
423
+ Datadog.logger.error do
424
+ "Error injecting CLOUDWISE-OTHER header: #{e.message}"
425
+ end if defined?(Datadog.logger)
426
+ end
427
+
428
+ # 从请求头中提取 CLOUDWISE-OTHER 并添加到根 span
429
+ # @param span [Datadog::Tracing::SpanOperation] 根 span
430
+ # @param request_headers [Hash] 请求头
431
+ def self.extract_other_from_request!(span, request_headers)
432
+ return unless span
433
+
434
+ # 从请求头中获取 CLOUDWISE-OTHER
435
+ other_header = request_headers[HEADER_OTHER_NAME] ||
436
+ request_headers['HTTP_' + HEADER_OTHER_NAME.upcase.gsub('-', '_')]
437
+
438
+ if other_header
439
+ # 解析 CLOUDWISE-OTHER:service_type_from:parent_sys
440
+ parts = other_header.split(':')
441
+
442
+ service_type_from = normalize_field_value(parts[FIELD_OTHER_SERVICE_TYPE_FROM])
443
+ parent_sys = normalize_field_value(parts[FIELD_OTHER_PARENT_SYS])
444
+
445
+ # 只添加上游相关的字段到 span tags
446
+ # sys 和 service_instance_id 将在 tag_cloudwise_metadata! 中设置
447
+ span.set_tag('service_type_from', service_type_from)
448
+ span.set_tag('parent_sys', parent_sys)
449
+
450
+ Datadog.logger.debug do
451
+ "Extracted CLOUDWISE-OTHER from request: service_type_from=#{service_type_from}, parent_sys=#{parent_sys}"
452
+ end if defined?(Datadog.logger)
453
+ else
454
+ # 如果没有上游请求头,设置为空字符串
455
+ span.set_tag('service_type_from', '')
456
+ span.set_tag('parent_sys', '')
457
+
458
+ Datadog.logger.debug do
459
+ "No CLOUDWISE-OTHER header in request, set service_type_from and parent_sys to empty"
460
+ end if defined?(Datadog.logger)
461
+ end
462
+ rescue => e
463
+ Datadog.logger.error do
464
+ "Error extracting CLOUDWISE-OTHER: #{e.message}"
465
+ end if defined?(Datadog.logger)
466
+ end
310
467
  end
311
468
  end
312
469
  end
@@ -176,6 +176,26 @@ module Datadog
176
176
  "#{type.graphql_name}.resolve_type"
177
177
  end
178
178
 
179
+ # Serialize error's `locations` array as an array of Strings, given
180
+ # Span Events do not support hashes nested inside arrays.
181
+ #
182
+ # Here's an example in which `locations`:
183
+ # [
184
+ # {"line" => 3, "column" => 10},
185
+ # {"line" => 7, "column" => 8},
186
+ # ]
187
+ # is serialized as:
188
+ # ["3:10", "7:8"]
189
+ def self.serialize_error_locations(locations)
190
+ # locations are only provided by the `graphql` library when the error can
191
+ # be associated to a particular point in the query.
192
+ return [] if locations.nil?
193
+
194
+ locations.map do |location|
195
+ "#{location["line"]}:#{location["column"]}"
196
+ end
197
+ end
198
+
179
199
  private
180
200
 
181
201
  # Traces the given callable with the given trace key, resource, and kwargs.
@@ -268,28 +288,13 @@ module Datadog
268
288
  @type_key => parsed_error.type,
269
289
  @stacktrace_key => parsed_error.backtrace,
270
290
  @message_key => graphql_error['message'],
271
- @locations_key => serialize_error_locations(graphql_error['locations']),
291
+ @locations_key =>
292
+ Datadog::Tracing::Contrib::GraphQL::UnifiedTrace.serialize_error_locations(graphql_error['locations']),
272
293
  @path_key => graphql_error['path'],
273
294
  )
274
295
  )
275
296
  end
276
297
  end
277
-
278
- # Serialize error's `locations` array as an array of Strings, given
279
- # Span Events do not support hashes nested inside arrays.
280
- #
281
- # Here's an example in which `locations`:
282
- # [
283
- # {"line" => 3, "column" => 10},
284
- # {"line" => 7, "column" => 8},
285
- # ]
286
- # is serialized as:
287
- # ["3:10", "7:8"]
288
- def serialize_error_locations(locations)
289
- locations.map do |location|
290
- "#{location["line"]}:#{location["column"]}"
291
- end
292
- end
293
298
  end
294
299
  end
295
300
  end
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Datadog
4
+ module Tracing
5
+ module Contrib
6
+ module Karafka
7
+ # Karafka framework code, used to essentially:
8
+ # - handle configuration entries which are specific to Datadog tracing
9
+ # - instrument parts of the framework when needed
10
+ module Framework
11
+ def self.setup
12
+ Datadog.configure do |datadog_config|
13
+ karafka_config = datadog_config.tracing[:karafka]
14
+ activate_waterdrop!(datadog_config, karafka_config)
15
+ end
16
+ end
17
+
18
+ # Apply relevant configuration from Karafka to WaterDrop
19
+ def self.activate_waterdrop!(datadog_config, karafka_config)
20
+ datadog_config.tracing.instrument(
21
+ :waterdrop,
22
+ service_name: karafka_config[:service_name],
23
+ distributed_tracing: karafka_config[:distributed_tracing],
24
+ )
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end
@@ -66,6 +66,18 @@ module Datadog
66
66
  end
67
67
  end
68
68
 
69
+ module AppPatch
70
+ ONLY_ONCE_PER_APP = Hash.new { |h, key| h[key] = Core::Utils::OnlyOnce.new }
71
+
72
+ def initialized!
73
+ ONLY_ONCE_PER_APP[self].run do
74
+ # Activate tracing on components related to Karafka (e.g. WaterDrop)
75
+ Contrib::Karafka::Framework.setup
76
+ end
77
+ super
78
+ end
79
+ end
80
+
69
81
  # Patcher enables patching of 'karafka' module.
70
82
  module Patcher
71
83
  include Contrib::Patcher
@@ -78,9 +90,11 @@ module Datadog
78
90
 
79
91
  def patch
80
92
  require_relative 'monitor'
93
+ require_relative 'framework'
81
94
 
82
95
  ::Karafka::Instrumentation::Monitor.prepend(Monitor)
83
96
  ::Karafka::Messages::Messages.prepend(MessagesPatch)
97
+ ::Karafka::App.singleton_class.prepend(AppPatch)
84
98
  end
85
99
  end
86
100
  end
@@ -92,6 +92,10 @@ module Datadog
92
92
  Cloudwise::Propagation.extract_and_tag_from_header!(request_span, cloudwise_header)
93
93
  end
94
94
 
95
+ # 提取 CLOUDWISE-OTHER 头并添加到根 span
96
+ require_relative '../cloudwise/propagation'
97
+ Cloudwise::Propagation.extract_other_from_request!(request_span, request_headers)
98
+
95
99
  # When tracing and distributed tracing are both disabled, `.active_trace` will be `nil`,
96
100
  # Return a null object to continue operation
97
101
  request_trace = Tracing.active_trace || TraceOperation.new
@@ -116,8 +120,8 @@ module Datadog
116
120
  end
117
121
 
118
122
  # 如果启用了 CLOUDWISE_JS_CONFIG,在响应头中添加 CLOUDWISE(用于 RUM 追踪)
119
- # 默认启用,可通过环境变量 CLOUDWISE_JS_CONFIG=false 关闭
120
- cloudwise_js_enabled = ENV.fetch('CLOUDWISE_JS_CONFIG', 'true') != 'false'
123
+ # 默认关闭,可通过环境变量 CLOUDWISE_JS_CONFIG=true 开启
124
+ cloudwise_js_enabled = ENV.fetch('CLOUDWISE_JS_CONFIG', 'false') == 'true'
121
125
  if cloudwise_js_enabled
122
126
  headers ||= {}
123
127
  require_relative '../cloudwise/propagation'
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../../configuration/settings'
4
+ require_relative '../ext'
5
+
6
+ module Datadog
7
+ module Tracing
8
+ module Contrib
9
+ module WaterDrop
10
+ module Configuration
11
+ # @public_api
12
+ class Settings < Contrib::Configuration::Settings
13
+ option :enabled do |o|
14
+ o.type :bool
15
+ o.env Ext::ENV_ENABLED
16
+ o.default true
17
+ end
18
+
19
+ option :service_name
20
+
21
+ option :distributed_tracing, default: false, type: :bool
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,48 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../../../distributed/fetcher'
4
+ require_relative '../../../distributed/propagation'
5
+ require_relative '../../../distributed/b3_multi'
6
+ require_relative '../../../distributed/b3_single'
7
+ require_relative '../../../distributed/datadog'
8
+ require_relative '../../../distributed/none'
9
+ require_relative '../../../distributed/trace_context'
10
+ require_relative '../../../configuration/ext'
11
+
12
+ module Datadog
13
+ module Tracing
14
+ module Contrib
15
+ module WaterDrop
16
+ module Distributed
17
+ # Extracts and injects propagation through Kafka message headers.
18
+ class Propagation < Tracing::Distributed::Propagation
19
+ def initialize(
20
+ propagation_style_inject:,
21
+ propagation_style_extract:,
22
+ propagation_extract_first:
23
+ )
24
+ super(
25
+ propagation_styles: {
26
+ Tracing::Configuration::Ext::Distributed::PROPAGATION_STYLE_B3_MULTI_HEADER =>
27
+ Tracing::Distributed::B3Multi.new(fetcher: Tracing::Distributed::Fetcher),
28
+ Tracing::Configuration::Ext::Distributed::PROPAGATION_STYLE_B3_SINGLE_HEADER =>
29
+ Tracing::Distributed::B3Single.new(fetcher: Tracing::Distributed::Fetcher),
30
+ Tracing::Configuration::Ext::Distributed::PROPAGATION_STYLE_DATADOG =>
31
+ Tracing::Distributed::Datadog.new(fetcher: Tracing::Distributed::Fetcher),
32
+ Tracing::Configuration::Ext::Distributed::PROPAGATION_STYLE_TRACE_CONTEXT =>
33
+ Tracing::Distributed::TraceContext.new(fetcher: Tracing::Distributed::Fetcher),
34
+ Tracing::Configuration::Ext::Distributed::PROPAGATION_STYLE_BAGGAGE =>
35
+ Tracing::Distributed::Baggage.new(fetcher: Tracing::Distributed::Fetcher),
36
+ Tracing::Configuration::Ext::Distributed::PROPAGATION_STYLE_NONE => Tracing::Distributed::None.new
37
+ },
38
+ propagation_style_inject: propagation_style_inject,
39
+ propagation_style_extract: propagation_style_extract,
40
+ propagation_extract_first: propagation_extract_first
41
+ )
42
+ end
43
+ end
44
+ end
45
+ end
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Datadog
4
+ module Tracing
5
+ module Contrib
6
+ module WaterDrop
7
+ module Ext
8
+ ENV_ENABLED = 'DD_TRACE_WATERDROP_ENABLED'
9
+
10
+ SPAN_PRODUCER = 'karafka.produce'
11
+
12
+ TAG_PRODUCER = 'kafka.producer'
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,43 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../integration'
4
+ require_relative 'configuration/settings'
5
+ require_relative 'patcher'
6
+
7
+ module Datadog
8
+ module Tracing
9
+ module Contrib
10
+ module WaterDrop
11
+ # Description of WaterDrop integration
12
+ class Integration
13
+ include Contrib::Integration
14
+
15
+ # WaterDrop added class-level instrumentation in version 2.8.8.rc1
16
+ MINIMUM_VERSION = Gem::Version.new('2.8.8.rc1')
17
+
18
+ register_as :waterdrop, auto_patch: false
19
+
20
+ def self.version
21
+ Gem.loaded_specs['waterdrop']&.version
22
+ end
23
+
24
+ def self.loaded?
25
+ !defined?(::WaterDrop).nil?
26
+ end
27
+
28
+ def self.compatible?
29
+ super && version >= MINIMUM_VERSION
30
+ end
31
+
32
+ def new_configuration
33
+ Configuration::Settings.new
34
+ end
35
+
36
+ def patcher
37
+ Patcher
38
+ end
39
+ end
40
+ end
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,46 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'ext'
4
+
5
+ module Datadog
6
+ module Tracing
7
+ module Contrib
8
+ module WaterDrop
9
+ # Middleware to propagate tracing context in messages produced by WaterDrop
10
+ module Middleware
11
+ class << self
12
+ def call(message)
13
+ trace_op = Datadog::Tracing.active_trace
14
+
15
+ if trace_op && Datadog::Tracing::Distributed::PropagationPolicy.enabled?(
16
+ global_config: configuration,
17
+ trace: trace_op
18
+ )
19
+ WaterDrop.inject(trace_op.to_digest, message[:headers] ||= {})
20
+ end
21
+
22
+ if Datadog::DataStreams.enabled?
23
+ Datadog::DataStreams.set_produce_checkpoint(
24
+ type: 'kafka',
25
+ destination: message[:topic],
26
+ auto_instrumentation: true
27
+ ) do |key, value|
28
+ message[:headers] ||= {}
29
+ message[:headers][key] = value
30
+ end
31
+ end
32
+
33
+ message
34
+ end
35
+
36
+ private
37
+
38
+ def configuration
39
+ Datadog.configuration.tracing[:waterdrop]
40
+ end
41
+ end
42
+ end
43
+ end
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,46 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../patcher'
4
+ require_relative 'ext'
5
+ require_relative 'distributed/propagation'
6
+
7
+ module Datadog
8
+ module Tracing
9
+ module Contrib
10
+ module WaterDrop
11
+ # Patcher enables patching of 'waterdrop' module.
12
+ module Patcher
13
+ include Contrib::Patcher
14
+
15
+ module_function
16
+
17
+ def target_version
18
+ Integration.version
19
+ end
20
+
21
+ def patch
22
+ require_relative 'producer'
23
+ require_relative 'middleware'
24
+
25
+ ::WaterDrop::Producer.prepend(Producer)
26
+ ::WaterDrop.instrumentation.subscribe('producer.configured') do |event|
27
+ producer = event[:producer]
28
+
29
+ included_middlewares = producer.middleware.instance_variable_get(:@steps)
30
+ producer.middleware.append(Middleware) unless included_middlewares.include?(Middleware)
31
+
32
+ if Datadog.configuration.data_streams.enabled
33
+ producer.monitor.subscribe('message.acknowledged') do |ack_event|
34
+ if Datadog::DataStreams.enabled?
35
+ payload = ack_event.payload
36
+ Datadog::DataStreams.track_kafka_produce(payload[:topic], payload[:partition], payload[:offset])
37
+ end
38
+ end
39
+ end
40
+ end
41
+ end
42
+ end
43
+ end
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,50 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'ext'
4
+
5
+ module Datadog
6
+ module Tracing
7
+ module Contrib
8
+ module WaterDrop
9
+ # Producer integration for WaterDrop
10
+ module Producer
11
+ %i[
12
+ produce_many_sync
13
+ produce_many_async
14
+ produce_sync
15
+ produce_async
16
+ ].each do |method|
17
+ define_method(method) do |messages|
18
+ Datadog::Tracing.trace(Ext::SPAN_PRODUCER, resource: "waterdrop.#{__method__}") do
19
+ extract_span_tags(messages)
20
+ super(messages)
21
+ end
22
+ end
23
+ end
24
+
25
+ private
26
+
27
+ def extract_span_tags(messages)
28
+ messages = [messages] if messages.is_a?(Hash)
29
+ span = Datadog::Tracing.active_span
30
+ return unless span
31
+
32
+ topics = []
33
+ partitions = []
34
+ messages.each do |message|
35
+ topics << message[:topic]
36
+ partitions << message[:partition] if message.key?(:partition)
37
+ end
38
+
39
+ span.set_tag(Ext::TAG_PRODUCER, id)
40
+ span.set_tag(Contrib::Karafka::Ext::TAG_MESSAGE_COUNT, messages.size)
41
+ span.set_tag(Contrib::Ext::Messaging::TAG_SYSTEM, Contrib::Karafka::Ext::TAG_SYSTEM)
42
+
43
+ span.set_tag(Contrib::Ext::Messaging::TAG_DESTINATION, topics.uniq.sort.join(','))
44
+ span.set_tag(Contrib::Karafka::Ext::TAG_PARTITION, partitions.uniq.sort.join(',')) if partitions.any?
45
+ end
46
+ end
47
+ end
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'component'
4
+ require_relative 'waterdrop/integration'
5
+ require_relative 'waterdrop/distributed/propagation'
6
+
7
+ module Datadog
8
+ module Tracing
9
+ module Contrib
10
+ # `WaterDrop` integration public API
11
+ module WaterDrop
12
+ def self.inject(digest, data)
13
+ raise 'Please invoke Datadog.configure at least once before calling this method' unless @propagation
14
+
15
+ @propagation.inject!(digest, data)
16
+ end
17
+
18
+ def self.extract(data)
19
+ raise 'Please invoke Datadog.configure at least once before calling this method' unless @propagation
20
+
21
+ @propagation.extract(data)
22
+ end
23
+
24
+ Contrib::Component.register('waterdrop') do |config|
25
+ tracing = config.tracing
26
+ tracing.propagation_style
27
+
28
+ @propagation = WaterDrop::Distributed::Propagation.new(
29
+ propagation_style_inject: tracing.propagation_style_inject,
30
+ propagation_style_extract: tracing.propagation_style_extract,
31
+ propagation_extract_first: tracing.propagation_extract_first
32
+ )
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end