cw-datadog 2.23.0.4 → 2.23.0.5

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 (46) hide show
  1. checksums.yaml +4 -4
  2. data/ext/libdatadog_api/feature_flags.c +554 -0
  3. data/ext/libdatadog_api/feature_flags.h +5 -0
  4. data/ext/libdatadog_api/init.c +2 -0
  5. data/lib/datadog/core/cloudwise/client.rb +99 -4
  6. data/lib/datadog/core/cloudwise/component.rb +55 -13
  7. data/lib/datadog/core/cloudwise/docc_operation_worker.rb +11 -1
  8. data/lib/datadog/core/cloudwise/license_worker.rb +7 -0
  9. data/lib/datadog/core/cloudwise/time_sync_worker.rb +200 -0
  10. data/lib/datadog/core/configuration/settings.rb +12 -0
  11. data/lib/datadog/core/configuration/supported_configurations.rb +14 -0
  12. data/lib/datadog/core/environment/ext.rb +6 -0
  13. data/lib/datadog/core/environment/process.rb +79 -0
  14. data/lib/datadog/core/feature_flags.rb +61 -0
  15. data/lib/datadog/core/tag_normalizer.rb +84 -0
  16. data/lib/datadog/core/transport/http/adapters/net.rb +8 -0
  17. data/lib/datadog/core/utils/array.rb +29 -0
  18. data/lib/datadog/core/utils.rb +2 -0
  19. data/lib/datadog/data_streams/processor.rb +1 -1
  20. data/lib/datadog/di/transport/http.rb +6 -2
  21. data/lib/datadog/di/transport/input.rb +62 -2
  22. data/lib/datadog/open_feature/evaluation_engine.rb +19 -9
  23. data/lib/datadog/open_feature/ext.rb +1 -0
  24. data/lib/datadog/open_feature/native_evaluator.rb +38 -0
  25. data/lib/datadog/open_feature/noop_evaluator.rb +3 -3
  26. data/lib/datadog/open_feature/provider.rb +15 -8
  27. data/lib/datadog/open_feature/remote.rb +1 -1
  28. data/lib/datadog/opentelemetry/configuration/settings.rb +159 -0
  29. data/lib/datadog/opentelemetry/metrics.rb +110 -0
  30. data/lib/datadog/opentelemetry/sdk/configurator.rb +25 -1
  31. data/lib/datadog/opentelemetry/sdk/metrics_exporter.rb +38 -0
  32. data/lib/datadog/opentelemetry.rb +3 -0
  33. data/lib/datadog/tracing/configuration/ext.rb +1 -0
  34. data/lib/datadog/tracing/contrib/cloudwise/propagation.rb +143 -17
  35. data/lib/datadog/tracing/contrib/grape/endpoint.rb +141 -0
  36. data/lib/datadog/tracing/contrib/kafka/events/consumer/process_batch.rb +26 -0
  37. data/lib/datadog/tracing/contrib/kafka/events/consumer/process_message.rb +26 -0
  38. data/lib/datadog/tracing/contrib/kafka/instrumentation/consumer.rb +79 -9
  39. data/lib/datadog/tracing/contrib/kafka/instrumentation/producer.rb +29 -6
  40. data/lib/datadog/tracing/contrib/rack/middlewares.rb +6 -54
  41. data/lib/datadog/tracing/diagnostics/environment_logger.rb +1 -1
  42. data/lib/datadog/tracing/transport/serializable_trace.rb +8 -1
  43. data/lib/datadog/tracing/transport/trace_formatter.rb +11 -0
  44. data/lib/datadog/tracing/transport/traces.rb +3 -5
  45. data/lib/datadog/version.rb +1 -1
  46. metadata +29 -4
