datadog 2.18.0 → 2.19.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 +47 -1
- data/ext/datadog_profiling_native_extension/collectors_cpu_and_wall_time_worker.c +51 -10
- data/ext/datadog_profiling_native_extension/collectors_stack.c +58 -49
- data/ext/datadog_profiling_native_extension/collectors_stack.h +2 -1
- data/ext/datadog_profiling_native_extension/collectors_thread_context.c +5 -6
- data/ext/datadog_profiling_native_extension/collectors_thread_context.h +1 -1
- data/ext/datadog_profiling_native_extension/private_vm_api_access.c +37 -26
- data/ext/datadog_profiling_native_extension/private_vm_api_access.h +0 -1
- data/ext/datadog_profiling_native_extension/ruby_helpers.h +1 -1
- data/lib/datadog/appsec/api_security/route_extractor.rb +7 -1
- data/lib/datadog/appsec/instrumentation/gateway/argument.rb +1 -1
- data/lib/datadog/core/configuration/settings.rb +20 -0
- data/lib/datadog/core/telemetry/component.rb +8 -4
- data/lib/datadog/core/telemetry/event/app_started.rb +21 -3
- data/lib/datadog/di/instrumenter.rb +11 -18
- data/lib/datadog/di/probe_notification_builder.rb +21 -16
- data/lib/datadog/di/serializer.rb +6 -2
- data/lib/datadog/di.rb +0 -6
- data/lib/datadog/kit/appsec/events/v2.rb +195 -0
- data/lib/datadog/profiling/collectors/cpu_and_wall_time_worker.rb +2 -0
- data/lib/datadog/profiling/collectors/info.rb +41 -0
- data/lib/datadog/profiling/component.rb +1 -0
- data/lib/datadog/profiling/exporter.rb +9 -3
- data/lib/datadog/profiling/sequence_tracker.rb +44 -0
- data/lib/datadog/profiling/tag_builder.rb +2 -0
- data/lib/datadog/profiling.rb +1 -0
- data/lib/datadog/single_step_instrument.rb +9 -0
- data/lib/datadog/tracing/contrib/active_support/cache/events/cache.rb +7 -1
- data/lib/datadog/tracing/contrib/active_support/configuration/settings.rb +13 -0
- data/lib/datadog/tracing/contrib/mysql2/instrumentation.rb +16 -6
- data/lib/datadog/tracing/contrib/rails/patcher.rb +4 -1
- data/lib/datadog/tracing/contrib/rails/runner.rb +61 -40
- data/lib/datadog/tracing/diagnostics/environment_logger.rb +3 -1
- data/lib/datadog/tracing/span_event.rb +1 -1
- data/lib/datadog/tracing/span_operation.rb +22 -0
- data/lib/datadog/version.rb +1 -1
- data/lib/datadog.rb +7 -0
- metadata +8 -6
@@ -0,0 +1,195 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative '../../identity'
|
4
|
+
|
5
|
+
module Datadog
|
6
|
+
module Kit
|
7
|
+
module AppSec
|
8
|
+
module Events
|
9
|
+
# The second version of Business Logic Events SDK
|
10
|
+
module V2
|
11
|
+
LOGIN_SUCCESS_EVENT = 'users.login.success'
|
12
|
+
LOGIN_FAILURE_EVENT = 'users.login.failure'
|
13
|
+
TELEMETRY_METRICS_NAMESPACE = 'appsec'
|
14
|
+
TELEMETRY_METRICS_SDK_EVENT = 'sdk.event'
|
15
|
+
TELEMETRY_METRICS_SDK_VERSION = 'v2'
|
16
|
+
TELEMETRY_METRICS_EVENTS_INTO_TYPES = {
|
17
|
+
LOGIN_SUCCESS_EVENT => 'login_success',
|
18
|
+
LOGIN_FAILURE_EVENT => 'login_failure'
|
19
|
+
}.freeze
|
20
|
+
|
21
|
+
class << self
|
22
|
+
# Attach user login success information to the service entry span
|
23
|
+
# and trigger AppSec event processing.
|
24
|
+
#
|
25
|
+
# @param login [String] The user login (e.g., username or email).
|
26
|
+
# @param user_or_id [String, Hash<Symbol, String>] (optional) If a
|
27
|
+
# String, considered as a user ID, if a Hash, considered as a user
|
28
|
+
# attributes. The Hash must include `:id` as a key.
|
29
|
+
# @param metadata [Hash<Symbol, String>] Additional flat free-form
|
30
|
+
# metadata to attach to the event.
|
31
|
+
#
|
32
|
+
# @example Login only
|
33
|
+
# Datadog::Kit::AppSec::Events::V2.track_user_login_success('alice@example.com')
|
34
|
+
#
|
35
|
+
# @example Login and user attributes
|
36
|
+
# Datadog::Kit::AppSec::Events::V2.track_user_login_success(
|
37
|
+
# 'alice@example.com',
|
38
|
+
# { id: 'user-123', email: 'alice@example.com', name: 'Alice' },
|
39
|
+
# ip: '192.168.1.1', device: 'mobile', 'usr.country': 'US'
|
40
|
+
# )
|
41
|
+
#
|
42
|
+
# @return [void]
|
43
|
+
def track_user_login_success(login, user_or_id = nil, metadata = {})
|
44
|
+
trace = service_entry_trace
|
45
|
+
span = service_entry_span
|
46
|
+
|
47
|
+
if trace.nil? || span.nil?
|
48
|
+
return Datadog.logger.warn(
|
49
|
+
'Kit::AppSec: Tracing is not enabled. Please enable tracing if you want to track events'
|
50
|
+
)
|
51
|
+
end
|
52
|
+
|
53
|
+
raise TypeError, '`login` argument must be a String' unless login.is_a?(String)
|
54
|
+
raise TypeError, '`metadata` argument must be a Hash' unless metadata.is_a?(Hash)
|
55
|
+
|
56
|
+
user_attributes = build_user_attributes(user_or_id, login)
|
57
|
+
|
58
|
+
set_span_tags(span, metadata, namespace: LOGIN_SUCCESS_EVENT)
|
59
|
+
set_span_tags(span, user_attributes, namespace: "#{LOGIN_SUCCESS_EVENT}.usr")
|
60
|
+
span.set_tag('appsec.events.users.login.success.track', 'true')
|
61
|
+
span.set_tag('_dd.appsec.events.users.login.success.sdk', 'true')
|
62
|
+
|
63
|
+
trace.keep!
|
64
|
+
|
65
|
+
record_event_telemetry_metric(LOGIN_SUCCESS_EVENT)
|
66
|
+
::Datadog::AppSec::Instrumentation.gateway.push('appsec.events.user_lifecycle', LOGIN_SUCCESS_EVENT)
|
67
|
+
|
68
|
+
# NOTE: Guard-clause will not work with Steep typechecking
|
69
|
+
return Kit::Identity.set_user(trace, span, **user_attributes) if user_attributes.key?(:id) # steep:ignore
|
70
|
+
|
71
|
+
# NOTE: This is a fallback for the case when we don't have an ID,
|
72
|
+
# but need to trigger WAF.
|
73
|
+
user = ::Datadog::AppSec::Instrumentation::Gateway::User.new(nil, login)
|
74
|
+
::Datadog::AppSec::Instrumentation.gateway.push('identity.set_user', user)
|
75
|
+
end
|
76
|
+
|
77
|
+
# Attach user login failure information to the service entry span
|
78
|
+
# and trigger AppSec event processing.
|
79
|
+
#
|
80
|
+
# @param login [String] The user login (e.g., username or email).
|
81
|
+
# @param user_exists [Boolean] Whether the user exists in the system.
|
82
|
+
# @param metadata [Hash<Symbol, String>] Additional flat free-form
|
83
|
+
# metadata to attach to the event.
|
84
|
+
#
|
85
|
+
# @example Login only
|
86
|
+
# Datadog::Kit::AppSec::Events::V2.track_user_login_failure('alice@example.com')
|
87
|
+
#
|
88
|
+
# @example With user existence and metadata
|
89
|
+
# Datadog::Kit::AppSec::Events::V2.track_user_login_failure(
|
90
|
+
# 'alice@example.com',
|
91
|
+
# true,
|
92
|
+
# ip: '192.168.1.1', device: 'mobile', 'usr.country': 'US'
|
93
|
+
# )
|
94
|
+
#
|
95
|
+
# @return [void]
|
96
|
+
def track_user_login_failure(login, user_exists = false, metadata = {})
|
97
|
+
trace = service_entry_trace
|
98
|
+
span = service_entry_span
|
99
|
+
|
100
|
+
if trace.nil? || span.nil?
|
101
|
+
return Datadog.logger.warn(
|
102
|
+
'Kit::AppSec: Tracing is not enabled. Please enable tracing if you want to track events'
|
103
|
+
)
|
104
|
+
end
|
105
|
+
|
106
|
+
raise TypeError, '`login` argument must be a String' unless login.is_a?(String)
|
107
|
+
raise TypeError, '`metadata` argument must be a Hash' unless metadata.is_a?(Hash)
|
108
|
+
|
109
|
+
unless user_exists.is_a?(TrueClass) || user_exists.is_a?(FalseClass)
|
110
|
+
raise TypeError, '`user_exists` argument must be a boolean'
|
111
|
+
end
|
112
|
+
|
113
|
+
set_span_tags(span, metadata, namespace: LOGIN_FAILURE_EVENT)
|
114
|
+
span.set_tag('appsec.events.users.login.failure.track', 'true')
|
115
|
+
span.set_tag('_dd.appsec.events.users.login.failure.sdk', 'true')
|
116
|
+
span.set_tag('appsec.events.users.login.failure.usr.login', login)
|
117
|
+
span.set_tag('appsec.events.users.login.failure.usr.exists', user_exists.to_s)
|
118
|
+
|
119
|
+
trace.keep!
|
120
|
+
|
121
|
+
record_event_telemetry_metric(LOGIN_FAILURE_EVENT)
|
122
|
+
::Datadog::AppSec::Instrumentation.gateway.push('appsec.events.user_lifecycle', LOGIN_FAILURE_EVENT)
|
123
|
+
|
124
|
+
user = ::Datadog::AppSec::Instrumentation::Gateway::User.new(nil, login)
|
125
|
+
::Datadog::AppSec::Instrumentation.gateway.push('identity.set_user', user)
|
126
|
+
end
|
127
|
+
|
128
|
+
private
|
129
|
+
|
130
|
+
# NOTE: Current tracer implementation does not provide a way to
|
131
|
+
# get the service entry span. This is a shortcut we take now.
|
132
|
+
def service_entry_trace
|
133
|
+
return Datadog::Tracing.active_trace unless Datadog::AppSec.active_context
|
134
|
+
|
135
|
+
Datadog::AppSec.active_context&.trace
|
136
|
+
end
|
137
|
+
|
138
|
+
# NOTE: Current tracer implementation does not provide a way to
|
139
|
+
# get the service entry span. This is a shortcut we take now.
|
140
|
+
def service_entry_span
|
141
|
+
return Datadog::Tracing.active_span unless Datadog::AppSec.active_context
|
142
|
+
|
143
|
+
Datadog::AppSec.active_context&.span
|
144
|
+
end
|
145
|
+
|
146
|
+
def build_user_attributes(user_or_id, login)
|
147
|
+
raise TypeError, '`login` argument must be a String' unless login.is_a?(String)
|
148
|
+
|
149
|
+
case user_or_id
|
150
|
+
when nil
|
151
|
+
{ login: login }
|
152
|
+
when String
|
153
|
+
{ login: login, id: user_or_id }
|
154
|
+
when Hash
|
155
|
+
raise ArgumentError, 'missing required user key `:id`' unless user_or_id.key?(:id)
|
156
|
+
raise TypeError, 'user key `:id` must be a String' unless user_or_id[:id].is_a?(String)
|
157
|
+
|
158
|
+
user_or_id.merge(login: login)
|
159
|
+
else
|
160
|
+
raise TypeError, '`user_or_id` argument must be either String or Hash'
|
161
|
+
end
|
162
|
+
end
|
163
|
+
|
164
|
+
def set_span_tags(span, tags, namespace:)
|
165
|
+
tags.each do |name, value|
|
166
|
+
next if value.nil?
|
167
|
+
|
168
|
+
span.set_tag("appsec.events.#{namespace}.#{name}", value)
|
169
|
+
end
|
170
|
+
end
|
171
|
+
|
172
|
+
# TODO: In case if we need to introduce telemetry metrics to the SDK v1
|
173
|
+
# or highly increase the number of metrics, this method should be
|
174
|
+
# extracted into a proper module.
|
175
|
+
def record_event_telemetry_metric(event)
|
176
|
+
telemetry = ::Datadog.send(:components, allow_initialization: false)&.telemetry
|
177
|
+
|
178
|
+
if telemetry.nil?
|
179
|
+
return Datadog.logger.debug(
|
180
|
+
'Kit::AppSec: Telemetry component is unavailable. Skip recording SDK metrics'
|
181
|
+
)
|
182
|
+
end
|
183
|
+
|
184
|
+
tags = {
|
185
|
+
event_type: TELEMETRY_METRICS_EVENTS_INTO_TYPES[event],
|
186
|
+
sdk_version: TELEMETRY_METRICS_SDK_VERSION
|
187
|
+
}
|
188
|
+
telemetry.inc(TELEMETRY_METRICS_NAMESPACE, TELEMETRY_METRICS_SDK_EVENT, 1, tags: tags)
|
189
|
+
end
|
190
|
+
end
|
191
|
+
end
|
192
|
+
end
|
193
|
+
end
|
194
|
+
end
|
195
|
+
end
|
@@ -23,6 +23,7 @@ module Datadog
|
|
23
23
|
allocation_profiling_enabled:,
|
24
24
|
allocation_counting_enabled:,
|
25
25
|
gvl_profiling_enabled:,
|
26
|
+
sighandler_sampling_enabled:,
|
26
27
|
# **NOTE**: This should only be used for testing; disabling the dynamic sampling rate will increase the
|
27
28
|
# profiler overhead!
|
28
29
|
dynamic_sampling_rate_enabled: true,
|
@@ -49,6 +50,7 @@ module Datadog
|
|
49
50
|
allocation_profiling_enabled: allocation_profiling_enabled,
|
50
51
|
allocation_counting_enabled: allocation_counting_enabled,
|
51
52
|
gvl_profiling_enabled: gvl_profiling_enabled,
|
53
|
+
sighandler_sampling_enabled: sighandler_sampling_enabled,
|
52
54
|
skip_idle_samples_for_testing: skip_idle_samples_for_testing,
|
53
55
|
)
|
54
56
|
@worker_thread = nil
|
@@ -28,6 +28,37 @@ module Datadog
|
|
28
28
|
|
29
29
|
private
|
30
30
|
|
31
|
+
# Ruby GC tuning environment variables
|
32
|
+
RUBY_GC_TUNING_ENV_VARS = [
|
33
|
+
"RUBY_GC_HEAP_FREE_SLOTS",
|
34
|
+
"RUBY_GC_HEAP_GROWTH_FACTOR",
|
35
|
+
"RUBY_GC_HEAP_GROWTH_MAX_SLOTS",
|
36
|
+
"RUBY_GC_HEAP_FREE_SLOTS_MIN_RATIO",
|
37
|
+
"RUBY_GC_HEAP_FREE_SLOTS_MAX_RATIO",
|
38
|
+
"RUBY_GC_HEAP_FREE_SLOTS_GOAL_RATIO",
|
39
|
+
"RUBY_GC_HEAP_OLDOBJECT_LIMIT_FACTOR",
|
40
|
+
"RUBY_GC_HEAP_REMEMBERED_WB_UNPROTECTED_OBJECTS_LIMIT_RATIO",
|
41
|
+
"RUBY_GC_MALLOC_LIMIT",
|
42
|
+
"RUBY_GC_MALLOC_LIMIT_MAX",
|
43
|
+
"RUBY_GC_MALLOC_LIMIT_GROWTH_FACTOR",
|
44
|
+
"RUBY_GC_OLDMALLOC_LIMIT",
|
45
|
+
"RUBY_GC_OLDMALLOC_LIMIT_MAX",
|
46
|
+
"RUBY_GC_OLDMALLOC_LIMIT_GROWTH_FACTOR",
|
47
|
+
# INIT_SLOTS changed for Ruby 3.3+:
|
48
|
+
# * https://bugs.ruby-lang.org/issues/19785
|
49
|
+
# * https://www.ruby-lang.org/en/news/2023/12/25/ruby-3-3-0-released/#:~:text=Removed%20environment%20variables
|
50
|
+
"RUBY_GC_HEAP_0_INIT_SLOTS",
|
51
|
+
"RUBY_GC_HEAP_1_INIT_SLOTS",
|
52
|
+
"RUBY_GC_HEAP_2_INIT_SLOTS",
|
53
|
+
"RUBY_GC_HEAP_3_INIT_SLOTS",
|
54
|
+
"RUBY_GC_HEAP_4_INIT_SLOTS",
|
55
|
+
# There was only one setting for older Rubies:
|
56
|
+
"RUBY_GC_HEAP_INIT_SLOTS",
|
57
|
+
# Ruby 2.x only, alias for others:
|
58
|
+
"RUBY_FREE_MIN",
|
59
|
+
"RUBY_HEAP_MIN_SLOTS",
|
60
|
+
].freeze
|
61
|
+
|
31
62
|
# Instead of trying to figure out real process start time by checking
|
32
63
|
# /proc or some other complex/non-portable way, approximate start time
|
33
64
|
# by time of requirement of this file.
|
@@ -51,6 +82,7 @@ module Datadog
|
|
51
82
|
engine: Datadog::Core::Environment::Identity.lang_engine,
|
52
83
|
version: Datadog::Core::Environment::Identity.lang_version,
|
53
84
|
platform: Datadog::Core::Environment::Identity.lang_platform,
|
85
|
+
gc_tuning: collect_gc_tuning_info,
|
54
86
|
}.freeze
|
55
87
|
end
|
56
88
|
|
@@ -109,6 +141,15 @@ module Datadog
|
|
109
141
|
v.inspect
|
110
142
|
end
|
111
143
|
end
|
144
|
+
|
145
|
+
def collect_gc_tuning_info
|
146
|
+
return @gc_tuning_info if defined?(@gc_tuning_info)
|
147
|
+
|
148
|
+
@gc_tuning_info = RUBY_GC_TUNING_ENV_VARS.each_with_object({}) do |var, hash|
|
149
|
+
current_value = ENV[var]
|
150
|
+
hash[var.to_sym] = current_value if current_value
|
151
|
+
end.freeze
|
152
|
+
end
|
112
153
|
end
|
113
154
|
end
|
114
155
|
end
|
@@ -67,6 +67,7 @@ module Datadog
|
|
67
67
|
allocation_profiling_enabled: allocation_profiling_enabled,
|
68
68
|
allocation_counting_enabled: settings.profiling.advanced.allocation_counting_enabled,
|
69
69
|
gvl_profiling_enabled: enable_gvl_profiling?(settings, logger),
|
70
|
+
sighandler_sampling_enabled: settings.profiling.advanced.sighandler_sampling_enabled,
|
70
71
|
)
|
71
72
|
|
72
73
|
internal_metadata = {
|
@@ -26,7 +26,8 @@ module Datadog
|
|
26
26
|
:last_flush_finish_at,
|
27
27
|
:created_at,
|
28
28
|
:internal_metadata,
|
29
|
-
:info_json
|
29
|
+
:info_json,
|
30
|
+
:sequence_tracker
|
30
31
|
|
31
32
|
public
|
32
33
|
|
@@ -37,7 +38,8 @@ module Datadog
|
|
37
38
|
code_provenance_collector:,
|
38
39
|
internal_metadata:,
|
39
40
|
minimum_duration_seconds: PROFILE_DURATION_THRESHOLD_SECONDS,
|
40
|
-
time_provider: Time
|
41
|
+
time_provider: Time,
|
42
|
+
sequence_tracker: Datadog::Profiling::SequenceTracker
|
41
43
|
)
|
42
44
|
@pprof_recorder = pprof_recorder
|
43
45
|
@worker = worker
|
@@ -50,6 +52,7 @@ module Datadog
|
|
50
52
|
# NOTE: At the time of this comment collected info does not change over time so we'll hardcode
|
51
53
|
# it on startup to prevent serializing the same info on every flush.
|
52
54
|
@info_json = JSON.generate(info_collector.info).freeze
|
55
|
+
@sequence_tracker = sequence_tracker
|
53
56
|
end
|
54
57
|
|
55
58
|
def flush
|
@@ -73,7 +76,10 @@ module Datadog
|
|
73
76
|
encoded_profile: encoded_profile,
|
74
77
|
code_provenance_file_name: Datadog::Profiling::Ext::Transport::HTTP::CODE_PROVENANCE_FILENAME,
|
75
78
|
code_provenance_data: uncompressed_code_provenance,
|
76
|
-
tags_as_array: Datadog::Profiling::TagBuilder.call(
|
79
|
+
tags_as_array: Datadog::Profiling::TagBuilder.call(
|
80
|
+
settings: Datadog.configuration,
|
81
|
+
profile_seq: sequence_tracker.get_next,
|
82
|
+
).to_a,
|
77
83
|
internal_metadata: internal_metadata.merge(
|
78
84
|
{
|
79
85
|
worker_stats: worker_stats,
|
@@ -0,0 +1,44 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative '../core/utils/forking'
|
4
|
+
|
5
|
+
module Datadog
|
6
|
+
module Profiling
|
7
|
+
# Used to generate the `profile_seq` tag, which effectively counts how many profiles we've attempted to report
|
8
|
+
# from a given runtime-id.
|
9
|
+
#
|
10
|
+
# Note that the above implies a few things:
|
11
|
+
# 1. The sequence number only gets incremented when we decide to report a profile and create a `Flush` for it
|
12
|
+
# 2. The `SequenceTracker` must live across profiler reconfigurations and resets, since no matter how many
|
13
|
+
# profiler instances get created due to reconfiguration, the runtime-id is still the same, so the sequence number
|
14
|
+
# should be kept and not restarted from 0
|
15
|
+
# 3. The `SequenceTracker` must be reset after a fork, since the runtime-id will change, and we want to start
|
16
|
+
# counting from 0 again
|
17
|
+
#
|
18
|
+
# This is why this module is implemented as a singleton that we reuse, not as an instance that we recreate.
|
19
|
+
#
|
20
|
+
# Note that this module is not thread-safe, so it's up to the callers to make sure
|
21
|
+
# it's only used by a single thread at a time (which is what the `Profiling::Exporter`)
|
22
|
+
# is doing.
|
23
|
+
module SequenceTracker
|
24
|
+
class << self
|
25
|
+
include Core::Utils::Forking
|
26
|
+
|
27
|
+
def get_next
|
28
|
+
reset! unless defined?(@sequence_number)
|
29
|
+
after_fork! { reset! }
|
30
|
+
|
31
|
+
next_seq = @sequence_number
|
32
|
+
@sequence_number += 1
|
33
|
+
next_seq
|
34
|
+
end
|
35
|
+
|
36
|
+
private
|
37
|
+
|
38
|
+
def reset!
|
39
|
+
@sequence_number = 0
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -12,10 +12,12 @@ module Datadog
|
|
12
12
|
def self.call(
|
13
13
|
settings:,
|
14
14
|
# Other metadata
|
15
|
+
profile_seq:,
|
15
16
|
profiler_version: Core::Environment::Identity.gem_datadog_version
|
16
17
|
)
|
17
18
|
hash = Core::TagBuilder.tags(settings).merge(
|
18
19
|
FORM_FIELD_TAG_PROFILER_VERSION => profiler_version,
|
20
|
+
'profile_seq' => profile_seq.to_s,
|
19
21
|
)
|
20
22
|
Core::Utils.encode_tags(hash)
|
21
23
|
end
|
data/lib/datadog/profiling.rb
CHANGED
@@ -5,8 +5,17 @@
|
|
5
5
|
#
|
6
6
|
# This file's path is private. Do not reference this file.
|
7
7
|
#
|
8
|
+
|
9
|
+
module Datadog
|
10
|
+
# This module handles conditional loading of single step auto-instrumentation,
|
11
|
+
# which enables Datadog tracing and profiling features when available.
|
12
|
+
module SingleStepInstrument
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
8
16
|
begin
|
9
17
|
require_relative 'auto_instrument'
|
18
|
+
Datadog::SingleStepInstrument::LOADED = true
|
10
19
|
rescue StandardError, LoadError => e
|
11
20
|
warn "Single step instrumentation failed: #{e.class}:#{e.message}\n\tSource:\n\t#{Array(e.backtrace).join("\n\t")}"
|
12
21
|
end
|
@@ -45,9 +45,15 @@ module Datadog
|
|
45
45
|
'cache_write_multi.active_support' => { resource: Ext::RESOURCE_CACHE_MSET, multi_key: true }
|
46
46
|
}.freeze
|
47
47
|
|
48
|
-
def trace?(event,
|
48
|
+
def trace?(event, payload)
|
49
49
|
return false if !Tracing.enabled? || !configuration.enabled
|
50
50
|
|
51
|
+
if (cache_store = configuration[:cache_store])
|
52
|
+
store = cache_backend(payload[:store])
|
53
|
+
|
54
|
+
return false unless cache_store.include?(store)
|
55
|
+
end
|
56
|
+
|
51
57
|
# DEV-3.0: Backwards compatibility code for the 2.x gem series.
|
52
58
|
# DEV-3.0: See documentation at {Datadog::Tracing::Contrib::ActiveSupport::Cache::Instrumentation}
|
53
59
|
# DEV-3.0: for the complete information about this backwards compatibility code.
|
@@ -49,6 +49,19 @@ module Datadog
|
|
49
49
|
o.default true
|
50
50
|
end
|
51
51
|
end
|
52
|
+
|
53
|
+
# Specifies which cache stores to trace.
|
54
|
+
# Accepts a list, with the same format as `config.cache_store`
|
55
|
+
# (e.g. `memory_store`, `file_store`, or symbols like `:file_store`).
|
56
|
+
# Defaults to `nil`, which traces all cache stores.
|
57
|
+
# @see https://github.com/rails/rails/blob/b7520a13adda46c0cc5f3fb4a4c1726633af2bba/guides/source/caching_with_rails.md?plain=1#L576-L582
|
58
|
+
option :cache_store do |o|
|
59
|
+
o.type :array, nilable: true
|
60
|
+
o.default nil
|
61
|
+
o.after_set do |stores|
|
62
|
+
stores&.map!(&:to_s) # Convert symbols to strings to match the Rails configuration format
|
63
|
+
end
|
64
|
+
end
|
52
65
|
end
|
53
66
|
end
|
54
67
|
end
|
@@ -45,16 +45,13 @@ module Datadog
|
|
45
45
|
span.set_tag(Tracing::Metadata::Ext::TAG_COMPONENT, Ext::TAG_COMPONENT)
|
46
46
|
span.set_tag(Tracing::Metadata::Ext::TAG_OPERATION, Ext::TAG_OPERATION_QUERY)
|
47
47
|
|
48
|
-
span
|
48
|
+
tag_database_instance(span, query_options[:database])
|
49
|
+
|
50
|
+
set_span_tags(span, query_options)
|
49
51
|
|
50
52
|
# Set analytics sample rate
|
51
53
|
Contrib::Analytics.set_sample_rate(span, analytics_sample_rate) if analytics_enabled?
|
52
54
|
|
53
|
-
span.set_tag(Contrib::Ext::DB::TAG_INSTANCE, query_options[:database])
|
54
|
-
span.set_tag(Ext::TAG_DB_NAME, query_options[:database])
|
55
|
-
span.set_tag(Tracing::Metadata::Ext::NET::TAG_TARGET_HOST, query_options[:host])
|
56
|
-
span.set_tag(Tracing::Metadata::Ext::NET::TAG_TARGET_PORT, query_options[:port])
|
57
|
-
|
58
55
|
Contrib::SpanAttributeSchema.set_peer_service!(span, Ext::PEER_SERVICE_SOURCES)
|
59
56
|
|
60
57
|
sql = inject_propagation(span, sql, trace_op)
|
@@ -91,6 +88,19 @@ module Datadog
|
|
91
88
|
def analytics_sample_rate
|
92
89
|
datadog_configuration[:analytics_sample_rate]
|
93
90
|
end
|
91
|
+
|
92
|
+
def tag_database_instance(span, database)
|
93
|
+
return if database.nil? || database.empty?
|
94
|
+
|
95
|
+
span.set_tag(Contrib::Ext::DB::TAG_INSTANCE, database)
|
96
|
+
span.set_tag(Ext::TAG_DB_NAME, database)
|
97
|
+
end
|
98
|
+
|
99
|
+
def set_span_tags(span, query_options)
|
100
|
+
span.set_tag(Tracing::Metadata::Ext::NET::TAG_TARGET_HOST, query_options[:host])
|
101
|
+
span.set_tag(Tracing::Metadata::Ext::NET::TAG_TARGET_PORT, query_options[:port])
|
102
|
+
span.set_tag(Tracing::Metadata::Ext::TAG_PEER_HOSTNAME, query_options[:host])
|
103
|
+
end
|
94
104
|
end
|
95
105
|
end
|
96
106
|
end
|
@@ -86,7 +86,10 @@ module Datadog
|
|
86
86
|
|
87
87
|
# Instruments the `bin/rails runner` command.
|
88
88
|
def patch_rails_runner
|
89
|
-
|
89
|
+
# The `RunnerCommand` class is only available in Rails 5.1 and later.
|
90
|
+
if defined?(::Rails::Command::RunnerCommand) && Integration.version >= Gem::Version.new('5.1')
|
91
|
+
::Rails::Command::RunnerCommand.prepend(Runner)
|
92
|
+
end
|
90
93
|
end
|
91
94
|
end
|
92
95
|
end
|
@@ -10,34 +10,48 @@ module Datadog
|
|
10
10
|
# * `-`: for code provided through the STDIN.
|
11
11
|
# * File path: for code provided through a local file.
|
12
12
|
# * `inline code`: for code provided directly as a command line argument.
|
13
|
+
#
|
14
|
+
# The difficulty in instrumenting the Rails Runner is that
|
15
|
+
# the Rails application (and as a consequence the Datadog tracing library)
|
16
|
+
# is loaded very late in the runner execution.
|
17
|
+
# The Rails application is loaded inside the same method the method
|
18
|
+
# that directly executes the code the user wants the runner to execute:
|
19
|
+
#
|
20
|
+
# ```ruby
|
21
|
+
# def perform(code_or_file = nil, *command_argv)
|
22
|
+
# boot_application! # Loads the Rails and Datadog
|
23
|
+
#
|
24
|
+
# if code_or_file == "-"
|
25
|
+
# eval($stdin.read, TOPLEVEL_BINDING, "stdin") # Calls the user code for this Runner
|
26
|
+
# # ...
|
27
|
+
# ```
|
28
|
+
#
|
29
|
+
# This means that there's no time to instrument the calling method, `perform`, which
|
30
|
+
# would be ideal. Instead, we resort to instrumenting `eval` and `load`, but
|
31
|
+
# only for calls from the `Rails::Command::RunnerCommand` class.
|
32
|
+
#
|
13
33
|
# @see https://guides.rubyonrails.org/v6.1/command_line.html#bin-rails-runner
|
14
34
|
module Runner
|
15
35
|
# Limit the maximum size of the source code captured in the source tag.
|
16
36
|
MAX_TAG_VALUE_SIZE = 4096
|
17
37
|
private_constant :MAX_TAG_VALUE_SIZE
|
18
38
|
|
19
|
-
|
20
|
-
|
39
|
+
# Instruments the `Kernel.eval` method, but only for the
|
40
|
+
# `Rails::Command::RunnerCommand` class.
|
41
|
+
def eval(*args)
|
42
|
+
source = args[0]
|
43
|
+
|
44
|
+
if args[2] == 'stdin'
|
21
45
|
name = Ext::SPAN_RUNNER_STDIN
|
22
|
-
resource = nil
|
23
46
|
operation = Ext::TAG_OPERATION_STDIN
|
24
|
-
# The source is not yet available for STDIN, but it will be captured in `eval`.
|
25
|
-
elsif File.exist?(code_or_file)
|
26
|
-
name = Ext::SPAN_RUNNER_FILE
|
27
|
-
resource = code_or_file
|
28
|
-
operation = Ext::TAG_OPERATION_FILE
|
29
|
-
source = File.read(code_or_file)
|
30
47
|
else
|
31
48
|
name = Ext::SPAN_RUNNER_INLINE
|
32
|
-
resource = nil
|
33
49
|
operation = Ext::TAG_OPERATION_INLINE
|
34
|
-
source = code_or_file
|
35
50
|
end
|
36
51
|
|
37
52
|
Tracing.trace(
|
38
53
|
name,
|
39
54
|
service: Datadog.configuration.tracing[:rails][:service_name],
|
40
|
-
resource: resource,
|
41
55
|
tags: {
|
42
56
|
Tracing::Metadata::Ext::TAG_COMPONENT => Ext::TAG_COMPONENT,
|
43
57
|
Tracing::Metadata::Ext::TAG_OPERATION => operation,
|
@@ -55,39 +69,46 @@ module Datadog
|
|
55
69
|
end
|
56
70
|
end
|
57
71
|
|
58
|
-
|
59
|
-
|
60
|
-
span = Datadog::Tracing.active_span
|
61
|
-
if span&.name == Ext::SPAN_RUNNER_STDIN
|
62
|
-
source = args[0]
|
63
|
-
span.set_tag(
|
64
|
-
Ext::TAG_RUNNER_SOURCE,
|
65
|
-
Core::Utils.truncate(source, MAX_TAG_VALUE_SIZE)
|
66
|
-
)
|
67
|
-
end
|
68
|
-
|
69
|
-
super
|
72
|
+
def self.prepend(base)
|
73
|
+
base.const_set(:Kernel, Kernel)
|
70
74
|
end
|
71
75
|
|
72
|
-
|
73
|
-
|
76
|
+
# Instruments the `Kernel.load` method, but only for the
|
77
|
+
# `Rails::Command::RunnerCommand` class.
|
78
|
+
module Kernel
|
79
|
+
def self.load(file, wrap = true)
|
80
|
+
name = Ext::SPAN_RUNNER_FILE
|
81
|
+
resource = file
|
82
|
+
operation = Ext::TAG_OPERATION_FILE
|
83
|
+
|
84
|
+
begin
|
85
|
+
# Reads one more byte than the limit to allow us to check if the source exceeds the limit.
|
86
|
+
source = File.read(file, MAX_TAG_VALUE_SIZE + 1)
|
87
|
+
rescue => e
|
88
|
+
Datadog.logger.debug("Failed to read file '#{file}' for Rails runner: #{e.message}")
|
89
|
+
end
|
74
90
|
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
91
|
+
Tracing.trace(
|
92
|
+
name,
|
93
|
+
service: Datadog.configuration.tracing[:rails][:service_name],
|
94
|
+
resource: resource,
|
95
|
+
tags: {
|
96
|
+
Tracing::Metadata::Ext::TAG_COMPONENT => Ext::TAG_COMPONENT,
|
97
|
+
Tracing::Metadata::Ext::TAG_OPERATION => operation,
|
98
|
+
}
|
99
|
+
) do |span|
|
100
|
+
if source
|
101
|
+
span.set_tag(
|
102
|
+
Ext::TAG_RUNNER_SOURCE,
|
103
|
+
Core::Utils.truncate(source, MAX_TAG_VALUE_SIZE)
|
104
|
+
)
|
105
|
+
end
|
106
|
+
Contrib::Analytics.set_rate!(span, Datadog.configuration.tracing[:rails])
|
107
|
+
|
108
|
+
super
|
109
|
+
end
|
86
110
|
end
|
87
|
-
ret
|
88
111
|
end
|
89
|
-
|
90
|
-
ruby2_keywords :find_by_namespace if respond_to?(:ruby2_keywords, true)
|
91
112
|
end
|
92
113
|
end
|
93
114
|
end
|
@@ -84,7 +84,7 @@ module Datadog
|
|
84
84
|
# @return [Numeric, nil] tracer sample rate configured
|
85
85
|
def sample_rate
|
86
86
|
sampler = Datadog.configuration.tracing.sampler
|
87
|
-
return
|
87
|
+
return Datadog.configuration.tracing.sampling.default_rate unless sampler
|
88
88
|
|
89
89
|
sampler.sample_rate(nil) rescue nil
|
90
90
|
end
|
@@ -97,6 +97,8 @@ module Datadog
|
|
97
97
|
# @return [Hash, nil] sample rules configured
|
98
98
|
def sampling_rules
|
99
99
|
sampler = Datadog.configuration.tracing.sampler
|
100
|
+
return Datadog.configuration.tracing.sampling.rules unless sampler
|
101
|
+
|
100
102
|
return nil unless sampler.is_a?(Tracing::Sampling::PrioritySampler) &&
|
101
103
|
sampler.priority_sampler.is_a?(Tracing::Sampling::RuleSampler)
|
102
104
|
|
@@ -56,7 +56,7 @@ module Datadog
|
|
56
56
|
attr = {}
|
57
57
|
@attributes.each do |key, value|
|
58
58
|
attr[key] = if value.is_a?(Array)
|
59
|
-
{ type: ARRAY_TYPE, array_value: value.map { |v| serialize_native_attribute(v) } }
|
59
|
+
{ type: ARRAY_TYPE, array_value: { values: value.map { |v| serialize_native_attribute(v) } } }
|
60
60
|
else
|
61
61
|
serialize_native_attribute(value)
|
62
62
|
end
|