cw-datadog 2.23.0.2 → 2.23.0.3

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 +364 -25
  14. data/lib/datadog/core/cloudwise/component.rb +197 -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 +3 -1
  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 +28 -0
  22. data/lib/datadog/core/configuration/supported_configurations.rb +5 -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 +40 -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
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Datadog
4
+ module OpenFeature
5
+ module Ext
6
+ ERROR = 'ERROR'
7
+ INITIALIZING = 'INITIALIZING'
8
+ UNKNOWN_TYPE = 'UNKNOWN_TYPE'
9
+ PROVIDER_FATAL = 'PROVIDER_FATAL'
10
+ PROVIDER_NOT_READY = 'PROVIDER_NOT_READY'
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'ext'
4
+ require_relative 'resolution_details'
5
+
6
+ module Datadog
7
+ module OpenFeature
8
+ # This class is a noop interface of evaluation logic
9
+ class NoopEvaluator
10
+ def initialize(_configuration)
11
+ # no-op
12
+ end
13
+
14
+ def get_assignment(_flag_key, default_value, _context, _expected_type)
15
+ ResolutionDetails.new(
16
+ value: default_value,
17
+ log?: false,
18
+ error?: true,
19
+ error_code: Ext::PROVIDER_NOT_READY,
20
+ error_message: 'Waiting for universal flag configuration',
21
+ reason: Ext::INITIALIZING
22
+ )
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,134 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'ext'
4
+ require 'open_feature/sdk'
5
+
6
+ module Datadog
7
+ module OpenFeature
8
+ # OpenFeature feature flagging provider backed by Datadog Remote Configuration.
9
+ #
10
+ # Implementation follows the OpenFeature contract of Provider SDK.
11
+ # For details see:
12
+ # - https://github.com/open-feature/ruby-sdk/blob/v0.4.1/README.md#develop-a-provider
13
+ # - https://github.com/open-feature/ruby-sdk/blob/v0.4.1/lib/open_feature/sdk/provider/no_op_provider.rb
14
+ #
15
+ # In the example below you can see how to configure the OpenFeature SDK
16
+ # https://github.com/open-feature/ruby-sdk to use the Datadog feature flags provider.
17
+ #
18
+ # Example:
19
+ #
20
+ # Make sure to enable Remote Configuration and OpenFeature in the Datadog configuration.
21
+ #
22
+ # ```ruby
23
+ # # FILE: initializers/datadog.rb
24
+ # Datadog.configure do |config|
25
+ # config.remote.enabled = true
26
+ # config.open_feature.enabled = true
27
+ # end
28
+ # ```
29
+ #
30
+ # And configure the OpenFeature SDK to use the Datadog feature flagging provider.
31
+ #
32
+ # ```ruby
33
+ # # FILE: initializers/open_feature.rb
34
+ # require 'open_feature/sdk'
35
+ # require 'datadog/open_feature/provider'
36
+ #
37
+ # OpenFeature::SDK.configure do |config|
38
+ # config.set_provider(Datadog::OpenFeature::Provider.new)
39
+ # end
40
+ # ```
41
+ #
42
+ # Now you can create OpenFeature SDK client and use it to fetch feature flag values.
43
+ #
44
+ # ```ruby
45
+ # client = OpenFeature::SDK.build_client
46
+ # context = OpenFeature::SDK::EvaluationContext.new('email' => 'john.doe@gmail.com')
47
+ #
48
+ # client.fetch_string_value(
49
+ # flag_key: 'banner', default_value: 'Greetings!', evaluation_context: context
50
+ # )
51
+ # # => 'Welcome back!'
52
+ # ```
53
+ class Provider
54
+ NAME = 'Datadog Feature Flagging Provider'
55
+
56
+ attr_reader :metadata
57
+
58
+ def initialize
59
+ @metadata = ::OpenFeature::SDK::Provider::ProviderMetadata.new(name: NAME).freeze
60
+ end
61
+
62
+ def init
63
+ # no-op
64
+ end
65
+
66
+ def shutdown
67
+ # no-op
68
+ end
69
+
70
+ def fetch_boolean_value(flag_key:, default_value:, evaluation_context: nil)
71
+ evaluate(flag_key, default_value: default_value, expected_type: 'boolean', evaluation_context: evaluation_context)
72
+ end
73
+
74
+ def fetch_string_value(flag_key:, default_value:, evaluation_context: nil)
75
+ evaluate(flag_key, default_value: default_value, expected_type: 'string', evaluation_context: evaluation_context)
76
+ end
77
+
78
+ def fetch_number_value(flag_key:, default_value:, evaluation_context: nil)
79
+ evaluate(flag_key, default_value: default_value, expected_type: 'number', evaluation_context: evaluation_context)
80
+ end
81
+
82
+ def fetch_integer_value(flag_key:, default_value:, evaluation_context: nil)
83
+ evaluate(flag_key, default_value: default_value, expected_type: 'integer', evaluation_context: evaluation_context)
84
+ end
85
+
86
+ def fetch_float_value(flag_key:, default_value:, evaluation_context: nil)
87
+ evaluate(flag_key, default_value: default_value, expected_type: 'float', evaluation_context: evaluation_context)
88
+ end
89
+
90
+ def fetch_object_value(flag_key:, default_value:, evaluation_context: nil)
91
+ evaluate(flag_key, default_value: default_value, expected_type: 'object', evaluation_context: evaluation_context)
92
+ end
93
+
94
+ private
95
+
96
+ def evaluate(flag_key, default_value:, expected_type:, evaluation_context:)
97
+ engine = OpenFeature.engine
98
+ return component_not_configured_default(default_value) if engine.nil?
99
+
100
+ result = engine.fetch_value(
101
+ flag_key: flag_key,
102
+ default_value: default_value,
103
+ expected_type: expected_type,
104
+ evaluation_context: evaluation_context
105
+ )
106
+
107
+ if result.error?
108
+ return ::OpenFeature::SDK::Provider::ResolutionDetails.new(
109
+ value: result.value,
110
+ error_code: result.error_code,
111
+ error_message: result.error_message,
112
+ reason: result.reason
113
+ )
114
+ end
115
+
116
+ ::OpenFeature::SDK::Provider::ResolutionDetails.new(
117
+ value: result.value,
118
+ variant: result.variant,
119
+ reason: result.reason,
120
+ flag_metadata: result.flag_metadata
121
+ )
122
+ end
123
+
124
+ def component_not_configured_default(value)
125
+ ::OpenFeature::SDK::Provider::ResolutionDetails.new(
126
+ value: value,
127
+ error_code: Ext::PROVIDER_FATAL,
128
+ error_message: "Datadog's OpenFeature component must be configured",
129
+ reason: Ext::ERROR
130
+ )
131
+ end
132
+ end
133
+ end
134
+ end
@@ -0,0 +1,74 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../core/remote/dispatcher'
4
+
5
+ module Datadog
6
+ module OpenFeature
7
+ # This module contains the remote configuration functionality for OpenFeature
8
+ module Remote
9
+ ReadError = Class.new(StandardError)
10
+
11
+ class << self
12
+ FFE_FLAG_CONFIGURATION_RULES = 1 << 46
13
+ FFE_PRODUCTS = ['FFE_FLAGS'].freeze
14
+ FFE_CAPABILITIES = [FFE_FLAG_CONFIGURATION_RULES].freeze
15
+
16
+ def capabilities
17
+ FFE_CAPABILITIES
18
+ end
19
+
20
+ def products
21
+ FFE_PRODUCTS
22
+ end
23
+
24
+ def receivers(telemetry)
25
+ matcher = Core::Remote::Dispatcher::Matcher::Product.new(FFE_PRODUCTS)
26
+ receiver = Core::Remote::Dispatcher::Receiver.new(matcher) do |repository, changes|
27
+ engine = OpenFeature.engine
28
+ break unless engine
29
+
30
+ changes.each do |change|
31
+ content = repository[change.path]
32
+
33
+ unless content || change.type == :delete
34
+ next telemetry.error("OpenFeature: Remote Configuration change is not present on #{change.type}")
35
+ end
36
+
37
+ # NOTE: In the current RC implementation we immediately apply the configuration,
38
+ # but that might change if we need to apply patches instead.
39
+ case change.type
40
+ when :insert, :update
41
+ begin
42
+ # @type var content: Core::Remote::Configuration::Content
43
+ engine.reconfigure!(read_content(content))
44
+ content.applied
45
+ rescue ReadError => e
46
+ content.errored("Error reading Remote Configuration content: #{e.message}")
47
+ rescue EvaluationEngine::ReconfigurationError => e
48
+ content.errored("Error applying OpenFeature configuration: #{e.message}")
49
+ end
50
+ when :delete
51
+ # NOTE: For now, we treat deletion as clearing the configuration
52
+ # In a multi-config scenario, we might track configs per path
53
+ engine.reconfigure!(nil)
54
+ end
55
+ end
56
+ end
57
+
58
+ [receiver]
59
+ end
60
+
61
+ private
62
+
63
+ def read_content(content)
64
+ data = content.data.read
65
+ content.data.rewind
66
+
67
+ raise ReadError, 'EOF reached' if data.nil?
68
+
69
+ data
70
+ end
71
+ end
72
+ end
73
+ end
74
+ end
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'ext'
4
+
5
+ module Datadog
6
+ module OpenFeature
7
+ # This class is based on the `OpenFeature::SDK::Provider::ResolutionDetails` class
8
+ #
9
+ # See: https://github.com/open-feature/ruby-sdk/blob/v0.4.1/lib/open_feature/sdk/provider/resolution_details.rb
10
+ class ResolutionDetails < Struct.new(
11
+ :value,
12
+ :reason,
13
+ :variant,
14
+ :error_code,
15
+ :error_message,
16
+ :flag_metadata,
17
+ :allocation_key,
18
+ :extra_logging,
19
+ :log?,
20
+ :error?,
21
+ keyword_init: true
22
+ )
23
+ def self.build_error(value:, error_code:, error_message:, reason: Ext::ERROR)
24
+ new(
25
+ value: value,
26
+ error_code: error_code,
27
+ error_message: error_message,
28
+ reason: reason,
29
+ error?: true,
30
+ log?: false
31
+ ).freeze
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,72 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../core/transport/http'
4
+ require_relative '../core/transport/http/env'
5
+ require_relative '../core/transport/parcel'
6
+ require_relative '../core/transport/request'
7
+
8
+ module Datadog
9
+ module OpenFeature
10
+ module Transport
11
+ class EncodedParcel
12
+ include Core::Transport::Parcel
13
+
14
+ def encode_with(encoder)
15
+ encoder.encode(data)
16
+ end
17
+ end
18
+
19
+ class HTTP
20
+ class Spec < Core::Transport::HTTP::API::Spec
21
+ def initialize
22
+ @endpoint = Core::Transport::HTTP::API::Endpoint.new(
23
+ :post, '/evp_proxy/v2/api/v2/exposures'
24
+ )
25
+
26
+ super
27
+ end
28
+
29
+ def call(env, &block)
30
+ @endpoint.call(env) do |request_env|
31
+ request_env.headers['Content-Type'] = Core::Encoding::JSONEncoder.content_type
32
+ request_env.headers['X-Datadog-EVP-Subdomain'] = 'event-platform-intake'
33
+ request_env.body = env.request.parcel.encode_with(Core::Encoding::JSONEncoder)
34
+
35
+ block.call(request_env)
36
+ end
37
+ end
38
+ end
39
+
40
+ class Instance < Core::Transport::HTTP::API::Instance
41
+ def send_exposures(env)
42
+ @spec.call(env) { |request_env| call(request_env) }
43
+ end
44
+ end
45
+
46
+ def self.build(agent_settings:, logger:)
47
+ Core::Transport::HTTP.build(
48
+ api_instance_class: HTTP::Instance,
49
+ agent_settings: agent_settings,
50
+ logger: logger
51
+ ) { |t| t.api('exposures', HTTP::Spec.new) }.to_transport(self)
52
+ end
53
+
54
+ def initialize(apis, default_api, logger:)
55
+ @api = apis[default_api]
56
+ @logger = logger
57
+ end
58
+
59
+ def send_exposures(payload)
60
+ request = Core::Transport::Request.new(EncodedParcel.new(payload))
61
+ @api.send_exposures(Core::Transport::HTTP::Env.new(request))
62
+ rescue => e
63
+ message = "Internal error during request. Cause: #{e.class.name} #{e.message} " \
64
+ "Location: #{Array(e.backtrace).first}"
65
+ @logger.debug(message)
66
+
67
+ Core::Transport::InternalErrorResponse.new(e)
68
+ end
69
+ end
70
+ end
71
+ end
72
+ end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'core/configuration'
4
+ require_relative 'open_feature/configuration'
5
+
6
+ module Datadog
7
+ # A namespace for the OpenFeature component.
8
+ module OpenFeature
9
+ Core::Configuration::Settings.extend(Configuration::Settings)
10
+
11
+ def self.enabled?
12
+ Datadog.configuration.open_feature.enabled
13
+ end
14
+
15
+ def self.engine
16
+ Datadog.send(:components).open_feature&.engine
17
+ end
18
+ end
19
+ end
@@ -220,6 +220,12 @@ module Datadog
220
220
  "Please upgrade to Ruby >= 3.1 in order to use this feature. Heap profiling has been disabled."
