datadog 2.21.0 → 2.23.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 +106 -2
- data/ext/LIBDATADOG_DEVELOPMENT.md +3 -0
- data/ext/datadog_profiling_native_extension/collectors_discrete_dynamic_sampler.c +1 -1
- data/ext/datadog_profiling_native_extension/collectors_stack.c +4 -0
- data/ext/datadog_profiling_native_extension/datadog_ruby_common.h +1 -1
- data/ext/datadog_profiling_native_extension/extconf.rb +6 -4
- data/ext/datadog_profiling_native_extension/heap_recorder.c +1 -1
- data/ext/libdatadog_api/datadog_ruby_common.h +1 -1
- data/ext/libdatadog_api/ddsketch.c +106 -0
- data/ext/libdatadog_api/feature_flags.c +554 -0
- data/ext/libdatadog_api/feature_flags.h +5 -0
- data/ext/libdatadog_api/init.c +5 -0
- data/ext/libdatadog_api/library_config.c +34 -25
- data/ext/libdatadog_api/process_discovery.c +19 -13
- data/ext/libdatadog_extconf_helpers.rb +1 -1
- data/lib/datadog/appsec/api_security/endpoint_collection/grape_route_serializer.rb +26 -0
- data/lib/datadog/appsec/api_security/endpoint_collection/rails_collector.rb +59 -0
- data/lib/datadog/appsec/api_security/endpoint_collection/rails_route_serializer.rb +29 -0
- data/lib/datadog/appsec/api_security/endpoint_collection/sinatra_route_serializer.rb +26 -0
- data/lib/datadog/appsec/api_security/endpoint_collection.rb +10 -0
- 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/waf_rules/README.md +30 -36
- data/lib/datadog/appsec/assets/waf_rules/recommended.json +359 -4
- data/lib/datadog/appsec/assets/waf_rules/strict.json +43 -2
- data/lib/datadog/appsec/assets.rb +1 -1
- data/lib/datadog/appsec/compressed_json.rb +1 -1
- data/lib/datadog/appsec/configuration/settings.rb +9 -0
- data/lib/datadog/appsec/contrib/active_record/instrumentation.rb +3 -1
- data/lib/datadog/appsec/contrib/excon/ssrf_detection_middleware.rb +3 -2
- data/lib/datadog/appsec/contrib/faraday/ssrf_detection_middleware.rb +3 -1
- data/lib/datadog/appsec/contrib/graphql/gateway/watcher.rb +3 -1
- data/lib/datadog/appsec/contrib/rack/gateway/watcher.rb +9 -4
- data/lib/datadog/appsec/contrib/rack/request_middleware.rb +5 -1
- data/lib/datadog/appsec/contrib/rails/gateway/watcher.rb +7 -2
- data/lib/datadog/appsec/contrib/rails/patcher.rb +30 -0
- data/lib/datadog/appsec/contrib/rest_client/request_ssrf_detection_patch.rb +3 -1
- data/lib/datadog/appsec/contrib/sinatra/gateway/watcher.rb +10 -4
- data/lib/datadog/appsec/event.rb +12 -14
- data/lib/datadog/appsec/metrics/collector.rb +19 -3
- data/lib/datadog/appsec/metrics/telemetry_exporter.rb +2 -1
- data/lib/datadog/appsec/monitor/gateway/watcher.rb +4 -4
- data/lib/datadog/appsec/remote.rb +29 -13
- data/lib/datadog/appsec/response.rb +18 -4
- data/lib/datadog/appsec/security_engine/result.rb +28 -9
- data/lib/datadog/appsec/security_engine/runner.rb +17 -7
- data/lib/datadog/appsec/security_event.rb +5 -7
- data/lib/datadog/core/configuration/components.rb +44 -9
- data/lib/datadog/core/configuration/config_helper.rb +1 -1
- data/lib/datadog/core/configuration/settings.rb +14 -0
- data/lib/datadog/core/configuration/stable_config.rb +10 -0
- data/lib/datadog/core/configuration/supported_configurations.rb +330 -299
- data/lib/datadog/core/configuration.rb +1 -1
- data/lib/datadog/core/ddsketch.rb +19 -0
- data/lib/datadog/core/environment/ext.rb +6 -0
- data/lib/datadog/core/environment/process.rb +79 -0
- data/lib/datadog/core/environment/yjit.rb +2 -1
- data/lib/datadog/core/feature_flags.rb +61 -0
- data/lib/datadog/core/pin.rb +4 -8
- data/lib/datadog/core/process_discovery.rb +4 -2
- data/lib/datadog/core/remote/client/capabilities.rb +7 -0
- data/lib/datadog/core/remote/component.rb +4 -6
- 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 +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 +18 -3
- data/lib/datadog/core/telemetry/emitter.rb +6 -6
- data/lib/datadog/core/telemetry/event/app_endpoints_loaded.rb +30 -0
- data/lib/datadog/core/telemetry/event/app_started.rb +52 -49
- data/lib/datadog/core/telemetry/event/synth_app_client_configuration_change.rb +1 -1
- data/lib/datadog/core/telemetry/event.rb +1 -0
- data/lib/datadog/core/telemetry/logger.rb +2 -2
- data/lib/datadog/core/telemetry/logging.rb +2 -8
- 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/transport/http/client.rb +69 -0
- data/lib/datadog/core/transport/response.rb +4 -1
- 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 +22 -1
- data/lib/datadog/core/utils/only_once_successful.rb +6 -2
- data/lib/datadog/core/utils.rb +2 -0
- 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/api.rb +33 -0
- data/lib/datadog/data_streams/transport/http/client.rb +21 -0
- data/lib/datadog/data_streams/transport/http/stats.rb +87 -0
- data/lib/datadog/data_streams/transport/http.rb +41 -0
- data/lib/datadog/data_streams/transport/stats.rb +60 -0
- data/lib/datadog/data_streams.rb +100 -0
- data/lib/datadog/di/boot.rb +1 -0
- data/lib/datadog/di/component.rb +14 -16
- data/lib/datadog/di/context.rb +70 -0
- data/lib/datadog/di/el/compiler.rb +164 -0
- data/lib/datadog/di/el/evaluator.rb +159 -0
- data/lib/datadog/di/el/expression.rb +42 -0
- data/lib/datadog/di/el.rb +5 -0
- data/lib/datadog/di/error.rb +29 -0
- data/lib/datadog/di/instrumenter.rb +163 -48
- data/lib/datadog/di/probe.rb +55 -15
- data/lib/datadog/di/probe_builder.rb +39 -1
- data/lib/datadog/di/probe_manager.rb +13 -4
- data/lib/datadog/di/probe_notification_builder.rb +105 -67
- data/lib/datadog/di/proc_responder.rb +32 -0
- data/lib/datadog/di/serializer.rb +151 -7
- 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/http.rb +6 -2
- data/lib/datadog/di/transport/input.rb +64 -4
- 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 +69 -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 +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/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/profiling/collectors/code_provenance.rb +15 -6
- 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/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/component.rb +6 -17
- data/lib/datadog/tracing/configuration/dynamic.rb +2 -2
- data/lib/datadog/tracing/configuration/ext.rb +9 -0
- data/lib/datadog/tracing/configuration/settings.rb +77 -3
- 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/component.rb +2 -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/faraday/configuration/settings.rb +11 -7
- data/lib/datadog/tracing/contrib/grape/configuration/settings.rb +7 -3
- data/lib/datadog/tracing/contrib/graphql/configuration/settings.rb +7 -0
- data/lib/datadog/tracing/contrib/graphql/ext.rb +1 -0
- data/lib/datadog/tracing/contrib/graphql/unified_trace.rb +74 -44
- 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 +32 -0
- 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 +7 -0
- 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/metadata/ext.rb +9 -1
- data/lib/datadog/tracing/transport/http/client.rb +12 -26
- data/lib/datadog/tracing/transport/trace_formatter.rb +11 -0
- data/lib/datadog/tracing/transport/traces.rb +3 -5
- data/lib/datadog/version.rb +2 -2
- data/lib/datadog.rb +2 -0
- metadata +92 -16
- data/ext/libdatadog_api/macos_development.md +0 -26
- 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,60 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'msgpack'
|
|
4
|
+
require 'zlib'
|
|
5
|
+
require_relative '../../core/transport/parcel'
|
|
6
|
+
require_relative '../../core/transport/request'
|
|
7
|
+
|
|
8
|
+
module Datadog
|
|
9
|
+
module DataStreams
|
|
10
|
+
module Transport
|
|
11
|
+
module Stats
|
|
12
|
+
# Parcel for encoded DSM stats payload
|
|
13
|
+
class EncodedParcel
|
|
14
|
+
include Datadog::Core::Transport::Parcel
|
|
15
|
+
|
|
16
|
+
def initialize(data)
|
|
17
|
+
@data = data
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
attr_reader :data
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
# Request for DSM stats
|
|
24
|
+
class Request < Datadog::Core::Transport::Request
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
# Transport for Data Streams Monitoring stats
|
|
28
|
+
class Transport
|
|
29
|
+
attr_reader :client, :apis, :current_api_id, :logger
|
|
30
|
+
|
|
31
|
+
def initialize(apis, default_api, logger:)
|
|
32
|
+
@apis = apis
|
|
33
|
+
@logger = logger
|
|
34
|
+
@default_api = default_api
|
|
35
|
+
@current_api_id = default_api
|
|
36
|
+
|
|
37
|
+
@client = DataStreams::Transport::HTTP::Client.new(current_api, logger: @logger)
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def send_stats(payload)
|
|
41
|
+
# MessagePack encode and gzip compress the payload
|
|
42
|
+
msgpack_data = MessagePack.pack(payload)
|
|
43
|
+
compressed_data = Zlib.gzip(msgpack_data)
|
|
44
|
+
|
|
45
|
+
# Create parcel and request
|
|
46
|
+
parcel = EncodedParcel.new(compressed_data)
|
|
47
|
+
request = Request.new(parcel)
|
|
48
|
+
|
|
49
|
+
# Send to agent
|
|
50
|
+
client.send_stats_payload(request)
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def current_api
|
|
54
|
+
apis[@current_api_id]
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
end
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative 'data_streams/processor'
|
|
4
|
+
require_relative 'data_streams/pathway_context'
|
|
5
|
+
require_relative 'data_streams/configuration/settings'
|
|
6
|
+
require_relative 'data_streams/extensions'
|
|
7
|
+
require_relative 'core/utils/time'
|
|
8
|
+
|
|
9
|
+
module Datadog
|
|
10
|
+
# Datadog Data Streams Monitoring public API.
|
|
11
|
+
#
|
|
12
|
+
# The Datadog team ensures that public methods in this module
|
|
13
|
+
# only receive backwards compatible changes, and breaking changes
|
|
14
|
+
# will only occur in new major versions releases.
|
|
15
|
+
# @public_api
|
|
16
|
+
module DataStreams
|
|
17
|
+
class << self
|
|
18
|
+
# Set a produce checkpoint for Data Streams Monitoring
|
|
19
|
+
#
|
|
20
|
+
# @param type [String] The type of the checkpoint (e.g., 'kafka', 'kinesis', 'sns')
|
|
21
|
+
# @param destination [String] The destination (e.g., topic, exchange, stream name)
|
|
22
|
+
# @param auto_instrumentation [Boolean] Whether this checkpoint was set by auto-instrumentation (default: false)
|
|
23
|
+
# @param tags [Hash] Additional tags to include
|
|
24
|
+
# @yield [key, value] Block to inject context into carrier
|
|
25
|
+
# @return [String, nil] Base64 encoded pathway context or nil if disabled
|
|
26
|
+
# @public_api
|
|
27
|
+
def set_produce_checkpoint(type:, destination:, auto_instrumentation: false, tags: {}, &block)
|
|
28
|
+
processor&.set_produce_checkpoint(
|
|
29
|
+
type: type,
|
|
30
|
+
destination: destination,
|
|
31
|
+
manual_checkpoint: !auto_instrumentation,
|
|
32
|
+
tags: tags,
|
|
33
|
+
&block
|
|
34
|
+
)
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
# Set a consume checkpoint for Data Streams Monitoring
|
|
38
|
+
#
|
|
39
|
+
# @param type [String] The type of the checkpoint (e.g., 'kafka', 'kinesis', 'sns')
|
|
40
|
+
# @param source [String] The source (e.g., topic, exchange, stream name)
|
|
41
|
+
# @param auto_instrumentation [Boolean] Whether this checkpoint was set by auto-instrumentation (default: false)
|
|
42
|
+
# @param tags [Hash] Additional tags to include
|
|
43
|
+
# @yield [key] Block to extract context from carrier
|
|
44
|
+
# @return [String, nil] Base64 encoded pathway context or nil if disabled
|
|
45
|
+
# @public_api
|
|
46
|
+
def set_consume_checkpoint(type:, source:, auto_instrumentation: false, tags: {}, &block)
|
|
47
|
+
processor&.set_consume_checkpoint(
|
|
48
|
+
type: type,
|
|
49
|
+
source: source,
|
|
50
|
+
manual_checkpoint: !auto_instrumentation,
|
|
51
|
+
tags: tags,
|
|
52
|
+
&block
|
|
53
|
+
)
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
# Track Kafka produce offset for lag monitoring
|
|
57
|
+
#
|
|
58
|
+
# @param topic [String] The Kafka topic name
|
|
59
|
+
# @param partition [Integer] The partition number
|
|
60
|
+
# @param offset [Integer] The offset of the produced message
|
|
61
|
+
# @return [Boolean, nil] true if tracking succeeded, nil if disabled
|
|
62
|
+
# @!visibility private
|
|
63
|
+
def track_kafka_produce(topic, partition, offset)
|
|
64
|
+
processor&.track_kafka_produce(topic, partition, offset, Core::Utils::Time.now)
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
# Track Kafka message consumption for consumer lag monitoring
|
|
68
|
+
#
|
|
69
|
+
# @param topic [String] The Kafka topic name
|
|
70
|
+
# @param partition [Integer] The partition number
|
|
71
|
+
# @param offset [Integer] The offset of the consumed message
|
|
72
|
+
# @return [Boolean, nil] true if tracking succeeded, nil if disabled
|
|
73
|
+
# @!visibility private
|
|
74
|
+
def track_kafka_consume(topic, partition, offset)
|
|
75
|
+
processor&.track_kafka_consume(topic, partition, offset, Core::Utils::Time.now)
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
# Check if Data Streams Monitoring is enabled and available
|
|
79
|
+
#
|
|
80
|
+
# @return [Boolean] true if the processor is available
|
|
81
|
+
# @public_api
|
|
82
|
+
def enabled?
|
|
83
|
+
!processor.nil?
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
private
|
|
87
|
+
|
|
88
|
+
def processor
|
|
89
|
+
components.data_streams
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
def components
|
|
93
|
+
Datadog.send(:components)
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
# Expose Data Streams to global shared objects
|
|
98
|
+
Extensions.activate!
|
|
99
|
+
end
|
|
100
|
+
end
|
data/lib/datadog/di/boot.rb
CHANGED
data/lib/datadog/di/component.rb
CHANGED
|
@@ -29,22 +29,6 @@ module Datadog
|
|
|
29
29
|
end
|
|
30
30
|
end
|
|
31
31
|
|
|
32
|
-
def build!(settings, agent_settings, logger, telemetry: nil)
|
|
33
|
-
unless settings.respond_to?(:dynamic_instrumentation) && settings.dynamic_instrumentation.enabled
|
|
34
|
-
raise "Requested DI component but DI is not enabled in settings"
|
|
35
|
-
end
|
|
36
|
-
|
|
37
|
-
unless settings.respond_to?(:remote) && settings.remote.enabled
|
|
38
|
-
raise "Requested DI component but remote config is not enabled in settings"
|
|
39
|
-
end
|
|
40
|
-
|
|
41
|
-
unless environment_supported?(settings, logger)
|
|
42
|
-
raise "DI does not support the environment (development or Ruby version too low or not MRI)"
|
|
43
|
-
end
|
|
44
|
-
|
|
45
|
-
new(settings, agent_settings, logger, code_tracker: DI.code_tracker, telemetry: telemetry)
|
|
46
|
-
end
|
|
47
|
-
|
|
48
32
|
# Checks whether the runtime environment is supported by
|
|
49
33
|
# dynamic instrumentation. Currently we only require that, if Rails
|
|
50
34
|
# is used, that Rails environment is not development because
|
|
@@ -115,6 +99,20 @@ module Datadog
|
|
|
115
99
|
|
|
116
100
|
def parse_probe_spec_and_notify(probe_spec)
|
|
117
101
|
probe = ProbeBuilder.build_from_remote_config(probe_spec)
|
|
102
|
+
rescue => exc
|
|
103
|
+
begin
|
|
104
|
+
probe = Struct.new(:id).new(
|
|
105
|
+
probe_spec['id'],
|
|
106
|
+
)
|
|
107
|
+
payload = probe_notification_builder.build_errored(probe, exc)
|
|
108
|
+
probe_notifier_worker.add_status(payload)
|
|
109
|
+
rescue # standard:disable Lint/UselessRescue
|
|
110
|
+
# TODO report via instrumentation telemetry?
|
|
111
|
+
raise
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
raise
|
|
115
|
+
else
|
|
118
116
|
payload = probe_notification_builder.build_received(probe)
|
|
119
117
|
probe_notifier_worker.add_status(payload)
|
|
120
118
|
probe
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Datadog
|
|
4
|
+
module DI
|
|
5
|
+
# Contains local and instance variables used when evaluating
|
|
6
|
+
# expressions in DI Expression Language.
|
|
7
|
+
#
|
|
8
|
+
# @api private
|
|
9
|
+
class Context
|
|
10
|
+
def initialize(probe:, settings:, serializer:, locals: nil,
|
|
11
|
+
# In Ruby everything is a method, therefore we should always have
|
|
12
|
+
# a target self. However, if we are not capturing a snapshot,
|
|
13
|
+
# there is no need to pass in the target self.
|
|
14
|
+
target_self: nil,
|
|
15
|
+
path: nil, caller_locations: nil,
|
|
16
|
+
serialized_entry_args: nil,
|
|
17
|
+
return_value: nil, duration: nil, exception: nil)
|
|
18
|
+
@probe = probe
|
|
19
|
+
@settings = settings
|
|
20
|
+
@serializer = serializer
|
|
21
|
+
@locals = locals
|
|
22
|
+
@target_self = target_self
|
|
23
|
+
@path = path
|
|
24
|
+
@caller_locations = caller_locations
|
|
25
|
+
@serialized_entry_args = serialized_entry_args
|
|
26
|
+
@return_value = return_value
|
|
27
|
+
@duration = duration
|
|
28
|
+
@exception = exception
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
attr_reader :probe
|
|
32
|
+
attr_reader :settings
|
|
33
|
+
attr_reader :serializer
|
|
34
|
+
attr_reader :locals
|
|
35
|
+
attr_reader :target_self
|
|
36
|
+
# Actual path of the instrumented file.
|
|
37
|
+
attr_reader :path
|
|
38
|
+
# TODO check how many stack frames we should be keeping/sending,
|
|
39
|
+
# this should be all frames for enriched probes and no frames for
|
|
40
|
+
# non-enriched probes?
|
|
41
|
+
attr_reader :caller_locations
|
|
42
|
+
attr_reader :serialized_entry_args
|
|
43
|
+
# Return value for the method, for a method probe
|
|
44
|
+
attr_reader :return_value
|
|
45
|
+
# How long the method took to execute, for a method probe
|
|
46
|
+
attr_reader :duration
|
|
47
|
+
# Exception raised by the method, if any, for a method probe
|
|
48
|
+
attr_reader :exception
|
|
49
|
+
|
|
50
|
+
def serialized_locals
|
|
51
|
+
# TODO cache?
|
|
52
|
+
locals && serializer.serialize_vars(locals,
|
|
53
|
+
depth: probe.max_capture_depth || settings.dynamic_instrumentation.max_capture_depth,
|
|
54
|
+
attribute_count: probe.max_capture_attribute_count || settings.dynamic_instrumentation.max_capture_attribute_count,)
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def fetch(var_name)
|
|
58
|
+
unless locals
|
|
59
|
+
# TODO return "undefined" instead?
|
|
60
|
+
return nil
|
|
61
|
+
end
|
|
62
|
+
locals[var_name.to_sym]
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def fetch_ivar(var_name)
|
|
66
|
+
target_self.instance_variable_get(var_name)
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
end
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Datadog
|
|
4
|
+
module DI
|
|
5
|
+
module EL
|
|
6
|
+
# DI Expression Language compiler.
|
|
7
|
+
#
|
|
8
|
+
# Converts AST in probe definitions into Expression objects.
|
|
9
|
+
#
|
|
10
|
+
# WARNING: this class produces strings that are then eval'd as
|
|
11
|
+
# Ruby code. Input ASTs are user-controlled. As such the compiler
|
|
12
|
+
# must sanitize and escape all input to avoid injection.
|
|
13
|
+
#
|
|
14
|
+
# Besides quotes and backslashes we must also escape # which is
|
|
15
|
+
# starting string interpolation (#{...}).
|
|
16
|
+
#
|
|
17
|
+
# @api private
|
|
18
|
+
class Compiler
|
|
19
|
+
def compile(ast)
|
|
20
|
+
compile_partial(ast)
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
private
|
|
24
|
+
|
|
25
|
+
OPERATORS = {
|
|
26
|
+
'eq' => '==',
|
|
27
|
+
'ne' => '!=',
|
|
28
|
+
'ge' => '>=',
|
|
29
|
+
'gt' => '>',
|
|
30
|
+
'le' => '<=',
|
|
31
|
+
'lt' => '<',
|
|
32
|
+
}.freeze
|
|
33
|
+
|
|
34
|
+
SINGLE_ARG_METHODS = %w[
|
|
35
|
+
len isEmpty isUndefined
|
|
36
|
+
].freeze
|
|
37
|
+
|
|
38
|
+
TWO_ARG_METHODS = %w[
|
|
39
|
+
startsWith endsWith contains matches
|
|
40
|
+
getmember index instanceof
|
|
41
|
+
].freeze
|
|
42
|
+
|
|
43
|
+
MULTI_ARG_METHODS = {
|
|
44
|
+
'and' => '&&',
|
|
45
|
+
'or' => '||',
|
|
46
|
+
}.freeze
|
|
47
|
+
|
|
48
|
+
def compile_partial(ast)
|
|
49
|
+
case ast
|
|
50
|
+
when Hash
|
|
51
|
+
if ast.length != 1
|
|
52
|
+
raise DI::Error::InvalidExpression, "Expected hash of length 1: #{ast}"
|
|
53
|
+
end
|
|
54
|
+
op, target = ast.first
|
|
55
|
+
case op
|
|
56
|
+
when 'ref'
|
|
57
|
+
unless String === target
|
|
58
|
+
raise DI::Error::InvalidExpression, "Bad ref value type: #{target.class}: #{target}"
|
|
59
|
+
end
|
|
60
|
+
case target
|
|
61
|
+
when '@it'
|
|
62
|
+
'current_item'
|
|
63
|
+
when '@key'
|
|
64
|
+
'current_key'
|
|
65
|
+
when '@value'
|
|
66
|
+
'current_value'
|
|
67
|
+
when '@return'
|
|
68
|
+
# For @return, @duration and @exception we shadow
|
|
69
|
+
# instance variables.
|
|
70
|
+
"context.return_value"
|
|
71
|
+
when '@duration'
|
|
72
|
+
# There is no way to explicitly format the duration.
|
|
73
|
+
# TODO come up with better formatting?
|
|
74
|
+
# We could format to a string here but what if customer
|
|
75
|
+
# has @duration as part of an expression and wants
|
|
76
|
+
# to retain it as a number?
|
|
77
|
+
"(context.duration * 1000)"
|
|
78
|
+
when '@exception'
|
|
79
|
+
"context.exception"
|
|
80
|
+
else
|
|
81
|
+
# Ruby technically allows all kinds of symbols in variable
|
|
82
|
+
# names, for example spaces and many characters.
|
|
83
|
+
# Start out with strict validation to avoid possible
|
|
84
|
+
# surprises and need to escape.
|
|
85
|
+
unless target =~ %r{\A(@?)([a-zA-Z0-9_]+)\z}
|
|
86
|
+
raise DI::Error::BadVariableName, "Bad variable name: #{target}"
|
|
87
|
+
end
|
|
88
|
+
method_name = (($1 == '@') ? 'iref' : 'ref')
|
|
89
|
+
"#{method_name}('#{target}')"
|
|
90
|
+
end
|
|
91
|
+
when *SINGLE_ARG_METHODS
|
|
92
|
+
method_name = op.gsub(/[A-Z]/) { |m| "_#{m.downcase}" }
|
|
93
|
+
"#{method_name}(#{compile_partial(target)}, '#{var_name_maybe(target)}')"
|
|
94
|
+
when *TWO_ARG_METHODS
|
|
95
|
+
method_name = op.gsub(/[A-Z]/) { |m| "_#{m.downcase}" }
|
|
96
|
+
unless Array === target && target.length == 2
|
|
97
|
+
raise DI::Error::InvalidExpression, "Improper #{op} syntax"
|
|
98
|
+
end
|
|
99
|
+
first, second = target
|
|
100
|
+
"#{method_name}(#{compile_partial(first)}, (#{compile_partial(second)}))"
|
|
101
|
+
when *MULTI_ARG_METHODS.keys
|
|
102
|
+
unless Array === target && target.length >= 1
|
|
103
|
+
raise DI::Error::InvalidExpression, "Improper #{op} syntax"
|
|
104
|
+
end
|
|
105
|
+
compiled_targets = target.map do |item|
|
|
106
|
+
"(#{compile_partial(item)})"
|
|
107
|
+
end
|
|
108
|
+
compiled_op = MULTI_ARG_METHODS[op]
|
|
109
|
+
"(#{compiled_targets.join(" #{compiled_op} ")})"
|
|
110
|
+
when 'substring'
|
|
111
|
+
unless Array === target && target.length == 3
|
|
112
|
+
raise DI::Error::InvalidExpression, "Improper #{op} syntax"
|
|
113
|
+
end
|
|
114
|
+
"#{op}(#{target.map { |arg| "(#{compile_partial(arg)})" }.join(", ")})"
|
|
115
|
+
when 'not'
|
|
116
|
+
"!(#{compile_partial(target)})"
|
|
117
|
+
when *OPERATORS.keys
|
|
118
|
+
unless Array === target && target.length == 2
|
|
119
|
+
raise DI::Error::InvalidExpression, "Improper #{op} syntax"
|
|
120
|
+
end
|
|
121
|
+
first, second = target
|
|
122
|
+
operator = OPERATORS.fetch(op)
|
|
123
|
+
"(#{compile_partial(first)}) #{operator} (#{compile_partial(second)})"
|
|
124
|
+
when 'any', 'all', 'filter'
|
|
125
|
+
"#{op}(#{compile_partial(target.first)}) { |current_item, current_key, current_value| #{compile_partial(target.last)} }"
|
|
126
|
+
else
|
|
127
|
+
raise DI::Error::InvalidExpression, "Unknown operation: #{op}"
|
|
128
|
+
end
|
|
129
|
+
when Numeric, true, false, nil
|
|
130
|
+
# No escaping is needed for the values here.
|
|
131
|
+
ast.inspect
|
|
132
|
+
when String
|
|
133
|
+
"\"#{escape(ast)}\""
|
|
134
|
+
when Array
|
|
135
|
+
# Arrays are commonly used as arguments of operators/methods,
|
|
136
|
+
# but there are no arrays at the top level in the syntax that
|
|
137
|
+
# we currently understand. Provide a helpful error message in case
|
|
138
|
+
# syntax is expanded in the future.
|
|
139
|
+
raise DI::Error::InvalidExpression, "Array is not valid at its location, do you need to upgrade dd-trace-rb? #{ast}"
|
|
140
|
+
else
|
|
141
|
+
raise DI::Error::InvalidExpression, "Unknown type in AST: #{ast}"
|
|
142
|
+
end
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
# Returns a textual description of +target+ for use in exception
|
|
146
|
+
# messages. +target+ could be any expression language expression.
|
|
147
|
+
# WARNING: the result of this method is included in eval'd code,
|
|
148
|
+
# it must be sanitized to avoid injection.
|
|
149
|
+
def var_name_maybe(target)
|
|
150
|
+
if Hash === target && target.length == 1 && target.keys.first == 'ref' &&
|
|
151
|
+
String === (value = target.values.first)
|
|
152
|
+
escape(value)
|
|
153
|
+
else
|
|
154
|
+
'(expression)'
|
|
155
|
+
end
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
def escape(needle)
|
|
159
|
+
needle.gsub("\\") { "\\\\" }.gsub('"') { "\\\"" }.gsub('#') { "\\#" }
|
|
160
|
+
end
|
|
161
|
+
end
|
|
162
|
+
end
|
|
163
|
+
end
|
|
164
|
+
end
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Datadog
|
|
4
|
+
module DI
|
|
5
|
+
module EL
|
|
6
|
+
# Evaluator for expression language.
|
|
7
|
+
#
|
|
8
|
+
# @api private
|
|
9
|
+
class Evaluator
|
|
10
|
+
def ref(var)
|
|
11
|
+
@context.fetch(var)
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def iref(var)
|
|
15
|
+
@context.fetch_ivar(var)
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def len(var, var_name)
|
|
19
|
+
case var
|
|
20
|
+
when Array, String, Hash
|
|
21
|
+
var.length
|
|
22
|
+
else
|
|
23
|
+
raise DI::Error::ExpressionEvaluationError, "Unsupported type for length: #{var.class}: #{var_name}"
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def is_empty(var, var_name)
|
|
28
|
+
case var
|
|
29
|
+
when nil, Numeric
|
|
30
|
+
false
|
|
31
|
+
when Array, String
|
|
32
|
+
var.empty?
|
|
33
|
+
else
|
|
34
|
+
raise DI::Error::ExpressionEvaluationError, "Unsupported type for isEmpty: #{var.class}: #{var_name}"
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def is_undefined(var, var_name)
|
|
39
|
+
var.nil?
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def contains(haystack, needle)
|
|
43
|
+
if String === haystack && String === needle or # standard:disable Style/AndOr
|
|
44
|
+
Array === haystack
|
|
45
|
+
haystack.include?(needle)
|
|
46
|
+
else
|
|
47
|
+
raise DI::Error::ExpressionEvaluationError, "Invalid arguments for contains: #{haystack}, #{needle}"
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def matches(haystack, needle)
|
|
52
|
+
re = Regexp.compile(needle)
|
|
53
|
+
!!(haystack =~ re)
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def getmember(object, field)
|
|
57
|
+
object.instance_variable_get("@#{field}")
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def index(array_or_hash, index_or_key)
|
|
61
|
+
case array_or_hash
|
|
62
|
+
when Array
|
|
63
|
+
case index_or_key
|
|
64
|
+
when Integer
|
|
65
|
+
array_or_hash[index_or_key]
|
|
66
|
+
else
|
|
67
|
+
raise DI::Error::ExpressionEvaluationError, "Invalid index value: #{index_or_key}"
|
|
68
|
+
end
|
|
69
|
+
when Hash
|
|
70
|
+
array_or_hash[index_or_key]
|
|
71
|
+
else
|
|
72
|
+
raise DI::Error::ExpressionEvaluationError, "Invalid argument for index: #{array_or_hash}"
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def substring(object, from, to)
|
|
77
|
+
unless String === object
|
|
78
|
+
raise DI::Error::ExpressionEvaluationError, "Invalid type for substring: #{object}"
|
|
79
|
+
end
|
|
80
|
+
object[from...to]
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
def starts_with(haystack, needle)
|
|
84
|
+
# To guard against running arbitrary customer code, check that
|
|
85
|
+
# the haystack is a string. This does not help if customer
|
|
86
|
+
# overrode String#start_with? but at least it's better than nothing.
|
|
87
|
+
String === haystack && haystack.start_with?(needle)
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
def ends_with(haystack, needle)
|
|
91
|
+
String === haystack && haystack.end_with?(needle)
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
def all(collection, &block)
|
|
95
|
+
case collection
|
|
96
|
+
when Array
|
|
97
|
+
collection.all? do |item|
|
|
98
|
+
block.call(item)
|
|
99
|
+
end
|
|
100
|
+
when Hash
|
|
101
|
+
# For hashes, the expression language has both @it and
|
|
102
|
+
# @key/@value. Manufacture @it from the key and value.
|
|
103
|
+
collection.all? do |key, value|
|
|
104
|
+
block.call([key, value], key, value)
|
|
105
|
+
end
|
|
106
|
+
else
|
|
107
|
+
raise DI::Error::ExpressionEvaluationError, "Bad collection type for all: #{collection.class}"
|
|
108
|
+
end
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
def any(collection, &block)
|
|
112
|
+
case collection
|
|
113
|
+
when Array
|
|
114
|
+
collection.any? do |item|
|
|
115
|
+
block.call(item)
|
|
116
|
+
end
|
|
117
|
+
when Hash
|
|
118
|
+
collection.any? do |key, value|
|
|
119
|
+
# For hashes, the expression language has both @it and
|
|
120
|
+
# @key/@value. Manufacture @it from the key and value.
|
|
121
|
+
block.call([key, value], key, value)
|
|
122
|
+
end
|
|
123
|
+
else
|
|
124
|
+
raise DI::Error::ExpressionEvaluationError, "Bad collection type for any: #{collection.class}"
|
|
125
|
+
end
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
def filter(collection, &block)
|
|
129
|
+
case collection
|
|
130
|
+
when Array
|
|
131
|
+
collection.select do |item|
|
|
132
|
+
block.call(item)
|
|
133
|
+
end
|
|
134
|
+
when Hash
|
|
135
|
+
collection.select do |key, value|
|
|
136
|
+
block.call([key, value], key, value)
|
|
137
|
+
end.to_h
|
|
138
|
+
else
|
|
139
|
+
raise DI::Error::ExpressionEvaluationError, "Bad collection type for filter: #{collection.class}"
|
|
140
|
+
end
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
def instanceof(object, cls_name)
|
|
144
|
+
cls = object.class
|
|
145
|
+
loop do
|
|
146
|
+
if cls.name == cls_name
|
|
147
|
+
return true
|
|
148
|
+
end
|
|
149
|
+
if supercls = cls.superclass # standard:disable Lint/AssignmentInCondition
|
|
150
|
+
cls = supercls
|
|
151
|
+
else
|
|
152
|
+
return false
|
|
153
|
+
end
|
|
154
|
+
end
|
|
155
|
+
end
|
|
156
|
+
end
|
|
157
|
+
end
|
|
158
|
+
end
|
|
159
|
+
end
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Datadog
|
|
4
|
+
module DI
|
|
5
|
+
module EL
|
|
6
|
+
# Represents an Expression Language expression.
|
|
7
|
+
#
|
|
8
|
+
# @api private
|
|
9
|
+
class Expression
|
|
10
|
+
def initialize(dsl_expr, compiled_expr)
|
|
11
|
+
unless String === compiled_expr
|
|
12
|
+
raise ArgumentError, "compiled_expr must be a string"
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
@dsl_expr = dsl_expr
|
|
16
|
+
|
|
17
|
+
cls = Class.new(Evaluator)
|
|
18
|
+
cls.class_exec do
|
|
19
|
+
eval(<<-RUBY, Object.new.send(:binding), __FILE__, __LINE__ + 1) # standard:disable Security/Eval
|
|
20
|
+
def evaluate(context)
|
|
21
|
+
@context = context
|
|
22
|
+
#{compiled_expr}
|
|
23
|
+
end
|
|
24
|
+
RUBY
|
|
25
|
+
end
|
|
26
|
+
@evaluator = cls.new
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
attr_reader :dsl_expr
|
|
30
|
+
attr_reader :evaluator
|
|
31
|
+
|
|
32
|
+
def evaluate(context)
|
|
33
|
+
@evaluator.evaluate(context)
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def satisfied?(context)
|
|
37
|
+
!!evaluate(context)
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|