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.
- checksums.yaml +4 -4
- data/ext/datadog_profiling_native_extension/extconf.rb +4 -2
- data/ext/libdatadog_api/library_config.c +12 -11
- data/ext/libdatadog_extconf_helpers.rb +1 -1
- data/lib/datadog/appsec/api_security/route_extractor.rb +20 -5
- data/lib/datadog/appsec/api_security/sampler.rb +3 -1
- data/lib/datadog/appsec/assets/blocked.html +8 -0
- data/lib/datadog/appsec/assets/blocked.json +1 -1
- data/lib/datadog/appsec/assets/blocked.text +3 -1
- data/lib/datadog/appsec/assets.rb +1 -1
- data/lib/datadog/appsec/remote.rb +4 -0
- data/lib/datadog/appsec/response.rb +18 -4
- data/lib/datadog/core/cloudwise/client.rb +364 -25
- data/lib/datadog/core/cloudwise/component.rb +197 -52
- data/lib/datadog/core/cloudwise/docc_heartbeat_worker.rb +105 -0
- data/lib/datadog/core/cloudwise/docc_operation_worker.rb +191 -0
- data/lib/datadog/core/cloudwise/docc_registration_worker.rb +89 -0
- data/lib/datadog/core/cloudwise/license_worker.rb +3 -1
- data/lib/datadog/core/cloudwise/probe_state.rb +134 -12
- data/lib/datadog/core/configuration/components.rb +10 -9
- data/lib/datadog/core/configuration/settings.rb +28 -0
- data/lib/datadog/core/configuration/supported_configurations.rb +5 -2
- data/lib/datadog/core/remote/client/capabilities.rb +7 -0
- data/lib/datadog/core/remote/component.rb +2 -2
- data/lib/datadog/core/remote/transport/config.rb +2 -10
- data/lib/datadog/core/remote/transport/http/config.rb +9 -9
- data/lib/datadog/core/remote/transport/http/negotiation.rb +17 -8
- data/lib/datadog/core/remote/transport/http.rb +2 -0
- data/lib/datadog/core/remote/transport/negotiation.rb +2 -18
- data/lib/datadog/core/remote/worker.rb +23 -35
- data/lib/datadog/core/telemetry/component.rb +26 -13
- data/lib/datadog/core/telemetry/event/app_started.rb +67 -49
- data/lib/datadog/core/telemetry/event/synth_app_client_configuration_change.rb +27 -4
- data/lib/datadog/core/telemetry/transport/http/telemetry.rb +5 -6
- data/lib/datadog/core/telemetry/transport/telemetry.rb +1 -2
- data/lib/datadog/core/telemetry/worker.rb +51 -6
- data/lib/datadog/core/transport/http/adapters/net.rb +2 -0
- data/lib/datadog/core/transport/http/client.rb +69 -0
- data/lib/datadog/core/utils/only_once_successful.rb +6 -2
- data/lib/datadog/data_streams/transport/http/client.rb +4 -32
- data/lib/datadog/data_streams/transport/stats.rb +1 -1
- data/lib/datadog/di/probe_notification_builder.rb +35 -13
- data/lib/datadog/di/transport/diagnostics.rb +2 -2
- data/lib/datadog/di/transport/http/diagnostics.rb +2 -4
- data/lib/datadog/di/transport/http/input.rb +2 -4
- data/lib/datadog/di/transport/input.rb +2 -2
- data/lib/datadog/open_feature/component.rb +60 -0
- data/lib/datadog/open_feature/configuration.rb +27 -0
- data/lib/datadog/open_feature/evaluation_engine.rb +59 -0
- data/lib/datadog/open_feature/exposures/batch_builder.rb +32 -0
- data/lib/datadog/open_feature/exposures/buffer.rb +43 -0
- data/lib/datadog/open_feature/exposures/deduplicator.rb +30 -0
- data/lib/datadog/open_feature/exposures/event.rb +60 -0
- data/lib/datadog/open_feature/exposures/reporter.rb +40 -0
- data/lib/datadog/open_feature/exposures/worker.rb +116 -0
- data/lib/datadog/open_feature/ext.rb +13 -0
- data/lib/datadog/open_feature/noop_evaluator.rb +26 -0
- data/lib/datadog/open_feature/provider.rb +134 -0
- data/lib/datadog/open_feature/remote.rb +74 -0
- data/lib/datadog/open_feature/resolution_details.rb +35 -0
- data/lib/datadog/open_feature/transport.rb +72 -0
- data/lib/datadog/open_feature.rb +19 -0
- data/lib/datadog/profiling/component.rb +6 -0
- data/lib/datadog/profiling/profiler.rb +4 -0
- data/lib/datadog/profiling.rb +1 -2
- data/lib/datadog/single_step_instrument.rb +1 -1
- data/lib/datadog/tracing/contrib/cloudwise/propagation.rb +164 -7
- data/lib/datadog/tracing/contrib/graphql/unified_trace.rb +22 -17
- data/lib/datadog/tracing/contrib/karafka/framework.rb +30 -0
- data/lib/datadog/tracing/contrib/karafka/patcher.rb +14 -0
- data/lib/datadog/tracing/contrib/rack/middlewares.rb +6 -2
- data/lib/datadog/tracing/contrib/waterdrop/configuration/settings.rb +27 -0
- data/lib/datadog/tracing/contrib/waterdrop/distributed/propagation.rb +48 -0
- data/lib/datadog/tracing/contrib/waterdrop/ext.rb +17 -0
- data/lib/datadog/tracing/contrib/waterdrop/integration.rb +43 -0
- data/lib/datadog/tracing/contrib/waterdrop/middleware.rb +46 -0
- data/lib/datadog/tracing/contrib/waterdrop/patcher.rb +46 -0
- data/lib/datadog/tracing/contrib/waterdrop/producer.rb +50 -0
- data/lib/datadog/tracing/contrib/waterdrop.rb +37 -0
- data/lib/datadog/tracing/contrib.rb +1 -0
- data/lib/datadog/tracing/transport/http/api.rb +40 -1
- data/lib/datadog/tracing/transport/http/client.rb +12 -26
- data/lib/datadog/tracing/transport/http/traces.rb +4 -2
- data/lib/datadog/tracing/transport/trace_formatter.rb +16 -0
- data/lib/datadog/version.rb +2 -2
- data/lib/datadog.rb +1 -0
- metadata +38 -15
- data/lib/datadog/core/cloudwise/IMPLEMENTATION_V2.md +0 -517
- data/lib/datadog/core/cloudwise/QUICKSTART.md +0 -398
- data/lib/datadog/core/cloudwise/README.md +0 -722
- data/lib/datadog/core/remote/transport/http/client.rb +0 -49
- data/lib/datadog/core/telemetry/transport/http/client.rb +0 -49
- 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
|
data/lib/datadog/profiling.rb
CHANGED
|
@@ -62,8 +62,7 @@ module Datadog
|
|
|
62
62
|
|
|
63
63
|
def self.enabled?
|
|
64
64
|
profiler = Datadog.send(:components).profiler
|
|
65
|
-
|
|
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
|
|
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
|
|
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]
|
|
295
|
-
host_id: parts[FIELD_HOST_ID]
|
|
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]
|
|
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]
|
|
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
|