@@ -0,0 +1,159 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../../core/configuration/ext'
4
+
5
+ module Datadog
6
+ module OpenTelemetry
7
+ module Configuration
8
+ module Settings
9
+ def self.extended(base)
10
+ base = base.singleton_class unless base.is_a?(Class)
11
+ add_settings!(base)
12
+ end
13
+
14
+ def self.normalize_temporality_preference(env_var_name)
15
+ proc do |value|
16
+ if value && value.to_s.downcase != 'delta' && value.to_s.downcase != 'cumulative'
17
+ Datadog.logger.warn("#{env_var_name}=#{value} is not supported. Using delta instead.")
18
+ 'delta'
19
+ else
20
+ value
21
+ end
22
+ end
23
+ end
24
+
25
+ def self.normalize_protocol(env_var_name)
26
+ proc do |value|
27
+ if value && value.to_s.downcase != 'http/protobuf'
28
+ Datadog.logger.warn("#{env_var_name}=#{value} is not supported. Using http/protobuf instead.")
29
+ end
30
+ 'http/protobuf'
31
+ end
32
+ end
33
+
34
+ def self.headers_parser(env_var_name)
35
+ lambda do |value|
36
+ return {} if value.nil? || value.empty?
37
+
38
+ headers = {}
39
+ header_items = value.split(',')
40
+ header_items.each do |key_value|
41
+ key, header_value = key_value.split('=', 2)
42
+ # If header is malformed, return an empty hash
43
+ if key.nil? || header_value.nil?
44
+ Datadog.logger.warn("#{env_var_name} has malformed header: #{key_value.inspect}")
45
+ return {}
46
+ end
47
+
48
+ key.strip!
49
+ header_value.strip!
50
+ if key.empty? || header_value.empty?
51
+ Datadog.logger.warn("#{env_var_name} has empty key or value in: #{key_value.inspect}")
52
+ return {}
53
+ end
54
+
55
+ headers[key] = header_value
56
+ end
57
+ headers
58
+ end
59
+ end
60
+
61
+ def self.add_settings!(base)
62
+ base.class_eval do
63
+ settings :opentelemetry do
64
+ settings :exporter do
65
+ option :protocol do |o|
66
+ o.type :string
67
+ o.setter(&Settings.normalize_protocol('OTEL_EXPORTER_OTLP_PROTOCOL'))
68
+ o.env 'OTEL_EXPORTER_OTLP_PROTOCOL'
69
+ o.default 'http/protobuf'
70
+ end
71
+
72
+ option :timeout_millis do |o|
73
+ o.type :int
74
+ o.env 'OTEL_EXPORTER_OTLP_TIMEOUT'
75
+ o.default 10_000
76
+ end
77
+
78
+ option :headers do |o|
79
+ o.type :hash
80
+ o.env 'OTEL_EXPORTER_OTLP_HEADERS'
81
+ o.default { {} }
82
+ o.env_parser(&Settings.headers_parser('OTEL_EXPORTER_OTLP_HEADERS'))
83
+ end
84
+
85
+ option :endpoint do |o|
86
+ o.type :string, nilable: true
87
+ o.env 'OTEL_EXPORTER_OTLP_ENDPOINT'
88
+ o.default nil
89
+ end
90
+ end
91
+
92
+ settings :metrics do
93
+ # Metrics-specific options default to nil to detect unset state.
94
+ # If a metrics-specific env var (e.g., OTEL_EXPORTER_OTLP_METRICS_TIMEOUT) is not set,
95
+ # we fall back to the general OTLP env var (e.g., OTEL_EXPORTER_OTLP_TIMEOUT) per OpenTelemetry spec.
96
+ option :enabled do |o|
97
+ o.type :bool
98
+ o.env 'DD_METRICS_OTEL_ENABLED'
99
+ o.default false
100
+ end
101
+
102
+ option :exporter do |o|
103
+ o.type :string
104
+ o.env 'OTEL_METRICS_EXPORTER'
105
+ o.default 'otlp'
106
+ end
107
+
108
+ option :export_interval_millis do |o|
109
+ o.type :int
110
+ o.env 'OTEL_METRIC_EXPORT_INTERVAL'
111
+ o.default 10_000
112
+ end
113
+
114
+ option :export_timeout_millis do |o|
115
+ o.type :int
116
+ o.env 'OTEL_METRIC_EXPORT_TIMEOUT'
117
+ o.default 7_500
118
+ end
119
+
120
+ option :temporality_preference do |o|
121
+ o.type :string
122
+ o.env 'OTEL_EXPORTER_OTLP_METRICS_TEMPORALITY_PREFERENCE'
123
+ o.default 'delta'
124
+ o.setter(&Settings.normalize_temporality_preference('OTEL_EXPORTER_OTLP_METRICS_TEMPORALITY_PREFERENCE'))
125
+ end
126
+
127
+ option :endpoint do |o|
128
+ o.type :string, nilable: true
129
+ o.env 'OTEL_EXPORTER_OTLP_METRICS_ENDPOINT'
130
+ o.default nil
131
+ end
132
+
133
+ option :headers do |o|
134
+ o.type :hash, nilable: true
135
+ o.env 'OTEL_EXPORTER_OTLP_METRICS_HEADERS'
136
+ o.default nil
137
+ o.env_parser(&Settings.headers_parser('OTEL_EXPORTER_OTLP_METRICS_HEADERS'))
138
+ end
139
+
140
+ option :timeout_millis do |o|
141
+ o.type :int, nilable: true
142
+ o.env 'OTEL_EXPORTER_OTLP_METRICS_TIMEOUT'
143
+ o.default nil
144
+ end
145
+
146
+ option :protocol do |o|
147
+ o.type :string, nilable: true
148
+ o.env 'OTEL_EXPORTER_OTLP_METRICS_PROTOCOL'
149
+ o.default nil
150
+ o.setter(&Settings.normalize_protocol('OTEL_EXPORTER_OTLP_METRICS_PROTOCOL'))
151
+ end
152
+ end
153
+ end
154
+ end
155
+ end
156
+ end
157
+ end
158
+ end
159
+ end
@@ -0,0 +1,110 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../core/configuration/ext'
4
+
5
+ module Datadog
6
+ module OpenTelemetry
7
+ class Metrics
8
+ EXPORTER_NONE = 'none'
9
+
10
+ def self.initialize!(components)
11
+ new(components).configure_metrics_sdk
12
+ true
13
+ rescue => exc
14
+ components.logger.error("Failed to initialize OpenTelemetry metrics: #{exc.class}: #{exc}: #{exc.backtrace.join("\n")}")
15
+ false
16
+ end
17
+
18
+ def initialize(components)
19
+ @logger = components.logger
20
+ @settings = components.settings
21
+ @agent_host = components.agent_settings.hostname
22
+ @agent_ssl = components.agent_settings.ssl
23
+ end
24
+
25
+ def configure_metrics_sdk
26
+ provider = ::OpenTelemetry.meter_provider
27
+ provider.shutdown if provider.is_a?(::OpenTelemetry::SDK::Metrics::MeterProvider)
28
+
29
+ # The OpenTelemetry SDK defaults to cumulative temporality, but Datadog prefers delta temporality.
30
+ # Here is an example of how this config is applied: https://github.com/open-telemetry/opentelemetry-ruby/blob/1933d4c18e5f5e45c53fa9e902e58aa91e85cc38/metrics_sdk/lib/opentelemetry/sdk/metrics/aggregation/sum.rb#L14
31
+ if DATADOG_ENV['OTEL_EXPORTER_OTLP_METRICS_TEMPORALITY_PREFERENCE'].nil?
32
+ ENV['OTEL_EXPORTER_OTLP_METRICS_TEMPORALITY_PREFERENCE'] = 'delta' # rubocop:disable CustomCops/EnvUsageCop
33
+ end
34
+
35
+ resource = create_resource
36
+ provider = ::OpenTelemetry::SDK::Metrics::MeterProvider.new(resource: resource)
37
+ configure_metric_reader(provider)
38
+ ::OpenTelemetry.meter_provider = provider
39
+ end
40
+
41
+ private
42
+
43
+ def create_resource
44
+ resource_attributes = {}
45
+ resource_attributes['host.name'] = Datadog::Core::Environment::Socket.hostname if @settings.tracing.report_hostname
46
+
47
+ @settings.tags&.each do |key, value|
48
+ otel_key = case key
49
+ when 'service' then 'service.name'
50
+ when 'env' then 'deployment.environment'
51
+ when 'version' then 'service.version'
52
+ else key
53
+ end
54
+ resource_attributes[otel_key] = value
55
+ end
56
+
57
+ resource_attributes['service.name'] = @settings.service_without_fallback || resource_attributes['service.name'] || Datadog::Core::Environment::Ext::FALLBACK_SERVICE_NAME
58
+ resource_attributes['deployment.environment'] = @settings.env if @settings.env
59
+ resource_attributes['service.version'] = @settings.version if @settings.version
60
+
61
+ ::OpenTelemetry::SDK::Resources::Resource.create(resource_attributes)
62
+ end
63
+
64
+ def configure_metric_reader(provider)
65
+ exporter_name = @settings.opentelemetry.metrics.exporter
66
+ return if exporter_name == EXPORTER_NONE
67
+
68
+ configure_otlp_exporter(provider)
69
+ rescue => e
70
+ @logger.warn("Failed to configure OTLP metrics exporter: #{e.class}: #{e}")
71
+ end
72
+
73
+ def resolve_metrics_endpoint
74
+ metrics_config = @settings.opentelemetry.metrics
75
+ exporter_config = @settings.opentelemetry.exporter
76
+
77
+ return metrics_config.endpoint if metrics_config.endpoint
78
+ return exporter_config.endpoint if exporter_config.endpoint
79
+ "#{@agent_ssl ? "https" : "http"}://#{@agent_host}:4318/v1/metrics"
80
+ end
81
+
82
+ def configure_otlp_exporter(provider)
83
+ require 'opentelemetry/exporter/otlp_metrics'
84
+ require_relative 'sdk/metrics_exporter'
85
+
86
+ metrics_config = @settings.opentelemetry.metrics
87
+ exporter_config = @settings.opentelemetry.exporter
88
+ timeout = metrics_config.timeout_millis || exporter_config.timeout_millis
89
+ headers = metrics_config.headers || exporter_config.headers || {}
90
+
91
+ protocol = metrics_config.protocol || exporter_config.protocol
92
+ exporter = Datadog::OpenTelemetry::SDK::MetricsExporter.new(
93
+ endpoint: resolve_metrics_endpoint,
94
+ timeout: timeout / 1000.0,
95
+ headers: headers,
96
+ protocol: protocol
97
+ )
98
+
99
+ reader = ::OpenTelemetry::SDK::Metrics::Export::PeriodicMetricReader.new(
100
+ exporter: exporter,
101
+ export_interval_millis: metrics_config.export_interval_millis,
102
+ export_timeout_millis: metrics_config.export_timeout_millis
103
+ )
104
+ provider.add_metric_reader(reader)
105
+ rescue LoadError => e
106
+ @logger.warn("Could not load OTLP metrics exporter: #{e.class}: #{e}")
107
+ end
108
+ end
109
+ end
110
+ end
@@ -30,7 +30,31 @@ module Datadog
30
30
  [SpanProcessor.new]
