datadog 2.22.0 → 2.24.0
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/CHANGELOG.md +100 -1
- data/ext/LIBDATADOG_DEVELOPMENT.md +1 -58
- data/ext/datadog_profiling_native_extension/collectors_stack.c +21 -5
- data/ext/datadog_profiling_native_extension/crashtracking_runtime_stacks.c +239 -0
- data/ext/datadog_profiling_native_extension/datadog_ruby_common.h +1 -1
- data/ext/datadog_profiling_native_extension/extconf.rb +9 -4
- data/ext/datadog_profiling_native_extension/heap_recorder.c +1 -1
- data/ext/datadog_profiling_native_extension/private_vm_api_access.c +12 -0
- data/ext/datadog_profiling_native_extension/private_vm_api_access.h +4 -0
- data/ext/datadog_profiling_native_extension/profiling.c +2 -0
- data/ext/libdatadog_api/datadog_ruby_common.h +1 -1
- 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/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 +23 -6
- data/lib/datadog/appsec/api_security/sampler.rb +7 -4
- 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/context.rb +2 -1
- data/lib/datadog/appsec/remote.rb +5 -9
- data/lib/datadog/appsec/response.rb +18 -4
- data/lib/datadog/appsec/security_engine/result.rb +2 -1
- data/lib/datadog/core/configuration/components.rb +30 -3
- data/lib/datadog/core/configuration/config_helper.rb +2 -2
- data/lib/datadog/core/configuration/deprecations.rb +2 -2
- data/lib/datadog/core/configuration/option_definition.rb +4 -2
- data/lib/datadog/core/configuration/options.rb +8 -5
- data/lib/datadog/core/configuration/settings.rb +28 -3
- data/lib/datadog/core/configuration/supported_configurations.rb +332 -302
- data/lib/datadog/core/ddsketch.rb +0 -2
- data/lib/datadog/core/environment/cgroup.rb +52 -25
- data/lib/datadog/core/environment/container.rb +140 -46
- data/lib/datadog/core/environment/ext.rb +7 -0
- data/lib/datadog/core/environment/process.rb +87 -0
- data/lib/datadog/core/feature_flags.rb +61 -0
- data/lib/datadog/core/rate_limiter.rb +9 -1
- data/lib/datadog/core/remote/client/capabilities.rb +7 -0
- data/lib/datadog/core/remote/client.rb +14 -6
- data/lib/datadog/core/remote/component.rb +6 -4
- data/lib/datadog/core/remote/configuration/content.rb +15 -2
- data/lib/datadog/core/remote/configuration/digest.rb +14 -7
- data/lib/datadog/core/remote/configuration/repository.rb +1 -1
- data/lib/datadog/core/remote/configuration/target.rb +13 -6
- data/lib/datadog/core/remote/transport/config.rb +4 -25
- data/lib/datadog/core/remote/transport/http/config.rb +10 -50
- data/lib/datadog/core/remote/transport/http/negotiation.rb +14 -44
- data/lib/datadog/core/remote/transport/http.rb +15 -24
- data/lib/datadog/core/remote/transport/negotiation.rb +8 -33
- data/lib/datadog/core/remote/worker.rb +25 -37
- data/lib/datadog/core/tag_builder.rb +0 -4
- data/lib/datadog/core/tag_normalizer.rb +84 -0
- data/lib/datadog/core/telemetry/component.rb +59 -16
- data/lib/datadog/core/telemetry/event/app_started.rb +86 -49
- data/lib/datadog/core/telemetry/event/synth_app_client_configuration_change.rb +27 -4
- data/lib/datadog/core/telemetry/logger.rb +2 -2
- data/lib/datadog/core/telemetry/logging.rb +2 -8
- data/lib/datadog/core/telemetry/metrics_manager.rb +9 -0
- data/lib/datadog/core/telemetry/request.rb +17 -3
- data/lib/datadog/core/telemetry/transport/http/telemetry.rb +3 -34
- data/lib/datadog/core/telemetry/transport/http.rb +21 -16
- data/lib/datadog/core/telemetry/transport/telemetry.rb +3 -11
- data/lib/datadog/core/telemetry/worker.rb +88 -32
- data/lib/datadog/core/transport/ext.rb +2 -0
- data/lib/datadog/core/transport/http/api/endpoint.rb +9 -4
- data/lib/datadog/core/transport/http/api/instance.rb +4 -21
- data/lib/datadog/core/transport/http/builder.rb +9 -5
- data/lib/datadog/core/transport/http/client.rb +80 -0
- data/lib/datadog/core/transport/http.rb +22 -19
- data/lib/datadog/core/transport/response.rb +9 -0
- data/lib/datadog/core/transport/transport.rb +90 -0
- data/lib/datadog/core/utils/array.rb +29 -0
- data/lib/datadog/{appsec/api_security → core/utils}/lru_cache.rb +10 -21
- data/lib/datadog/core/utils/network.rb +3 -1
- data/lib/datadog/core/utils/only_once_successful.rb +8 -2
- data/lib/datadog/core/utils/time.rb +1 -1
- data/lib/datadog/core/utils.rb +2 -0
- data/lib/datadog/core/workers/async.rb +10 -1
- data/lib/datadog/core/workers/interval_loop.rb +44 -3
- data/lib/datadog/core/workers/polling.rb +2 -0
- data/lib/datadog/core/workers/queue.rb +100 -1
- data/lib/datadog/data_streams/configuration/settings.rb +49 -0
- data/lib/datadog/data_streams/configuration.rb +11 -0
- data/lib/datadog/data_streams/ext.rb +11 -0
- data/lib/datadog/data_streams/extensions.rb +16 -0
- data/lib/datadog/data_streams/pathway_context.rb +169 -0
- data/lib/datadog/data_streams/processor.rb +509 -0
- data/lib/datadog/data_streams/transport/http/stats.rb +52 -0
- data/lib/datadog/data_streams/transport/http.rb +40 -0
- data/lib/datadog/data_streams/transport/stats.rb +46 -0
- data/lib/datadog/data_streams.rb +100 -0
- data/lib/datadog/di/component.rb +0 -16
- data/lib/datadog/di/contrib/active_record.rb +31 -5
- data/lib/datadog/di/el/compiler.rb +8 -4
- data/lib/datadog/di/el/evaluator.rb +1 -1
- data/lib/datadog/di/error.rb +9 -0
- data/lib/datadog/di/instrumenter.rb +93 -34
- data/lib/datadog/di/probe.rb +20 -0
- data/lib/datadog/di/probe_builder.rb +2 -1
- data/lib/datadog/di/probe_manager.rb +47 -33
- data/lib/datadog/di/probe_notification_builder.rb +77 -25
- data/lib/datadog/di/proc_responder.rb +32 -0
- data/lib/datadog/di/remote.rb +89 -84
- data/lib/datadog/di/transport/diagnostics.rb +8 -36
- data/lib/datadog/di/transport/http/diagnostics.rb +1 -33
- data/lib/datadog/di/transport/http/input.rb +1 -33
- data/lib/datadog/di/transport/http.rb +32 -17
- data/lib/datadog/di/transport/input.rb +67 -34
- data/lib/datadog/di.rb +61 -5
- 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 +70 -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 +14 -0
- data/lib/datadog/open_feature/native_evaluator.rb +38 -0
- data/lib/datadog/open_feature/noop_evaluator.rb +26 -0
- data/lib/datadog/open_feature/provider.rb +141 -0
- data/lib/datadog/open_feature/remote.rb +67 -0
- data/lib/datadog/open_feature/resolution_details.rb +35 -0
- data/lib/datadog/open_feature/transport.rb +70 -0
- data/lib/datadog/open_feature.rb +19 -0
- data/lib/datadog/opentelemetry/api/baggage.rb +1 -1
- data/lib/datadog/opentelemetry/configuration/settings.rb +159 -0
- data/lib/datadog/opentelemetry/metrics.rb +117 -0
- data/lib/datadog/opentelemetry/sdk/configurator.rb +25 -1
- data/lib/datadog/opentelemetry/sdk/metrics_exporter.rb +35 -0
- data/lib/datadog/opentelemetry.rb +3 -0
- data/lib/datadog/profiling/collectors/code_provenance.rb +41 -7
- data/lib/datadog/profiling/collectors/cpu_and_wall_time_worker.rb +1 -1
- data/lib/datadog/profiling/collectors/idle_sampling_helper.rb +1 -1
- data/lib/datadog/profiling/collectors/info.rb +2 -1
- data/lib/datadog/profiling/component.rb +12 -11
- data/lib/datadog/profiling/http_transport.rb +4 -1
- data/lib/datadog/profiling/profiler.rb +4 -0
- data/lib/datadog/profiling/tag_builder.rb +36 -3
- data/lib/datadog/profiling.rb +1 -2
- data/lib/datadog/single_step_instrument.rb +1 -1
- data/lib/datadog/tracing/configuration/ext.rb +9 -0
- data/lib/datadog/tracing/configuration/settings.rb +74 -0
- data/lib/datadog/tracing/contrib/action_pack/action_controller/instrumentation.rb +4 -4
- data/lib/datadog/tracing/contrib/action_pack/utils.rb +1 -2
- data/lib/datadog/tracing/contrib/active_job/log_injection.rb +21 -7
- data/lib/datadog/tracing/contrib/active_job/patcher.rb +5 -1
- data/lib/datadog/tracing/contrib/aws/instrumentation.rb +4 -2
- data/lib/datadog/tracing/contrib/ethon/easy_patch.rb +4 -1
- data/lib/datadog/tracing/contrib/excon/configuration/settings.rb +11 -3
- data/lib/datadog/tracing/contrib/extensions.rb +10 -2
- data/lib/datadog/tracing/contrib/faraday/configuration/settings.rb +11 -7
- data/lib/datadog/tracing/contrib/grape/configuration/settings.rb +7 -3
- data/lib/datadog/tracing/contrib/graphql/unified_trace.rb +22 -17
- data/lib/datadog/tracing/contrib/http/configuration/settings.rb +11 -3
- data/lib/datadog/tracing/contrib/httpclient/configuration/settings.rb +11 -3
- data/lib/datadog/tracing/contrib/httprb/configuration/settings.rb +11 -3
- data/lib/datadog/tracing/contrib/kafka/instrumentation/consumer.rb +66 -0
- data/lib/datadog/tracing/contrib/kafka/instrumentation/producer.rb +66 -0
- data/lib/datadog/tracing/contrib/kafka/patcher.rb +14 -0
- data/lib/datadog/tracing/contrib/karafka/framework.rb +30 -0
- data/lib/datadog/tracing/contrib/karafka/monitor.rb +11 -0
- data/lib/datadog/tracing/contrib/karafka/patcher.rb +35 -4
- data/lib/datadog/tracing/contrib/rack/middlewares.rb +59 -27
- data/lib/datadog/tracing/contrib/rack/route_inference.rb +53 -0
- data/lib/datadog/tracing/contrib/rails/middlewares.rb +2 -2
- data/lib/datadog/tracing/contrib/rest_client/request_patch.rb +4 -1
- data/lib/datadog/tracing/contrib/roda/instrumentation.rb +3 -1
- data/lib/datadog/tracing/contrib/sinatra/tracer_middleware.rb +3 -1
- data/lib/datadog/tracing/contrib/status_range_matcher.rb +9 -1
- data/lib/datadog/tracing/contrib/utils/quantization/hash.rb +3 -1
- 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 +49 -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/diagnostics/environment_logger.rb +1 -1
- data/lib/datadog/tracing/metadata/ext.rb +1 -1
- data/lib/datadog/tracing/remote.rb +1 -9
- data/lib/datadog/tracing/span_event.rb +2 -2
- data/lib/datadog/tracing/span_operation.rb +9 -4
- data/lib/datadog/tracing/trace_operation.rb +44 -6
- data/lib/datadog/tracing/tracer.rb +42 -16
- data/lib/datadog/tracing/transport/http/client.rb +12 -26
- data/lib/datadog/tracing/transport/http/traces.rb +2 -50
- data/lib/datadog/tracing/transport/http.rb +15 -9
- data/lib/datadog/tracing/transport/io/client.rb +1 -1
- data/lib/datadog/tracing/transport/trace_formatter.rb +11 -0
- data/lib/datadog/tracing/transport/traces.rb +9 -71
- data/lib/datadog/tracing/workers/trace_writer.rb +5 -0
- data/lib/datadog/tracing/writer.rb +1 -0
- data/lib/datadog/version.rb +2 -2
- data/lib/datadog.rb +2 -0
- metadata +78 -21
- data/lib/datadog/core/remote/transport/http/api.rb +0 -53
- data/lib/datadog/core/remote/transport/http/client.rb +0 -49
- data/lib/datadog/core/telemetry/transport/http/api.rb +0 -43
- data/lib/datadog/core/telemetry/transport/http/client.rb +0 -49
- data/lib/datadog/core/transport/http/api/spec.rb +0 -36
- data/lib/datadog/di/transport/http/api.rb +0 -42
- data/lib/datadog/di/transport/http/client.rb +0 -47
- data/lib/datadog/opentelemetry/api/baggage.rbs +0 -26
- data/lib/datadog/tracing/transport/http/api.rb +0 -44
|
@@ -1,31 +1,46 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require_relative '../../core/encoding'
|
|
4
|
+
require_relative '../../core/transport/http'
|
|
3
5
|
require_relative 'diagnostics'
|
|
4
6
|
require_relative 'input'
|
|
5
|
-
require_relative 'http/api'
|
|
6
|
-
require_relative '../../core/transport/http'
|
|
7
7
|
|
|
8
8
|
module Datadog
|
|
9
9
|
module DI
|
|
10
10
|
module Transport
|
|
11
11
|
# Namespace for HTTP transport components
|
|
12
12
|
module HTTP
|
|
13
|
-
|
|
13
|
+
DIAGNOSTICS = Diagnostics::API::Endpoint.new(
|
|
14
|
+
'/debugger/v1/diagnostics',
|
|
15
|
+
Core::Encoding::JSONEncoder,
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
INPUT = Input::API::Endpoint.new(
|
|
19
|
+
'/debugger/v2/input',
|
|
20
|
+
Core::Encoding::JSONEncoder,
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
LEGACY_INPUT = Input::API::Endpoint.new(
|
|
24
|
+
# We used to use /debugger/v1/input, but now input
|
|
25
|
+
# payloads should be going to the diagnostics endpoint
|
|
26
|
+
# which I gather performs data redaction.
|
|
27
|
+
'/debugger/v1/diagnostics',
|
|
28
|
+
Core::Encoding::JSONEncoder,
|
|
29
|
+
)
|
|
14
30
|
|
|
15
31
|
# Builds a new Transport::HTTP::Client with default settings
|
|
16
32
|
# Pass a block to override any settings.
|
|
17
|
-
def diagnostics(
|
|
33
|
+
def self.diagnostics(
|
|
18
34
|
agent_settings:,
|
|
19
35
|
logger:,
|
|
20
|
-
api_version: nil,
|
|
21
36
|
headers: nil
|
|
22
37
|
)
|
|
23
|
-
Core::Transport::HTTP.build(
|
|
38
|
+
Core::Transport::HTTP.build(
|
|
24
39
|
logger: logger,
|
|
25
|
-
agent_settings: agent_settings,
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
transport.api
|
|
40
|
+
agent_settings: agent_settings,
|
|
41
|
+
headers: headers,
|
|
42
|
+
) do |transport|
|
|
43
|
+
transport.api 'diagnostics', DIAGNOSTICS
|
|
29
44
|
|
|
30
45
|
# Call block to apply any customization, if provided
|
|
31
46
|
yield(transport) if block_given?
|
|
@@ -34,18 +49,18 @@ module Datadog
|
|
|
34
49
|
|
|
35
50
|
# Builds a new Transport::HTTP::Client with default settings
|
|
36
51
|
# Pass a block to override any settings.
|
|
37
|
-
def input(
|
|
52
|
+
def self.input(
|
|
38
53
|
agent_settings:,
|
|
39
54
|
logger:,
|
|
40
|
-
api_version: nil,
|
|
41
55
|
headers: nil
|
|
42
56
|
)
|
|
43
|
-
Core::Transport::HTTP.build(
|
|
57
|
+
Core::Transport::HTTP.build(
|
|
44
58
|
logger: logger,
|
|
45
|
-
agent_settings: agent_settings,
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
transport.api
|
|
59
|
+
agent_settings: agent_settings,
|
|
60
|
+
headers: headers,
|
|
61
|
+
) do |transport|
|
|
62
|
+
transport.api 'input', INPUT, fallback: 'legacy_input', default: true
|
|
63
|
+
transport.api 'legacy_input', LEGACY_INPUT
|
|
49
64
|
|
|
50
65
|
# Call block to apply any customization, if provided
|
|
51
66
|
yield(transport) if block_given?
|
|
@@ -1,7 +1,13 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require_relative '../../core/chunker'
|
|
4
|
+
require_relative '../../core/encoding'
|
|
5
|
+
require_relative '../../core/tag_builder'
|
|
3
6
|
require_relative '../../core/transport/parcel'
|
|
4
|
-
require_relative '
|
|
7
|
+
require_relative '../../core/transport/request'
|
|
8
|
+
require_relative '../../core/transport/transport'
|
|
9
|
+
require_relative '../error'
|
|
10
|
+
require_relative 'http/input'
|
|
5
11
|
|
|
6
12
|
module Datadog
|
|
7
13
|
module DI
|
|
@@ -21,47 +27,74 @@ module Datadog
|
|
|
21
27
|
end
|
|
22
28
|
end
|
|
23
29
|
|
|
24
|
-
class Transport
|
|
25
|
-
|
|
30
|
+
class Transport < Core::Transport::Transport
|
|
31
|
+
# The limit on an individual snapshot payload, aka "log line",
|
|
32
|
+
# is 1 MB.
|
|
33
|
+
#
|
|
34
|
+
# TODO There is an RFC for snapshot pruning that should be
|
|
35
|
+
# implemented to reduce the size of snapshots to be below this
|
|
36
|
+
# limit, so that we can send a portion of the captured data
|
|
37
|
+
# rather than dropping the snapshot entirely.
|
|
38
|
+
MAX_SERIALIZED_SNAPSHOT_SIZE = 1024 * 1024
|
|
26
39
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
40
|
+
# The maximum chunk (batch) size that intake permits is 5 MB.
|
|
41
|
+
#
|
|
42
|
+
# Two bytes are for the [ and ] of JSON array syntax.
|
|
43
|
+
MAX_CHUNK_SIZE = 5 * 1024 * 1024 - 2
|
|
30
44
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
@apis[HTTP::API::INPUT]
|
|
36
|
-
end
|
|
45
|
+
# Try to send smaller payloads to avoid large network requests.
|
|
46
|
+
# If a payload is larger than default chunk size but is under the
|
|
47
|
+
# max chunk size, it will still get sent out.
|
|
48
|
+
DEFAULT_CHUNK_SIZE = 2 * 1024 * 1024
|
|
37
49
|
|
|
38
50
|
def send_input(payload, tags)
|
|
39
|
-
|
|
40
|
-
parcel = EncodedParcel.new(json)
|
|
51
|
+
# Tags are the same for all chunks, serialize them one time.
|
|
41
52
|
serialized_tags = Core::TagBuilder.serialize_tags(tags)
|
|
53
|
+
|
|
54
|
+
encoder = Core::Encoding::JSONEncoder
|
|
55
|
+
encoded_snapshots = Core::Utils::Array.filter_map(payload) do |snapshot|
|
|
56
|
+
encoded = encoder.encode(snapshot)
|
|
57
|
+
if encoded.length > MAX_SERIALIZED_SNAPSHOT_SIZE
|
|
58
|
+
# Drop the snapshot.
|
|
59
|
+
# TODO report via telemetry metric?
|
|
60
|
+
logger.debug { "di: dropping too big snapshot" }
|
|
61
|
+
nil
|
|
62
|
+
else
|
|
63
|
+
encoded
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
Datadog::Core::Chunker.chunk_by_size(
|
|
68
|
+
encoded_snapshots, DEFAULT_CHUNK_SIZE,
|
|
69
|
+
).each do |chunk|
|
|
70
|
+
# We drop snapshots that are too big earlier.
|
|
71
|
+
# The limit on chunked payload length here is greater
|
|
72
|
+
# than the limit on snapshot size, therefore no chunks
|
|
73
|
+
# can exceed limits here.
|
|
74
|
+
chunked_payload = encoder.join(chunk)
|
|
75
|
+
|
|
76
|
+
# We need to rescue exceptions for each chunk so that
|
|
77
|
+
# subsequent chunks are attempted to be sent.
|
|
78
|
+
begin
|
|
79
|
+
send_input_chunk(chunked_payload, serialized_tags)
|
|
80
|
+
rescue => exc
|
|
81
|
+
logger.debug { "di: failed to send snapshot chunk: #{exc.class}: #{exc} (at #{exc.backtrace.first})" }
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
payload
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
def send_input_chunk(chunked_payload, serialized_tags)
|
|
89
|
+
parcel = EncodedParcel.new(chunked_payload)
|
|
42
90
|
request = Request.new(parcel, serialized_tags)
|
|
43
91
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
raise Error::AgentCommunicationError, "send_input failed: #{begin
|
|
50
|
-
response.code
|
|
51
|
-
rescue
|
|
52
|
-
"???"
|
|
53
|
-
end}: #{response.payload}"
|
|
92
|
+
client.send_request(:input, request).tap do |response|
|
|
93
|
+
if downgrade?(response)
|
|
94
|
+
downgrade!
|
|
95
|
+
return send_input_chunk(chunked_payload, serialized_tags)
|
|
96
|
+
end
|
|
54
97
|
end
|
|
55
|
-
rescue Error::AgentCommunicationError
|
|
56
|
-
raise
|
|
57
|
-
# Datadog::Core::Transport does not perform any exception mapping,
|
|
58
|
-
# therefore we could have any exception here from failure to parse
|
|
59
|
-
# agent URI for example.
|
|
60
|
-
# If we ever implement retries for network errors, we should distinguish
|
|
61
|
-
# actual network errors from non-network errors that are raised by
|
|
62
|
-
# transport code.
|
|
63
|
-
rescue => exc
|
|
64
|
-
raise Error::AgentCommunicationError, "send_input failed: #{exc.class}: #{exc}"
|
|
65
98
|
end
|
|
66
99
|
end
|
|
67
100
|
end
|
data/lib/datadog/di.rb
CHANGED
|
@@ -9,16 +9,13 @@ module Datadog
|
|
|
9
9
|
#
|
|
10
10
|
# @api private
|
|
11
11
|
module DI
|
|
12
|
+
INSTRUMENTED_COUNTERS_LOCK = Mutex.new
|
|
13
|
+
|
|
12
14
|
class << self
|
|
13
15
|
def enabled?
|
|
14
16
|
Datadog.configuration.dynamic_instrumentation.enabled
|
|
15
17
|
end
|
|
16
|
-
end
|
|
17
|
-
|
|
18
|
-
# Expose DI to global shared objects
|
|
19
|
-
Extensions.activate!
|
|
20
18
|
|
|
21
|
-
class << self
|
|
22
19
|
# This method is called from DI Remote handler to issue DI operations
|
|
23
20
|
# to the probe manager (add or remove probes).
|
|
24
21
|
#
|
|
@@ -31,6 +28,65 @@ module Datadog
|
|
|
31
28
|
def component
|
|
32
29
|
Datadog.send(:components).dynamic_instrumentation
|
|
33
30
|
end
|
|
31
|
+
|
|
32
|
+
# Track how many outstanding instrumentations are in DI.
|
|
33
|
+
#
|
|
34
|
+
# It is hard to find the actual instrumentations - there is no
|
|
35
|
+
# method provided by Ruby to list all trace points, and we would
|
|
36
|
+
# need to manually track our instrumentation modules for method probes.
|
|
37
|
+
# Plus, tracking the modules could create active references to
|
|
38
|
+
# instrumentation, which is not desired.
|
|
39
|
+
#
|
|
40
|
+
# A simpler solution is to maintain a counter which is increased
|
|
41
|
+
# whenever a probe is installed and decreased when a probe is removed.
|
|
42
|
+
#
|
|
43
|
+
# This counter does not include pending probes - being not installed,
|
|
44
|
+
# those pose no concerns to customer applications.
|
|
45
|
+
def instrumented_count(kind = nil)
|
|
46
|
+
INSTRUMENTED_COUNTERS_LOCK.synchronize do
|
|
47
|
+
if defined?(@instrumented_count)
|
|
48
|
+
if kind
|
|
49
|
+
validate_kind!(kind)
|
|
50
|
+
@instrumented_count[kind] || 0
|
|
51
|
+
else
|
|
52
|
+
@instrumented_count.inject(0) do |sum, (_kind, count)|
|
|
53
|
+
sum + count
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
else
|
|
57
|
+
0
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def instrumented_count_inc(kind)
|
|
63
|
+
validate_kind!(kind)
|
|
64
|
+
INSTRUMENTED_COUNTERS_LOCK.synchronize do
|
|
65
|
+
@instrumented_count = Hash.new(0) unless defined?(@instrumented_count)
|
|
66
|
+
@instrumented_count[kind] += 1
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def instrumented_count_dec(kind)
|
|
71
|
+
validate_kind!(kind)
|
|
72
|
+
INSTRUMENTED_COUNTERS_LOCK.synchronize do
|
|
73
|
+
@instrumented_count = Hash.new(0) unless defined?(@instrumented_count)
|
|
74
|
+
if @instrumented_count[kind] <= 0
|
|
75
|
+
Datadog.logger.debug { "di: attempting to decrease instrumented count below zero for #{kind}" }
|
|
76
|
+
return
|
|
77
|
+
end
|
|
78
|
+
@instrumented_count[kind] -= 1
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
private def validate_kind!(kind)
|
|
83
|
+
unless %i[line method].include?(kind)
|
|
84
|
+
raise ArgumentError, "Invalid kind: #{kind}"
|
|
85
|
+
end
|
|
86
|
+
end
|
|
34
87
|
end
|
|
88
|
+
|
|
89
|
+
# Expose DI to global shared objects
|
|
90
|
+
Extensions.activate!
|
|
35
91
|
end
|
|
36
92
|
end
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative 'transport'
|
|
4
|
+
require_relative 'evaluation_engine'
|
|
5
|
+
require_relative 'exposures/buffer'
|
|
6
|
+
require_relative 'exposures/worker'
|
|
7
|
+
require_relative 'exposures/deduplicator'
|
|
8
|
+
require_relative 'exposures/reporter'
|
|
9
|
+
|
|
10
|
+
module Datadog
|
|
11
|
+
module OpenFeature
|
|
12
|
+
# This class is the entry point for the OpenFeature component
|
|
13
|
+
class Component
|
|
14
|
+
attr_reader :engine
|
|
15
|
+
|
|
16
|
+
def self.build(settings, agent_settings, logger:, telemetry:)
|
|
17
|
+
return unless settings.respond_to?(:open_feature) && settings.open_feature.enabled
|
|
18
|
+
|
|
19
|
+
unless settings.respond_to?(:remote) && settings.remote.enabled
|
|
20
|
+
message = 'OpenFeature could not be enabled as Remote Configuration is currently disabled. ' \
|
|
21
|
+
'To enable Remote Configuration, see https://docs.datadoghq.com/remote_configuration/.'
|
|
22
|
+
|
|
23
|
+
logger.warn(message)
|
|
24
|
+
return
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
if RUBY_ENGINE != 'ruby'
|
|
28
|
+
message = 'OpenFeature could not be enabled as MRI is required, ' \
|
|
29
|
+
"but running on #{RUBY_ENGINE.inspect}"
|
|
30
|
+
|
|
31
|
+
logger.warn(message)
|
|
32
|
+
return
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
if (libdatadog_api_failure = Core::LIBDATADOG_API_FAILURE)
|
|
36
|
+
message = 'OpenFeature could not be enabled as `libdatadog` is not loaded: ' \
|
|
37
|
+
"#{libdatadog_api_failure.inspect}. For help solving this issue, " \
|
|
38
|
+
'please contact Datadog support at https://docs.datadoghq.com/help/.'
|
|
39
|
+
|
|
40
|
+
logger.warn(message)
|
|
41
|
+
return
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
new(settings, agent_settings, logger: logger, telemetry: telemetry)
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def initialize(settings, agent_settings, logger:, telemetry:)
|
|
48
|
+
transport = Transport::HTTP.build(agent_settings: agent_settings, logger: logger)
|
|
49
|
+
@worker = Exposures::Worker.new(settings: settings, transport: transport, telemetry: telemetry, logger: logger)
|
|
50
|
+
|
|
51
|
+
reporter = Exposures::Reporter.new(@worker, telemetry: telemetry, logger: logger)
|
|
52
|
+
@engine = EvaluationEngine.new(reporter, telemetry: telemetry, logger: logger)
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def shutdown!
|
|
56
|
+
@worker.graceful_shutdown
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
end
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Datadog
|
|
4
|
+
module OpenFeature
|
|
5
|
+
module Configuration
|
|
6
|
+
# A settings class for the OpenFeature component.
|
|
7
|
+
module Settings
|
|
8
|
+
def self.extended(base)
|
|
9
|
+
base = base.singleton_class unless base.is_a?(Class)
|
|
10
|
+
add_settings!(base)
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def self.add_settings!(base)
|
|
14
|
+
base.class_eval do
|
|
15
|
+
settings :open_feature do
|
|
16
|
+
option :enabled do |o|
|
|
17
|
+
o.type :bool
|
|
18
|
+
o.env 'DD_EXPERIMENTAL_FLAGGING_PROVIDER_ENABLED'
|
|
19
|
+
o.default false
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative 'ext'
|
|
4
|
+
require_relative 'noop_evaluator'
|
|
5
|
+
require_relative 'native_evaluator'
|
|
6
|
+
require_relative 'resolution_details'
|
|
7
|
+
|
|
8
|
+
module Datadog
|
|
9
|
+
module OpenFeature
|
|
10
|
+
# This class performs the evaluation of the feature flag
|
|
11
|
+
class EvaluationEngine
|
|
12
|
+
# Steep: https://github.com/soutaro/steep/issues/1880
|
|
13
|
+
ReconfigurationError = Class.new(StandardError) # steep:ignore IncompatibleAssignment
|
|
14
|
+
|
|
15
|
+
ALLOWED_TYPES = %i[boolean string number float integer object].freeze
|
|
16
|
+
|
|
17
|
+
def initialize(reporter, telemetry:, logger:)
|
|
18
|
+
@reporter = reporter
|
|
19
|
+
@telemetry = telemetry
|
|
20
|
+
@logger = logger
|
|
21
|
+
|
|
22
|
+
@evaluator = NoopEvaluator.new(nil)
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def fetch_value(flag_key, default_value:, expected_type:, evaluation_context: nil)
|
|
26
|
+
unless ALLOWED_TYPES.include?(expected_type)
|
|
27
|
+
message = "unknown type #{expected_type.inspect}, allowed types #{ALLOWED_TYPES.join(", ")}"
|
|
28
|
+
return ResolutionDetails.build_error(
|
|
29
|
+
value: default_value, error_code: Ext::UNKNOWN_TYPE, error_message: message
|
|
30
|
+
)
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
context = evaluation_context&.fields.to_h
|
|
34
|
+
result = @evaluator.get_assignment(
|
|
35
|
+
flag_key, default_value: default_value, context: context, expected_type: expected_type
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
@reporter.report(result, flag_key: flag_key, context: evaluation_context)
|
|
39
|
+
|
|
40
|
+
result
|
|
41
|
+
rescue => e
|
|
42
|
+
@telemetry.report(e, description: 'OpenFeature: Failed to fetch flag value')
|
|
43
|
+
|
|
44
|
+
ResolutionDetails.build_error(
|
|
45
|
+
value: default_value, error_code: Ext::GENERAL, error_message: e.message
|
|
46
|
+
)
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
# NOTE: In a currect implementation configuration is expected to be a raw
|
|
50
|
+
# JSON string containing feature flags (straight from the remote config)
|
|
51
|
+
# in the format expected by `libdatadog` without any modifications
|
|
52
|
+
def reconfigure!(configuration)
|
|
53
|
+
if configuration.nil?
|
|
54
|
+
@logger.debug('OpenFeature: Removing configuration')
|
|
55
|
+
|
|
56
|
+
return @evaluator = NoopEvaluator.new(configuration)
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
@evaluator = NativeEvaluator.new(configuration)
|
|
60
|
+
rescue => e
|
|
61
|
+
message = 'OpenFeature: Failed to reconfigure, reverting to the previous configuration'
|
|
62
|
+
|
|
63
|
+
@logger.error("#{message}, #{e.class}: #{e.message}")
|
|
64
|
+
@telemetry.report(e, description: "#{message} (#{e.class})")
|
|
65
|
+
|
|
66
|
+
raise ReconfigurationError, e.message
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
end
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Datadog
|
|
4
|
+
module OpenFeature
|
|
5
|
+
module Exposures
|
|
6
|
+
# This class builds a batch of exposures and context to be sent to the Agent
|
|
7
|
+
class BatchBuilder
|
|
8
|
+
def initialize(settings)
|
|
9
|
+
@context = build_context(settings)
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def payload_for(events)
|
|
13
|
+
{
|
|
14
|
+
context: @context,
|
|
15
|
+
exposures: events
|
|
16
|
+
}
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
private
|
|
20
|
+
|
|
21
|
+
def build_context(settings)
|
|
22
|
+
context = {}
|
|
23
|
+
context[:env] = settings.env if settings.env
|
|
24
|
+
context[:service] = settings.service if settings.service
|
|
25
|
+
context[:version] = settings.version if settings.version
|
|
26
|
+
|
|
27
|
+
context
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative '../../core/buffer/cruby'
|
|
4
|
+
|
|
5
|
+
module Datadog
|
|
6
|
+
module OpenFeature
|
|
7
|
+
module Exposures
|
|
8
|
+
# This class is a buffer for exposure events that evicts at random and
|
|
9
|
+
# keeps track of the number of dropped events
|
|
10
|
+
#
|
|
11
|
+
# WARNING: This class does not work as intended on JRuby
|
|
12
|
+
class Buffer < Core::Buffer::CRuby
|
|
13
|
+
DEFAULT_LIMIT = 1_000
|
|
14
|
+
|
|
15
|
+
attr_reader :dropped_count
|
|
16
|
+
|
|
17
|
+
def initialize(limit = DEFAULT_LIMIT)
|
|
18
|
+
@dropped = 0
|
|
19
|
+
@dropped_count = 0
|
|
20
|
+
|
|
21
|
+
super
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
protected
|
|
25
|
+
|
|
26
|
+
def drain!
|
|
27
|
+
drained = super
|
|
28
|
+
|
|
29
|
+
@dropped_count = @dropped
|
|
30
|
+
@dropped = 0
|
|
31
|
+
|
|
32
|
+
drained
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def replace!(item)
|
|
36
|
+
@dropped += 1
|
|
37
|
+
|
|
38
|
+
super
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative '../../core/utils/lru_cache'
|
|
4
|
+
|
|
5
|
+
module Datadog
|
|
6
|
+
module OpenFeature
|
|
7
|
+
module Exposures
|
|
8
|
+
# This class is a deduplication buffer based on LRU cache for exposure events
|
|
9
|
+
class Deduplicator
|
|
10
|
+
DEFAULT_CACHE_LIMIT = 1_000
|
|
11
|
+
|
|
12
|
+
def initialize(limit: DEFAULT_CACHE_LIMIT)
|
|
13
|
+
@cache = Datadog::Core::Utils::LRUCache.new(limit)
|
|
14
|
+
@mutex = Mutex.new
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def duplicate?(key, value)
|
|
18
|
+
@mutex.synchronize do
|
|
19
|
+
stored = @cache[key]
|
|
20
|
+
return true if stored == value
|
|
21
|
+
|
|
22
|
+
@cache[key] = value
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
false
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative '../../core/utils/time'
|
|
4
|
+
|
|
5
|
+
module Datadog
|
|
6
|
+
module OpenFeature
|
|
7
|
+
module Exposures
|
|
8
|
+
# A data model for an exposure event
|
|
9
|
+
module Event
|
|
10
|
+
TARGETING_KEY_FIELD = 'targeting_key'
|
|
11
|
+
ALLOWED_FIELD_TYPES = [String, Integer, Float, TrueClass, FalseClass].freeze
|
|
12
|
+
|
|
13
|
+
class << self
|
|
14
|
+
def cache_key(result, flag_key:, context:)
|
|
15
|
+
"#{flag_key}:#{context.targeting_key}"
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def cache_value(result, flag_key:, context:)
|
|
19
|
+
"#{result.allocation_key}:#{result.variant}"
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def build(result, flag_key:, context:)
|
|
23
|
+
{
|
|
24
|
+
timestamp: current_timestamp_ms,
|
|
25
|
+
allocation: {
|
|
26
|
+
key: result.allocation_key
|
|
27
|
+
},
|
|
28
|
+
flag: {
|
|
29
|
+
key: flag_key
|
|
30
|
+
},
|
|
31
|
+
variant: {
|
|
32
|
+
key: result.variant
|
|
33
|
+
},
|
|
34
|
+
subject: {
|
|
35
|
+
id: context.targeting_key,
|
|
36
|
+
attributes: extract_attributes(context)
|
|
37
|
+
}
|
|
38
|
+
}.freeze
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
private
|
|
42
|
+
|
|
43
|
+
# NOTE: We take all filds of the context that does not support nesting
|
|
44
|
+
# and will ignore targeting key as it will be set as `subject.id`
|
|
45
|
+
def extract_attributes(context)
|
|
46
|
+
context.fields.select do |key, value|
|
|
47
|
+
next false if key == TARGETING_KEY_FIELD
|
|
48
|
+
|
|
49
|
+
ALLOWED_FIELD_TYPES.include?(value.class)
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def current_timestamp_ms
|
|
54
|
+
(Datadog::Core::Utils::Time.now.to_f * 1000).to_i
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
end
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative 'event'
|
|
4
|
+
require_relative 'deduplicator'
|
|
5
|
+
|
|
6
|
+
module Datadog
|
|
7
|
+
module OpenFeature
|
|
8
|
+
module Exposures
|
|
9
|
+
# This class is responsible for reporting exposures to the Agent
|
|
10
|
+
class Reporter
|
|
11
|
+
def initialize(worker, telemetry:, logger:)
|
|
12
|
+
@worker = worker
|
|
13
|
+
@logger = logger
|
|
14
|
+
@telemetry = telemetry
|
|
15
|
+
@deduplicator = Deduplicator.new
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
# NOTE: Reporting expects evaluation context to be always present, but it
|
|
19
|
+
# might be missing depending on the customer way of using flags evaluation API.
|
|
20
|
+
# In addition to that the evaluation result must be marked for reporting.
|
|
21
|
+
def report(result, flag_key:, context:)
|
|
22
|
+
return false if context.nil?
|
|
23
|
+
return false unless result.log?
|
|
24
|
+
|
|
25
|
+
key = Event.cache_key(result, flag_key: flag_key, context: context)
|
|
26
|
+
value = Event.cache_value(result, flag_key: flag_key, context: context)
|
|
27
|
+
return false if @deduplicator.duplicate?(key, value)
|
|
28
|
+
|
|
29
|
+
event = Event.build(result, flag_key: flag_key, context: context)
|
|
30
|
+
@worker.enqueue(event)
|
|
31
|
+
rescue => e
|
|
32
|
+
@logger.debug { "OpenFeature: Failed to report resolution details: #{e.class}: #{e.message}" }
|
|
33
|
+
@telemetry.report(e, description: 'OpenFeature: Failed to report resolution details')
|
|
34
|
+
|
|
35
|
+
false
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|