221
221
  )
222
222
  return false
223
+ elsif RUBY_VERSION.start_with?("4.")
224
+ logger.warn(
225
+ "Heap profiling is not supported in current Ruby version (#{RUBY_VERSION}) due to https://bugs.ruby-lang.org/issues/21710. " \
226
+ "Heap profiling has been disabled."
227
+ )
228
+ return false
223
229
  end
224
230
 
225
231
  unless allocation_profiling_enabled
@@ -17,6 +17,10 @@ module Datadog
17
17
  @scheduler = scheduler
18
18
  end
19
19
 
20
+ def enabled?
21
+ scheduler.running?
22
+ end
23
+
20
24
  def start
21
25
  after_fork! do
22
26
  worker.reset_after_fork
@@ -62,8 +62,7 @@ module Datadog
62
62
 
63
63
  def self.enabled?
64
64
  profiler = Datadog.send(:components).profiler
65
- # Use .send(...) to avoid exposing the attr_reader as an API to the outside
66
- !!profiler&.send(:scheduler)&.running?
65
+ !!profiler&.enabled?
67
66
  end
68
67
 
69
68
  def self.wait_until_running(timeout_seconds: 5)
@@ -15,7 +15,7 @@ end
15
15
 
16
16
  begin
17
17
  require_relative 'auto_instrument'
18
- Datadog::SingleStepInstrument::LOADED = true
18
+ Datadog::SingleStepInstrument.const_set(:LOADED, true)
19
19
  rescue StandardError, LoadError => e
20
20
  warn "Single step instrumentation failed: #{e.class}:#{e.message}\n\tSource:\n\t#{Array(e.backtrace).join("\n\t")}"
21
21
  end
@@ -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