31
31
  end
32
32
 
33
- ::OpenTelemetry::SDK::Configurator.prepend(self)
33
+ def metrics_configuration_hook
34
+ components = Datadog.send(:components)
35
+ return super unless components.settings.opentelemetry.metrics.enabled
36
+
37
+ begin
38
+ require 'opentelemetry-metrics-sdk'
39
+ rescue LoadError => exc
40
+ components.logger.warn("Failed to load OpenTelemetry metrics gems: #{exc.class}: #{exc}")
41
+ return super
42
+ end
43
+
44
+ success = Datadog::OpenTelemetry::Metrics.initialize!(components)
45
+ super unless success
46
+ end
47
+
48
+ # Prepend to ConfiguratorPatch (not Configurator) so our hook runs first.
49
+ begin
50
+ require 'opentelemetry-metrics-sdk' if defined?(OpenTelemetry::SDK) && !defined?(OpenTelemetry::SDK::Metrics::ConfiguratorPatch)
51
+ rescue LoadError
52
+ end
53
+
54
+ if defined?(::OpenTelemetry::SDK::Metrics::ConfiguratorPatch)
55
+ ::OpenTelemetry::SDK::Metrics::ConfiguratorPatch.prepend(self) unless ::OpenTelemetry::SDK::Metrics::ConfiguratorPatch.ancestors.include?(self)
56
+ end
57
+ ::OpenTelemetry::SDK::Configurator.prepend(self) unless ::OpenTelemetry::SDK::Configurator.ancestors.include?(self)
34
58
  end
