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.
- checksums.yaml +4 -4
- data/ext/libdatadog_api/feature_flags.c +554 -0
- data/ext/libdatadog_api/feature_flags.h +5 -0
- data/ext/libdatadog_api/init.c +2 -0
- data/lib/datadog/core/cloudwise/client.rb +99 -4
- data/lib/datadog/core/cloudwise/component.rb +55 -13
- data/lib/datadog/core/cloudwise/docc_operation_worker.rb +11 -1
- data/lib/datadog/core/cloudwise/license_worker.rb +7 -0
- data/lib/datadog/core/cloudwise/time_sync_worker.rb +200 -0
- data/lib/datadog/core/configuration/settings.rb +12 -0
- data/lib/datadog/core/configuration/supported_configurations.rb +14 -0
- data/lib/datadog/core/environment/ext.rb +6 -0
- data/lib/datadog/core/environment/process.rb +79 -0
- data/lib/datadog/core/feature_flags.rb +61 -0
- data/lib/datadog/core/tag_normalizer.rb +84 -0
- data/lib/datadog/core/transport/http/adapters/net.rb +8 -0
- data/lib/datadog/core/utils/array.rb +29 -0
- data/lib/datadog/core/utils.rb +2 -0
- data/lib/datadog/data_streams/processor.rb +1 -1
- data/lib/datadog/di/transport/http.rb +6 -2
- data/lib/datadog/di/transport/input.rb +62 -2
- data/lib/datadog/open_feature/evaluation_engine.rb +19 -9
- data/lib/datadog/open_feature/ext.rb +1 -0
- data/lib/datadog/open_feature/native_evaluator.rb +38 -0
- data/lib/datadog/open_feature/noop_evaluator.rb +3 -3
- data/lib/datadog/open_feature/provider.rb +15 -8
- data/lib/datadog/open_feature/remote.rb +1 -1
- data/lib/datadog/opentelemetry/configuration/settings.rb +159 -0
- data/lib/datadog/opentelemetry/metrics.rb +110 -0
- data/lib/datadog/opentelemetry/sdk/configurator.rb +25 -1
- data/lib/datadog/opentelemetry/sdk/metrics_exporter.rb +38 -0
- data/lib/datadog/opentelemetry.rb +3 -0
- data/lib/datadog/tracing/configuration/ext.rb +1 -0
- data/lib/datadog/tracing/contrib/cloudwise/propagation.rb +143 -17
- data/lib/datadog/tracing/contrib/grape/endpoint.rb +141 -0
- data/lib/datadog/tracing/contrib/kafka/events/consumer/process_batch.rb +26 -0
- data/lib/datadog/tracing/contrib/kafka/events/consumer/process_message.rb +26 -0
- data/lib/datadog/tracing/contrib/kafka/instrumentation/consumer.rb +79 -9
- data/lib/datadog/tracing/contrib/kafka/instrumentation/producer.rb +29 -6
- data/lib/datadog/tracing/contrib/rack/middlewares.rb +6 -54
- data/lib/datadog/tracing/diagnostics/environment_logger.rb +1 -1
- data/lib/datadog/tracing/transport/serializable_trace.rb +8 -1
- data/lib/datadog/tracing/transport/trace_formatter.rb +11 -0
- data/lib/datadog/tracing/transport/traces.rb +3 -5
- data/lib/datadog/version.rb +1 -1
- 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
|
-
|
|
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
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
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
|
-
#
|
|
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 =
|
|
443
|
-
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
|
-
|
|
448
|
-
|
|
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
|
-
|
|
456
|
-
|
|
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
|