datadog 2.21.0 → 2.22.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 +48 -1
- data/ext/LIBDATADOG_DEVELOPMENT.md +60 -0
- data/ext/datadog_profiling_native_extension/collectors_discrete_dynamic_sampler.c +1 -1
- data/ext/libdatadog_api/ddsketch.c +106 -0
- data/ext/libdatadog_api/init.c +3 -0
- data/ext/libdatadog_api/library_config.c +35 -27
- 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/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/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 +25 -13
- 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 +14 -6
- data/lib/datadog/core/configuration/stable_config.rb +10 -0
- data/lib/datadog/core/configuration/supported_configurations.rb +2 -0
- data/lib/datadog/core/configuration.rb +1 -1
- data/lib/datadog/core/ddsketch.rb +21 -0
- data/lib/datadog/core/environment/yjit.rb +2 -1
- data/lib/datadog/core/pin.rb +4 -8
- data/lib/datadog/core/process_discovery.rb +4 -2
- data/lib/datadog/core/remote/component.rb +4 -6
- data/lib/datadog/core/telemetry/component.rb +11 -0
- 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.rb +1 -0
- data/lib/datadog/core/transport/response.rb +4 -1
- data/lib/datadog/core/utils/network.rb +19 -0
- data/lib/datadog/di/boot.rb +1 -0
- data/lib/datadog/di/component.rb +14 -0
- 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 +25 -0
- data/lib/datadog/di/instrumenter.rb +101 -32
- data/lib/datadog/di/probe.rb +35 -15
- data/lib/datadog/di/probe_builder.rb +39 -1
- data/lib/datadog/di/probe_manager.rb +3 -2
- data/lib/datadog/di/probe_notification_builder.rb +50 -51
- data/lib/datadog/di/serializer.rb +151 -7
- data/lib/datadog/tracing/component.rb +6 -17
- data/lib/datadog/tracing/configuration/dynamic.rb +2 -2
- data/lib/datadog/tracing/configuration/settings.rb +3 -3
- data/lib/datadog/tracing/contrib/component.rb +2 -2
- 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 +53 -28
- data/lib/datadog/tracing/metadata/ext.rb +8 -0
- data/lib/datadog/version.rb +1 -1
- metadata +22 -9
- data/ext/libdatadog_api/macos_development.md +0 -26
@@ -10,6 +10,7 @@ module Datadog
|
|
10
10
|
{"DD_AGENT_HOST" => {version: ["A"]},
|
11
11
|
"DD_API_KEY" => {version: ["A"]},
|
12
12
|
"DD_API_SECURITY_ENABLED" => {version: ["A"]},
|
13
|
+
"DD_API_SECURITY_ENDPOINT_COLLECTION_ENABLED" => {version: ["A"]},
|
13
14
|
"DD_API_SECURITY_REQUEST_SAMPLE_RATE" => {version: ["A"]},
|
14
15
|
"DD_API_SECURITY_SAMPLE_DELAY" => {version: ["A"]},
|
15
16
|
"DD_APM_TRACING_ENABLED" => {version: ["A"]},
|
@@ -171,6 +172,7 @@ module Datadog
|
|
171
172
|
"DD_TRACE_GRAPHQL_ANALYTICS_SAMPLE_RATE" => {version: ["A"]},
|
172
173
|
"DD_TRACE_GRAPHQL_ENABLED" => {version: ["A"]},
|
173
174
|
"DD_TRACE_GRAPHQL_ERROR_EXTENSIONS" => {version: ["A"]},
|
175
|
+
"DD_TRACE_GRAPHQL_ERROR_TRACKING" => {version: ["A"]},
|
174
176
|
"DD_TRACE_GRAPHQL_WITH_UNIFIED_TRACER" => {version: ["A"]},
|
175
177
|
"DD_TRACE_GRPC_ANALYTICS_ENABLED" => {version: ["A"]},
|
176
178
|
"DD_TRACE_GRPC_ANALYTICS_SAMPLE_RATE" => {version: ["A"]},
|
@@ -238,7 +238,7 @@ module Datadog
|
|
238
238
|
yield write_components
|
239
239
|
rescue ThreadError => e
|
240
240
|
logger_without_components.error(
|
241
|
-
|
241
|
+
"Detected deadlock during datadog initialization: #{e.class}: #{e}. " \
|
242
242
|
'Please report this at https://github.com/datadog/dd-trace-rb/blob/master/CONTRIBUTING.md#found-a-bug' \
|
243
243
|
"\n\tSource:\n\t#{Array(e.backtrace).join("\n\t")}"
|
244
244
|
)
|
@@ -0,0 +1,21 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'datadog/core'
|
4
|
+
|
5
|
+
module Datadog
|
6
|
+
module Core
|
7
|
+
# Used to access ddsketch APIs.
|
8
|
+
# APIs in this class are implemented as native code.
|
9
|
+
class DDSketch
|
10
|
+
def self.supported?
|
11
|
+
Datadog::Core::LIBDATADOG_API_FAILURE.nil?
|
12
|
+
end
|
13
|
+
|
14
|
+
def initialize
|
15
|
+
unless self.class.supported?
|
16
|
+
raise(ArgumentError, "DDSketch is not supported: #{Datadog::Core::LIBDATADOG_API_FAILURE}")
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
data/lib/datadog/core/pin.rb
CHANGED
@@ -43,18 +43,14 @@ module Datadog
|
|
43
43
|
# rubocop:disable Style/TrivialAccessors
|
44
44
|
def onto(obj)
|
45
45
|
unless obj.respond_to? :datadog_pin=
|
46
|
-
obj.
|
47
|
-
|
48
|
-
@datadog_pin = pin
|
49
|
-
end
|
46
|
+
obj.define_singleton_method(:datadog_pin=) do |pin|
|
47
|
+
@datadog_pin = pin
|
50
48
|
end
|
51
49
|
end
|
52
50
|
|
53
51
|
unless obj.respond_to? :datadog_pin
|
54
|
-
obj.
|
55
|
-
|
56
|
-
@datadog_pin
|
57
|
-
end
|
52
|
+
obj.define_singleton_method(:datadog_pin) do
|
53
|
+
@datadog_pin
|
58
54
|
end
|
59
55
|
end
|
60
56
|
|
@@ -37,14 +37,16 @@ module Datadog
|
|
37
37
|
# In the C method exposed by ddcommon, memfd_create replaces empty strings by None for these fields.
|
38
38
|
def get_metadata(settings)
|
39
39
|
{
|
40
|
-
schema_version: 1,
|
41
40
|
runtime_id: Core::Environment::Identity.id,
|
42
41
|
tracer_language: Core::Environment::Identity.lang,
|
43
42
|
tracer_version: Core::Environment::Identity.gem_datadog_version_semver2,
|
44
43
|
hostname: Core::Environment::Socket.hostname,
|
45
44
|
service_name: settings.service || '',
|
46
45
|
service_env: settings.env || '',
|
47
|
-
service_version: settings.version || ''
|
46
|
+
service_version: settings.version || '',
|
47
|
+
# TODO: Implement process tags and container id
|
48
|
+
process_tags: '',
|
49
|
+
container_id: ''
|
48
50
|
}
|
49
51
|
end
|
50
52
|
|
@@ -135,13 +135,11 @@ module Datadog
|
|
135
135
|
|
136
136
|
# Release all current waiters
|
137
137
|
def lift
|
138
|
-
@mutex.
|
138
|
+
@mutex.synchronize do
|
139
|
+
@once ||= true
|
139
140
|
|
140
|
-
|
141
|
-
|
142
|
-
@condition.broadcast
|
143
|
-
ensure
|
144
|
-
@mutex.unlock
|
141
|
+
@condition.broadcast
|
142
|
+
end
|
145
143
|
end
|
146
144
|
end
|
147
145
|
|
@@ -19,6 +19,8 @@ module Datadog
|
|
19
19
|
#
|
20
20
|
# @api private
|
21
21
|
class Component
|
22
|
+
ENDPOINT_COLLECTION_MESSAGE_LIMIT = 300
|
23
|
+
|
22
24
|
attr_reader :enabled, :logger, :transport, :worker
|
23
25
|
|
24
26
|
include Core::Utils::Forking
|
@@ -165,6 +167,15 @@ module Datadog
|
|
165
167
|
@worker.enqueue(Event::AppClientConfigurationChange.new(changes, 'remote_config'))
|
166
168
|
end
|
167
169
|
|
170
|
+
# Report application endpoints
|
171
|
+
def app_endpoints_loaded(endpoints, page_size: ENDPOINT_COLLECTION_MESSAGE_LIMIT)
|
172
|
+
return if !@enabled || forked?
|
173
|
+
|
174
|
+
endpoints.each_slice(page_size).with_index do |endpoints_slice, i|
|
175
|
+
@worker.enqueue(Event::AppEndpointsLoaded.new(endpoints_slice, is_first: i.zero?))
|
176
|
+
end
|
177
|
+
end
|
178
|
+
|
168
179
|
# Increments a count metric.
|
169
180
|
def inc(namespace, metric_name, value, tags: {}, common: true)
|
170
181
|
@metrics_manager.inc(namespace, metric_name, value, tags: tags, common: common)
|
@@ -31,15 +31,15 @@ module Datadog
|
|
31
31
|
seq_id = self.class.sequence.next
|
32
32
|
payload = Request.build_payload(event, seq_id, debug: debug?)
|
33
33
|
res = @transport.send_telemetry(request_type: event.type, payload: payload)
|
34
|
-
|
34
|
+
if res.ok?
|
35
|
+
logger.debug { "Telemetry sent for event `#{event.type}`" }
|
36
|
+
else
|
37
|
+
logger.debug { "Failed to send telemetry for event `#{event.type}`: #{res.inspect}" }
|
38
|
+
end
|
35
39
|
res
|
36
40
|
rescue => e
|
37
41
|
logger.debug {
|
38
|
-
"Unable to send telemetry request for event `#{
|
39
|
-
event.type
|
40
|
-
rescue
|
41
|
-
"unknown"
|
42
|
-
end}`: #{e}"
|
42
|
+
"Unable to send telemetry request for event `#{event.respond_to?(:type) ? event.type : event.to_s}`: #{e.class}: #{e}"
|
43
43
|
}
|
44
44
|
Core::Transport::InternalErrorResponse.new(e)
|
45
45
|
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'base'
|
4
|
+
|
5
|
+
module Datadog
|
6
|
+
module Core
|
7
|
+
module Telemetry
|
8
|
+
module Event
|
9
|
+
# Telemetry event class for sending 'app-endpoints' payload
|
10
|
+
class AppEndpointsLoaded < Base
|
11
|
+
def initialize(endpoints, is_first:)
|
12
|
+
@endpoints = endpoints
|
13
|
+
@is_first = !!is_first
|
14
|
+
end
|
15
|
+
|
16
|
+
def type
|
17
|
+
'app-endpoints'
|
18
|
+
end
|
19
|
+
|
20
|
+
def payload
|
21
|
+
{
|
22
|
+
is_first: @is_first,
|
23
|
+
endpoints: @endpoints
|
24
|
+
}
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -26,6 +26,7 @@ require_relative 'event/base'
|
|
26
26
|
require_relative 'event/app_client_configuration_change'
|
27
27
|
require_relative 'event/app_closing'
|
28
28
|
require_relative 'event/app_dependencies_loaded'
|
29
|
+
require_relative 'event/app_endpoints_loaded'
|
29
30
|
require_relative 'event/app_heartbeat'
|
30
31
|
require_relative 'event/app_integrations_change'
|
31
32
|
require_relative 'event/app_started'
|
@@ -34,7 +34,10 @@ module Datadog
|
|
34
34
|
end
|
35
35
|
|
36
36
|
def inspect
|
37
|
-
|
37
|
+
maybe_code = if respond_to?(:code)
|
38
|
+
" code:#{code}," # steep:ignore
|
39
|
+
end
|
40
|
+
"#{self.class} ok?:#{ok?},#{maybe_code} unsupported?:#{unsupported?}, " \
|
38
41
|
"not_found?:#{not_found?}, client_error?:#{client_error?}, " \
|
39
42
|
"server_error?:#{server_error?}, internal_error?:#{internal_error?}, " \
|
40
43
|
"payload:#{payload}"
|
@@ -13,6 +13,7 @@ module Datadog
|
|
13
13
|
true-client-ip
|
14
14
|
x-client-ip
|
15
15
|
x-forwarded
|
16
|
+
forwarded
|
16
17
|
forwarded-for
|
17
18
|
x-cluster-client-ip
|
18
19
|
fastly-client-ip
|
@@ -73,6 +74,8 @@ module Datadog
|
|
73
74
|
next unless value
|
74
75
|
|
75
76
|
ips = value.split(',')
|
77
|
+
ips = process_forwarded_header_values(ips) if name == 'forwarded'
|
78
|
+
|
76
79
|
ips.each do |ip|
|
77
80
|
parsed_ip = ip_to_ipaddr(ip.strip)
|
78
81
|
|
@@ -83,6 +86,22 @@ module Datadog
|
|
83
86
|
nil
|
84
87
|
end
|
85
88
|
|
89
|
+
def process_forwarded_header_values(values)
|
90
|
+
values.each_with_object([]) do |value, acc|
|
91
|
+
value.downcase!
|
92
|
+
|
93
|
+
value.split(';').each do |tuple_str|
|
94
|
+
tuple_str.strip!
|
95
|
+
next unless tuple_str.start_with?('for=')
|
96
|
+
|
97
|
+
tuple_str.delete_prefix!('for=')
|
98
|
+
tuple_str.delete!('"')
|
99
|
+
|
100
|
+
acc << tuple_str
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
86
105
|
# Returns whether the given value is more likely to be an IPv4 than an IPv6 address.
|
87
106
|
#
|
88
107
|
# This is done by checking if a dot (`'.'`) character appears before a colon (`':'`) in the value.
|
data/lib/datadog/di/boot.rb
CHANGED
data/lib/datadog/di/component.rb
CHANGED
@@ -115,6 +115,20 @@ module Datadog
|
|
115
115
|
|
116
116
|
def parse_probe_spec_and_notify(probe_spec)
|
117
117
|
probe = ProbeBuilder.build_from_remote_config(probe_spec)
|
118
|
+
rescue => exc
|
119
|
+
begin
|
120
|
+
probe = Struct.new(:id).new(
|
121
|
+
probe_spec['id'],
|
122
|
+
)
|
123
|
+
payload = probe_notification_builder.build_errored(probe, exc)
|
124
|
+
probe_notifier_worker.add_status(payload)
|
125
|
+
rescue # standard:disable Lint/UselessRescue
|
126
|
+
# TODO report via instrumentation telemetry?
|
127
|
+
raise
|
128
|
+
end
|
129
|
+
|
130
|
+
raise
|
131
|
+
else
|
118
132
|
payload = probe_notification_builder.build_received(probe)
|
119
133
|
probe_notifier_worker.add_status(payload)
|
120
134
|
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
|