35
59
  end
36
60
  end
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'opentelemetry/exporter/otlp_metrics'
4
+
5
+ module Datadog
6
+ module OpenTelemetry
7
+ module SDK
8
+ class MetricsExporter < ::OpenTelemetry::Exporter::OTLP::Metrics::MetricsExporter
9
+ METRIC_EXPORT_ATTEMPTS = 'otel.metrics_export_attempts'
10
+ METRIC_EXPORT_SUCCESSES = 'otel.metrics_export_successes'
11
+ METRIC_EXPORT_FAILURES = 'otel.metrics_export_failures'
12
+
13
+ def initialize(endpoint:, timeout:, headers:, protocol:)
14
+ super(endpoint: endpoint, timeout: timeout, headers: headers)
15
+ @telemetry_tags = {'protocol' => protocol, 'encoding' => 'protobuf'}
16
+ end
17
+
18
+ def export(metrics, timeout: nil)
19
+ telemetry&.inc('tracers', METRIC_EXPORT_ATTEMPTS, 1, tags: @telemetry_tags)
20
+ result = super
21
+ metric_name = (result == 0) ? METRIC_EXPORT_SUCCESSES : METRIC_EXPORT_FAILURES
22
+ telemetry&.inc('tracers', metric_name, 1, tags: @telemetry_tags)
23
+ result
24
+ rescue => e
25
+ Datadog.logger.error("Failed to export OpenTelemetry Metrics: #{e.class}: #{e}")
26
+ telemetry&.inc('tracers', METRIC_EXPORT_FAILURES, 1, tags: @telemetry_tags)
27
+ raise
28
+ end
29
+
30
+ private
31
+
32
+ def telemetry
33
+ Datadog.send(:components).telemetry
34
+ end
35
+ end
36
+ end
37
+ end
38
+ end
@@ -22,6 +22,8 @@ require_relative 'opentelemetry/api/baggage'
22
22
  require_relative 'opentelemetry/sdk/configurator' if defined?(OpenTelemetry::SDK)
23
23
  require_relative 'opentelemetry/sdk/trace/span' if defined?(OpenTelemetry::SDK)
24
24
 
25
+ require_relative 'opentelemetry/metrics' if defined?(OpenTelemetry::SDK::Metrics)
26
+
25
27
  module Datadog
26
28
  # Datadog OpenTelemetry integration.
27
29
  module OpenTelemetry
@@ -47,6 +49,7 @@ end
47
49
  # Currently, this closely translates to Datadog's partial flushing.
48
50
  #
49
51
  # @see OpenTelemetry::SDK::Trace::SpanProcessor#on_finish
52
+
50
53
  Datadog.configure do |c|
51
54
  c.tracing.partial_flush.enabled = true
52
55
  end
@@ -15,6 +15,7 @@ module Datadog
15
15
  ENV_NATIVE_SPAN_EVENTS = 'DD_TRACE_NATIVE_SPAN_EVENTS'
16
16
  ENV_RESOURCE_RENAMING_ENABLED = 'DD_TRACE_RESOURCE_RENAMING_ENABLED'
17
17
  ENV_RESOURCE_RENAMING_ALWAYS_SIMPLIFIED_ENDPOINT = 'DD_TRACE_RESOURCE_RENAMING_ALWAYS_SIMPLIFIED_ENDPOINT'
18
+ ENV_EXPERIMENTAL_PROPAGATE_PROCESS_TAGS_ENABLED = 'DD_EXPERIMENTAL_PROPAGATE_PROCESS_TAGS_ENABLED'
18
19
 
19
20
  # @public_api
20
21
  module SpanAttributeSchema
@@ -163,6 +163,15 @@ module Datadog
163
163
  end
164
164
  end
165
165
 
166
+ # 获取当前应用的 app_id
167
+ # 用于 Consumer 端设置 app_id_to 标签,表示当前消费消息的应用
168
+ # @return [String, nil] 当前应用的 app_id
169
+ def self.get_current_app_id
170
+ service_name = Datadog.configuration.service
171
+ return nil unless service_name
172
+ generate_app_id(service_name)
173
+ end
174
+
166
175
  # 获取实例 ID(基于 host_ip + port 的 MD5)
167
176
  # host_ip: 本机 IP 地址
168
177
  # port: 应用端口
@@ -265,16 +274,22 @@ module Datadog
265
274
  return unless fields
266
275
 
267
276
  # 为 span 添加标签(除了 type_from,其他字段都加 _from 后缀)
268
- span.set_tag('type_from', fields[:type_from]) if fields[:type_from]
269
- span.set_tag('sample_from', fields[:sample]) if fields[:sample]
270
- span.set_tag('host_id_from', fields[:host_id]) if fields[:host_id]
271
- span.set_tag('app_id_from', fields[:app_id]) if fields[:app_id]
272
- span.set_tag('instance_id_from', fields[:instance_id]) if fields[:instance_id]
273
- span.set_tag('trace_id_from', fields[:trace_id]) if fields[:trace_id]
274
- span.set_tag('assumed_app_id_from', fields[:assumed_app_id]) if fields[:assumed_app_id]
275
- span.set_tag('span_id_from', fields[:span_id]) if fields[:span_id]
276
- span.set_tag('segment_id_from', fields[:segment_id]) if fields[:segment_id]
277
- span.set_tag('app_name_from', fields[:app_name]) if fields[:app_name]
277
+ tags = {}
278
+ tags['type_from'] = fields[:type_from] if fields[:type_from]
279
+ tags['sample_from'] = fields[:sample] if fields[:sample]
280
+ tags['host_id_from'] = fields[:host_id] if fields[:host_id]
281
+ tags['app_id_from'] = fields[:app_id] if fields[:app_id]
282
+ tags['instance_id_from'] = fields[:instance_id] if fields[:instance_id]
283
+ tags['trace_id_from'] = fields[:trace_id] if fields[:trace_id]
284
+ tags['assumed_app_id_from'] = fields[:assumed_app_id] if fields[:assumed_app_id]
285
+ tags['span_id_from'] = fields[:span_id] if fields[:span_id]
286
+ tags['segment_id_from'] = fields[:segment_id] if fields[:segment_id]
287
+ tags['app_name_from'] = fields[:app_name] if fields[:app_name]
288
+
289
+ current_app_id = get_current_app_id
290
+ tags['app_id'] = current_app_id if current_app_id
291
+
292
+ span.set_tags(tags) unless tags.empty?
278
293
 
279
294
  Datadog.logger.debug do
280
295
  "Extracted CLOUDWISE header : cloudwise_header=#{cloudwise_header}"
@@ -339,8 +354,17 @@ module Datadog
339
354
  end
340
355
 
341
356
  # 获取当前服务的 sys 值
342
- # @return [String] sys 值,默认为 'default'
357
+ # 优先级: Datadog.configuration.cloudwise.sys > CW_SYS 环境变量 > 默认值 'default'
343
358
  def self.get_sys
359
+ # 优先从配置读取
360
+ if defined?(Datadog.configuration) &&
361
+ Datadog.configuration.respond_to?(:cloudwise) &&
362
+ Datadog.configuration.cloudwise.respond_to?(:sys)
363
+ sys = Datadog.configuration.cloudwise.sys
364
+ return sys if sys && !sys.empty? && sys != 'default'
365
+ end
366
+
367
+ # 其次从环境变量读取
344
368
  ENV['CW_SYS'] || 'default'
345
369
  end
346
370
 
@@ -435,35 +459,137 @@ module Datadog
435
459
  other_header = request_headers[HEADER_OTHER_NAME] ||
436
460
  request_headers['HTTP_' + HEADER_OTHER_NAME.upcase.gsub('-', '_')]
437
461
 
462
+ # ✅ 优化:使用 set_tags 批量设置,减少锁竞争
463
+ tags = {}
464
+
438
465
  if other_header
439
466
  # 解析 CLOUDWISE-OTHER:service_type_from:parent_sys
440
467
  parts = other_header.split(':')
441
468
 
442
- service_type_from = normalize_field_value(parts[FIELD_OTHER_SERVICE_TYPE_FROM])
443
- parent_sys = normalize_field_value(parts[FIELD_OTHER_PARENT_SYS])
469
+ service_type_from = parts[FIELD_OTHER_SERVICE_TYPE_FROM].to_s.strip
470
+ parent_sys = parts[FIELD_OTHER_PARENT_SYS].to_s.strip
444
471
 
445
472
  # 只添加上游相关的字段到 span tags
446
473
  # 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)
474
+ tags['service_type_from'] = service_type_from
475
+ tags['parent_sys'] = parent_sys
449
476
 
450
477
  Datadog.logger.debug do
451
478
  "Extracted CLOUDWISE-OTHER from request: service_type_from=#{service_type_from}, parent_sys=#{parent_sys}"
452
479
  end if defined?(Datadog.logger)
453
480
  else
454
481
  # 如果没有上游请求头,设置为空字符串
455
- span.set_tag('service_type_from', '')
456
- span.set_tag('parent_sys', '')
482
+ tags['service_type_from'] = ''
483
+ tags['parent_sys'] = ''
457
484
 
458
485
  Datadog.logger.debug do
459
486
  "No CLOUDWISE-OTHER header in request, set service_type_from and parent_sys to empty"
460
487
  end if defined?(Datadog.logger)
461
488
  end
489
+
490
+ span.set_tags(tags) unless tags.empty?
462
491
  rescue => e
463
492
  Datadog.logger.error do
464
493
  "Error extracting CLOUDWISE-OTHER: #{e.message}"
465
494
  end if defined?(Datadog.logger)
466
495
  end
496
+
497
+ # ===== Kafka 相关方法 =====
498
+
499
+ # 为 Kafka 消息注入 CLOUDWISE header
500
+ # @param message_headers [Hash] Kafka 消息的 headers
501
+ # @param topic [String] Kafka topic 名称
502
+ # @param service_name [String] 服务名称
503
+ def self.inject_kafka_headers!(message_headers, topic, service_name = nil)
504
+ return unless message_headers
505
+
506
+ service_name ||= Datadog.configuration.service
507
+ return unless service_name
508
+
509
+ # 获取当前活跃的 span 和 trace
510
+ span = Datadog::Tracing.active_span
511
+ trace = Datadog::Tracing.active_trace
512
+ return unless span && trace
513
+
514
+ # 构建 CLOUDWISE header 值
515
+ cloudwise_value = build_cloudwise_value_for_kafka(
516
+ span: span,
517
+ trace: trace,
518
+ service_name: service_name,
519
+ topic: topic
520
+ )
521
+
522
+ # 添加到消息 headers
523
+ message_headers[HEADER_NAME] = cloudwise_value if cloudwise_value
524
+
525
+ Datadog.logger.debug do
526
+ "Injected Kafka CLOUDWISE header: #{cloudwise_value}"
527
+ end if defined?(Datadog.logger)
528
+ rescue => e
529
+ Datadog.logger.error do
530
+ "Error injecting Kafka CLOUDWISE header: #{e.message}\n#{e.backtrace.join("\n")}"
531
+ end if defined?(Datadog.logger)
532
+ end
533
+
534
+ # 构建 Kafka 消息的 CLOUDWISE 值
535
+ # 格式:[type_from]:[sample]:[host_id]:[app_id]:[instance_id]:[trace_id]:[assumed_app_id]:[span_id]:[segment_id]:[app_name]
536
+ # @param span [Datadog::Tracing::SpanOperation] 当前 span
537
+ # @param trace [Datadog::Tracing::TraceOperation] 当前 trace
538
+ # @param service_name [String] 服务名称
539
+ # @param topic [String] Kafka topic 名称
540
+ # @return [String] CLOUDWISE header 值
541
+ def self.build_cloudwise_value_for_kafka(span:, trace:, service_name:, topic:)
542
+ type_from = TYPE_FROM
543
+ sample = 0
544
+ host_id = get_host_id
545
+ app_id = generate_app_id(service_name)
546
+ instance_id = get_instance_id
547
+ # 使用低 64 位 trace_id,与上报保持一致
548
+ trace_id = Tracing::Utils::TraceId.to_low_order(trace.id).to_s
549
+ # 对于 Kafka,使用 topic 生成 assumed_app_id
550
+ assumed_app_id = generate_assumed_app_id_for_kafka(topic)
551
+ span_id = span.id.to_s
552
+ segment_id = get_segment_id(span)
553
+ app_name = service_name
554
+
555
+ "#{type_from}:#{sample}:#{host_id}:#{app_id}:#{instance_id}:#{trace_id}:#{assumed_app_id}:#{span_id}:#{segment_id}:#{app_name}"
556
+ end
557
+
558
+ # 为 Kafka topic 生成 assumed_app_id
559
+ # @param topic [String] Kafka topic 名称
560
+ # @return [String] assumed_app_id
561
+ def self.generate_assumed_app_id_for_kafka(topic)
562
+ return '-1' unless topic
563
+
564
+ # 使用 topic 名称生成 assumed_app_id
565
+ md5_hex = Digest::MD5.hexdigest("kafka:#{topic}")
566
+ # 取前15位转为整数(避免超过 Java Long.MAX_VALUE)
567
+ md5_hex[0..14].to_i(16).to_s
568
+ rescue => e
569
+ Datadog.logger.debug { "Error generating assumed_app_id for Kafka: #{e.message}" } if defined?(Datadog.logger)
570
+ '-1'
571
+ end
572
+
573
+ # 从 Kafka 消息 headers 中提取 CLOUDWISE header 并设置到 span
574
+ # @param span [Datadog::Tracing::SpanOperation] 当前 span
575
+ # @param headers [Hash] Kafka 消息的 headers
576
+ def self.extract_kafka_cloudwise_headers!(span, headers)
577
+ return unless span && headers
578
+
579
+ # 提取 CLOUDWISE header
580
+ cloudwise_header = headers[HEADER_NAME]
581
+ if cloudwise_header
582
+ extract_and_tag_from_header!(span, cloudwise_header)
583
+ end
584
+
585
+ Datadog.logger.debug do
586
+ "Extracted Kafka CLOUDWISE headers from message"
587
+ end if defined?(Datadog.logger)
588
+ rescue => e
589
+ Datadog.logger.error do
590
+ "Error extracting Kafka CLOUDWISE headers: #{e.message}"
591
+ end if defined?(Datadog.logger)
592
+ end
467
593
  end
468
594
  end
469
595
  end