datadog 2.17.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 +90 -1
- data/ext/datadog_profiling_native_extension/collectors_cpu_and_wall_time_worker.c +63 -56
- data/ext/datadog_profiling_native_extension/collectors_stack.c +263 -76
- data/ext/datadog_profiling_native_extension/collectors_stack.h +20 -3
- data/ext/datadog_profiling_native_extension/collectors_thread_context.c +62 -12
- data/ext/datadog_profiling_native_extension/collectors_thread_context.h +1 -0
- data/ext/datadog_profiling_native_extension/extconf.rb +7 -0
- data/ext/datadog_profiling_native_extension/heap_recorder.c +239 -363
- data/ext/datadog_profiling_native_extension/heap_recorder.h +4 -6
- data/ext/datadog_profiling_native_extension/libdatadog_helpers.c +22 -0
- data/ext/datadog_profiling_native_extension/libdatadog_helpers.h +8 -5
- data/ext/datadog_profiling_native_extension/private_vm_api_access.c +38 -26
- data/ext/datadog_profiling_native_extension/private_vm_api_access.h +6 -4
- data/ext/datadog_profiling_native_extension/ruby_helpers.c +1 -13
- data/ext/datadog_profiling_native_extension/ruby_helpers.h +3 -11
- data/ext/datadog_profiling_native_extension/stack_recorder.c +154 -57
- data/ext/libdatadog_api/extconf.rb +2 -2
- data/ext/libdatadog_api/library_config.c +54 -12
- data/ext/libdatadog_api/library_config.h +6 -0
- data/ext/libdatadog_api/process_discovery.c +2 -7
- data/ext/libdatadog_extconf_helpers.rb +1 -1
- data/lib/datadog/appsec/api_security/lru_cache.rb +9 -2
- data/lib/datadog/appsec/api_security/route_extractor.rb +71 -0
- data/lib/datadog/appsec/api_security/sampler.rb +59 -0
- data/lib/datadog/appsec/api_security.rb +14 -0
- data/lib/datadog/appsec/assets/waf_rules/recommended.json +257 -85
- data/lib/datadog/appsec/assets/waf_rules/strict.json +10 -78
- data/lib/datadog/appsec/component.rb +30 -54
- data/lib/datadog/appsec/configuration/settings.rb +60 -2
- data/lib/datadog/appsec/context.rb +6 -6
- data/lib/datadog/appsec/contrib/devise/tracking_middleware.rb +1 -1
- data/lib/datadog/appsec/contrib/rack/request_middleware.rb +27 -16
- data/lib/datadog/appsec/instrumentation/gateway/argument.rb +1 -1
- data/lib/datadog/appsec/processor/rule_loader.rb +5 -6
- data/lib/datadog/appsec/remote.rb +15 -55
- data/lib/datadog/appsec/security_engine/engine.rb +194 -0
- data/lib/datadog/appsec/security_engine/runner.rb +10 -11
- data/lib/datadog/appsec.rb +4 -7
- data/lib/datadog/core/configuration/agent_settings.rb +52 -0
- data/lib/datadog/core/configuration/agent_settings_resolver.rb +1 -43
- data/lib/datadog/core/configuration/components.rb +2 -4
- data/lib/datadog/core/configuration/option.rb +9 -9
- data/lib/datadog/core/configuration/settings.rb +42 -10
- data/lib/datadog/core/configuration/stable_config.rb +1 -2
- data/lib/datadog/core/crashtracking/tag_builder.rb +4 -22
- data/lib/datadog/core/process_discovery/tracer_memfd.rb +15 -0
- data/lib/datadog/core/process_discovery.rb +5 -1
- data/lib/datadog/core/remote/configuration/repository.rb +12 -0
- data/lib/datadog/core/tag_builder.rb +56 -0
- data/lib/datadog/core/telemetry/component.rb +8 -4
- data/lib/datadog/core/telemetry/event/app_client_configuration_change.rb +1 -0
- data/lib/datadog/core/telemetry/event/app_started.rb +148 -40
- data/lib/datadog/core/telemetry/logger.rb +5 -4
- data/lib/datadog/core/telemetry/logging.rb +11 -5
- data/lib/datadog/core/transport/http/adapters/net.rb +17 -2
- data/lib/datadog/core/transport/http/builder.rb +2 -2
- data/lib/datadog/core/transport/http/env.rb +8 -0
- data/lib/datadog/core/utils.rb +7 -0
- data/lib/datadog/di/instrumenter.rb +48 -5
- data/lib/datadog/di/probe_notification_builder.rb +37 -42
- data/lib/datadog/di/probe_notifier_worker.rb +9 -1
- data/lib/datadog/di/serializer.rb +10 -2
- data/lib/datadog/di/transport/http/input.rb +10 -0
- data/lib/datadog/di/transport/input.rb +10 -2
- data/lib/datadog/di.rb +0 -6
- data/lib/datadog/kit/appsec/events/v2.rb +195 -0
- data/lib/datadog/profiling/collectors/code_provenance.rb +17 -8
- data/lib/datadog/profiling/collectors/cpu_and_wall_time_worker.rb +6 -0
- data/lib/datadog/profiling/collectors/idle_sampling_helper.rb +1 -0
- data/lib/datadog/profiling/collectors/info.rb +41 -0
- data/lib/datadog/profiling/collectors/thread_context.rb +16 -1
- data/lib/datadog/profiling/component.rb +8 -9
- data/lib/datadog/profiling/exporter.rb +9 -3
- data/lib/datadog/profiling/ext.rb +0 -12
- data/lib/datadog/profiling/http_transport.rb +2 -2
- data/lib/datadog/profiling/profiler.rb +2 -0
- data/lib/datadog/profiling/scheduler.rb +2 -1
- data/lib/datadog/profiling/sequence_tracker.rb +44 -0
- data/lib/datadog/profiling/stack_recorder.rb +5 -5
- data/lib/datadog/profiling/tag_builder.rb +7 -37
- data/lib/datadog/profiling/tasks/setup.rb +2 -0
- data/lib/datadog/profiling.rb +1 -0
- data/lib/datadog/single_step_instrument.rb +9 -0
- data/lib/datadog/tracing/contrib/action_pack/action_controller/instrumentation.rb +15 -0
- data/lib/datadog/tracing/contrib/action_pack/action_dispatch/instrumentation.rb +19 -12
- data/lib/datadog/tracing/contrib/action_pack/ext.rb +2 -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/lograge/patcher.rb +4 -2
- 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/contrib/sidekiq/ext.rb +1 -0
- data/lib/datadog/tracing/contrib/sidekiq/server_tracer.rb +5 -2
- 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/tracing/sync_writer.rb +1 -1
- data/lib/datadog/tracing/trace_operation.rb +12 -4
- data/lib/datadog/tracing/tracer.rb +6 -2
- data/lib/datadog/version.rb +1 -1
- data/lib/datadog.rb +7 -0
- metadata +14 -10
- data/lib/datadog/appsec/assets/waf_rules/processors.json +0 -321
- data/lib/datadog/appsec/assets/waf_rules/scanners.json +0 -1023
- data/lib/datadog/appsec/processor/rule_merger.rb +0 -171
- data/lib/datadog/appsec/processor.rb +0 -107
@@ -39,60 +39,70 @@ module Datadog
|
|
39
39
|
end
|
40
40
|
|
41
41
|
# Duration is in seconds.
|
42
|
+
# path is the actual path of the instrumented file.
|
42
43
|
def build_executed(probe,
|
43
|
-
|
44
|
-
args: nil, kwargs: nil,
|
45
|
-
|
46
|
-
|
47
|
-
raise "Cannot create snapshot because there is no trace point"
|
48
|
-
end
|
49
|
-
get_local_variables(trace_point)
|
50
|
-
end
|
51
|
-
# TODO check how many stack frames we should be keeping/sending,
|
52
|
-
# this should be all frames for enriched probes and no frames for
|
53
|
-
# non-enriched probes?
|
54
|
-
build_snapshot(probe, rv: rv, snapshot: snapshot,
|
44
|
+
path: nil, rv: nil, duration: nil, caller_locations: nil,
|
45
|
+
serialized_locals: nil, args: nil, kwargs: nil, target_self: nil,
|
46
|
+
serialized_entry_args: nil)
|
47
|
+
build_snapshot(probe, rv: rv, serialized_locals: serialized_locals,
|
55
48
|
# Actual path of the instrumented file.
|
56
|
-
path:
|
57
|
-
duration: duration,
|
49
|
+
path: path,
|
50
|
+
duration: duration,
|
51
|
+
# TODO check how many stack frames we should be keeping/sending,
|
52
|
+
# this should be all frames for enriched probes and no frames for
|
53
|
+
# non-enriched probes?
|
54
|
+
caller_locations: caller_locations,
|
55
|
+
args: args, kwargs: kwargs,
|
56
|
+
target_self: target_self,
|
58
57
|
serialized_entry_args: serialized_entry_args)
|
59
58
|
end
|
60
59
|
|
61
|
-
def build_snapshot(probe, rv: nil,
|
62
|
-
|
60
|
+
def build_snapshot(probe, rv: nil, serialized_locals: nil, path: nil,
|
61
|
+
# In Ruby everything is a method, therefore we should always have
|
62
|
+
# a target self. However, if we are not capturing a snapshot,
|
63
|
+
# there is no need to pass in the target self.
|
64
|
+
target_self: nil,
|
65
|
+
duration: nil, caller_locations: nil,
|
66
|
+
args: nil, kwargs: nil,
|
63
67
|
serialized_entry_args: nil)
|
68
|
+
if probe.capture_snapshot? && !target_self
|
69
|
+
raise ArgumentError, "Asked to build snapshot with snapshot capture but target_self is nil"
|
70
|
+
end
|
71
|
+
|
64
72
|
# TODO also verify that non-capturing probe does not pass
|
65
73
|
# snapshot or vars/args into this method
|
66
74
|
captures = if probe.capture_snapshot?
|
67
75
|
if probe.method?
|
76
|
+
return_arguments = {
|
77
|
+
"@return": serializer.serialize_value(rv,
|
78
|
+
depth: probe.max_capture_depth || settings.dynamic_instrumentation.max_capture_depth,
|
79
|
+
attribute_count: probe.max_capture_attribute_count || settings.dynamic_instrumentation.max_capture_attribute_count),
|
80
|
+
self: serializer.serialize_value(target_self),
|
81
|
+
}
|
68
82
|
{
|
69
83
|
entry: {
|
70
84
|
# standard:disable all
|
71
85
|
arguments: if serialized_entry_args
|
72
86
|
serialized_entry_args
|
73
87
|
else
|
74
|
-
(args || kwargs) && serializer.serialize_args(args, kwargs,
|
88
|
+
(args || kwargs) && serializer.serialize_args(args, kwargs, target_self,
|
75
89
|
depth: probe.max_capture_depth || settings.dynamic_instrumentation.max_capture_depth,
|
76
90
|
attribute_count: probe.max_capture_attribute_count || settings.dynamic_instrumentation.max_capture_attribute_count)
|
77
91
|
end,
|
78
|
-
throwable: nil,
|
79
92
|
# standard:enable all
|
80
93
|
},
|
81
94
|
return: {
|
82
|
-
arguments:
|
83
|
-
"@return": serializer.serialize_value(rv,
|
84
|
-
depth: probe.max_capture_depth || settings.dynamic_instrumentation.max_capture_depth,
|
85
|
-
attribute_count: probe.max_capture_attribute_count || settings.dynamic_instrumentation.max_capture_attribute_count),
|
86
|
-
},
|
95
|
+
arguments: return_arguments,
|
87
96
|
throwable: nil,
|
88
97
|
},
|
89
98
|
}
|
90
99
|
elsif probe.line?
|
91
100
|
{
|
92
|
-
lines:
|
93
|
-
probe.line_no => {
|
94
|
-
|
95
|
-
|
101
|
+
lines: serialized_locals && {
|
102
|
+
probe.line_no => {
|
103
|
+
locals: serialized_locals,
|
104
|
+
arguments: {self: serializer.serialize_value(target_self)},
|
105
|
+
},
|
96
106
|
},
|
97
107
|
}
|
98
108
|
end
|
@@ -194,21 +204,6 @@ module Datadog
|
|
194
204
|
(Core::Utils::Time.now.to_f * 1000).to_i
|
195
205
|
end
|
196
206
|
|
197
|
-
def get_local_variables(trace_point)
|
198
|
-
# binding appears to be constructed on access, therefore
|
199
|
-
# 1) we should attempt to cache it and
|
200
|
-
# 2) we should not call +binding+ until we actually need variable values.
|
201
|
-
binding = trace_point.binding
|
202
|
-
|
203
|
-
# steep hack - should never happen
|
204
|
-
return {} unless binding
|
205
|
-
|
206
|
-
binding.local_variables.each_with_object({}) do |name, map|
|
207
|
-
value = binding.local_variable_get(name)
|
208
|
-
map[name] = value
|
209
|
-
end
|
210
|
-
end
|
211
|
-
|
212
207
|
def active_trace
|
213
208
|
if defined?(Datadog::Tracing)
|
214
209
|
Datadog::Tracing.active_trace
|
@@ -183,7 +183,15 @@ module Datadog
|
|
183
183
|
end
|
184
184
|
|
185
185
|
def do_send_snapshot(batch)
|
186
|
-
snapshot_transport.send_input(batch)
|
186
|
+
snapshot_transport.send_input(batch, tags)
|
187
|
+
end
|
188
|
+
|
189
|
+
def tags
|
190
|
+
# DEV: The tags could be cached but they need to be recreated
|
191
|
+
# when process forks (since the child receives new runtime IDs).
|
192
|
+
Core::TagBuilder.tags(settings).merge(
|
193
|
+
'debugger_version' => Core::Environment::Identity.gem_datadog_version,
|
194
|
+
)
|
187
195
|
end
|
188
196
|
|
189
197
|
[
|
@@ -36,6 +36,10 @@ module Datadog
|
|
36
36
|
# efficient but there would be additional overhead from passing this
|
37
37
|
# parameter all the time and the API would get more complex.
|
38
38
|
#
|
39
|
+
# Note: "self" cannot be used as a parameter name in Ruby, therefore
|
40
|
+
# there should never be a conflict between instance variable
|
41
|
+
# serialization and method parameters.
|
42
|
+
#
|
39
43
|
# @api private
|
40
44
|
class Serializer
|
41
45
|
# Third-party library integration / custom serializers.
|
@@ -82,7 +86,11 @@ module Datadog
|
|
82
86
|
# between positional and keyword arguments. We convert positional
|
83
87
|
# arguments to keyword arguments ("arg1", "arg2", ...) and ensure
|
84
88
|
# the positional arguments are listed first.
|
85
|
-
|
89
|
+
#
|
90
|
+
# Instance variables are technically a hash just like kwargs,
|
91
|
+
# we take them as a separate parameter to avoid a hash merge
|
92
|
+
# in upstream code.
|
93
|
+
def serialize_args(args, kwargs, target_self,
|
86
94
|
depth: settings.dynamic_instrumentation.max_capture_depth,
|
87
95
|
attribute_count: settings.dynamic_instrumentation.max_capture_attribute_count)
|
88
96
|
counter = 0
|
@@ -91,7 +99,7 @@ module Datadog
|
|
91
99
|
# Conversion to symbol is needed here to put args ahead of
|
92
100
|
# kwargs when they are merged below.
|
93
101
|
c[:"arg#{counter}"] = value
|
94
|
-
end.update(kwargs)
|
102
|
+
end.update(kwargs).update(self: target_self)
|
95
103
|
serialize_vars(combined, depth: depth, attribute_count: attribute_count)
|
96
104
|
end
|
97
105
|
|
@@ -53,6 +53,16 @@ module Datadog
|
|
53
53
|
# Encode body & type
|
54
54
|
env.headers[HEADER_CONTENT_TYPE] = encoder.content_type
|
55
55
|
env.body = env.request.parcel.data
|
56
|
+
env.query = {
|
57
|
+
# DEV: In theory we could serialize the tags here
|
58
|
+
# rather than requiring them to be pre-serialized.
|
59
|
+
# In practice the tags should be relatively static
|
60
|
+
# (they would change when process forks, and hostname
|
61
|
+
# could change at any time but probably we should ignore
|
62
|
+
# those changes), therefore serializing the tags
|
63
|
+
# every time would be wasteful.
|
64
|
+
ddtags: env.request.serialized_tags,
|
65
|
+
}
|
56
66
|
|
57
67
|
super
|
58
68
|
end
|
@@ -12,6 +12,13 @@ module Datadog
|
|
12
12
|
end
|
13
13
|
|
14
14
|
class Request < Datadog::Core::Transport::Request
|
15
|
+
attr_reader :serialized_tags
|
16
|
+
|
17
|
+
def initialize(parcel, serialized_tags)
|
18
|
+
super(parcel)
|
19
|
+
|
20
|
+
@serialized_tags = serialized_tags
|
21
|
+
end
|
15
22
|
end
|
16
23
|
|
17
24
|
class Transport
|
@@ -28,10 +35,11 @@ module Datadog
|
|
28
35
|
@apis[HTTP::API::INPUT]
|
29
36
|
end
|
30
37
|
|
31
|
-
def send_input(payload)
|
38
|
+
def send_input(payload, tags)
|
32
39
|
json = JSON.dump(payload)
|
33
40
|
parcel = EncodedParcel.new(json)
|
34
|
-
|
41
|
+
serialized_tags = Core::TagBuilder.serialize_tags(tags)
|
42
|
+
request = Request.new(parcel, serialized_tags)
|
35
43
|
|
36
44
|
response = @client.send_input_payload(request)
|
37
45
|
unless response.ok?
|
data/lib/datadog/di.rb
CHANGED
@@ -35,9 +35,3 @@ module Datadog
|
|
35
35
|
end
|
36
36
|
end
|
37
37
|
end
|
38
|
-
|
39
|
-
# Line probes will not work on Ruby < 2.6 because of lack of :script_compiled
|
40
|
-
# trace point. Activate DI automatically on supported Ruby versions but
|
41
|
-
# always load its settings so that, for example, turning DI off when
|
42
|
-
# we are on Ruby 2.5 does not produce exceptions.
|
43
|
-
require_relative 'di/boot' if RUBY_VERSION >= '2.6' && RUBY_ENGINE != 'jruby'
|
@@ -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
|
@@ -14,7 +14,10 @@ module Datadog
|
|
14
14
|
#
|
15
15
|
# This class acts both as a collector (collecting data) as well as a recorder (records/serializes it)
|
16
16
|
class CodeProvenance
|
17
|
-
def initialize(
|
17
|
+
def initialize(
|
18
|
+
standard_library_path: RbConfig::CONFIG.fetch("rubylibdir"),
|
19
|
+
ruby_native_filename: Datadog::Profiling::Collectors::Stack._native_ruby_native_filename
|
20
|
+
)
|
18
21
|
@libraries_by_name = {}
|
19
22
|
@libraries_by_path = {}
|
20
23
|
@seen_files = Set.new
|
@@ -26,6 +29,7 @@ module Datadog
|
|
26
29
|
name: "stdlib",
|
27
30
|
version: RUBY_VERSION,
|
28
31
|
path: standard_library_path,
|
32
|
+
extra_path: ruby_native_filename,
|
29
33
|
)
|
30
34
|
)
|
31
35
|
end
|
@@ -37,10 +41,6 @@ module Datadog
|
|
37
41
|
self
|
38
42
|
end
|
39
43
|
|
40
|
-
def generate
|
41
|
-
seen_libraries
|
42
|
-
end
|
43
|
-
|
44
44
|
def generate_json
|
45
45
|
JSON.generate(v1: seen_libraries.to_a)
|
46
46
|
end
|
@@ -79,7 +79,15 @@ module Datadog
|
|
79
79
|
loaded_specs.each do |spec|
|
80
80
|
next if libraries_by_name.key?(spec.name)
|
81
81
|
|
82
|
-
record_library(
|
82
|
+
record_library(
|
83
|
+
Library.new(
|
84
|
+
kind: "library",
|
85
|
+
name: spec.name,
|
86
|
+
version: spec.version,
|
87
|
+
path: spec.gem_dir,
|
88
|
+
extra_path: (spec.extension_dir if spec.extensions.any?),
|
89
|
+
)
|
90
|
+
)
|
83
91
|
recorded_library = true
|
84
92
|
end
|
85
93
|
|
@@ -110,11 +118,12 @@ module Datadog
|
|
110
118
|
class Library
|
111
119
|
attr_reader :kind, :name, :version
|
112
120
|
|
113
|
-
def initialize(kind:, name:, version:, path:)
|
121
|
+
def initialize(kind:, name:, version:, path:, extra_path: nil)
|
122
|
+
extra_path = nil if extra_path&.empty?
|
114
123
|
@kind = kind.freeze
|
115
124
|
@name = name.dup.freeze
|
116
125
|
@version = version.to_s.dup.freeze
|
117
|
-
@paths = [path.dup.freeze].freeze
|
126
|
+
@paths = [path.dup.freeze, extra_path.dup.freeze].compact.freeze
|
118
127
|
freeze
|
119
128
|
end
|
120
129
|
|
@@ -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,
|
@@ -33,6 +34,9 @@ module Datadog
|
|
33
34
|
Datadog.logger.warn(
|
34
35
|
"Profiling dynamic sampling rate disabled. This should only be used for testing, and will increase overhead!"
|
35
36
|
)
|
37
|
+
Datadog::Core::Telemetry::Logger.error(
|
38
|
+
"Profiling dynamic sampling rate disabled. This should only be used for testing, and will increase overhead!"
|
39
|
+
)
|
36
40
|
end
|
37
41
|
|
38
42
|
self.class._native_initialize(
|
@@ -46,6 +50,7 @@ module Datadog
|
|
46
50
|
allocation_profiling_enabled: allocation_profiling_enabled,
|
47
51
|
allocation_counting_enabled: allocation_counting_enabled,
|
48
52
|
gvl_profiling_enabled: gvl_profiling_enabled,
|
53
|
+
sighandler_sampling_enabled: sighandler_sampling_enabled,
|
49
54
|
skip_idle_samples_for_testing: skip_idle_samples_for_testing,
|
50
55
|
)
|
51
56
|
@worker_thread = nil
|
@@ -77,6 +82,7 @@ module Datadog
|
|
77
82
|
"Cause: #{e.class.name} #{e.message} Location: #{Array(e.backtrace).first}"
|
78
83
|
)
|
79
84
|
on_failure_proc&.call
|
85
|
+
Datadog::Core::Telemetry::Logger.report(e, description: "CpuAndWallTimeWorker thread error", pii_safe: true)
|
80
86
|
end
|
81
87
|
@worker_thread.name = self.class.name # Repeated from above to make sure thread gets named asap
|
82
88
|
@worker_thread.thread_variable_set(:fork_safe, true)
|
@@ -41,6 +41,7 @@ module Datadog
|
|
41
41
|
"IdleSamplingHelper thread error. " \
|
42
42
|
"Cause: #{e.class.name} #{e.message} Location: #{Array(e.backtrace).first}"
|
43
43
|
)
|
44
|
+
Datadog::Core::Telemetry::Logger.report(e, description: "IdleSamplingHelper thread error", pii_safe: true)
|
44
45
|
end
|
45
46
|
@worker_thread.name = self.class.name # Repeated from above to make sure thread gets named asap
|
46
47
|
@worker_thread.thread_variable_set(:fork_safe, true)
|
@@ -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
|
@@ -21,7 +21,8 @@ module Datadog
|
|
21
21
|
endpoint_collection_enabled:,
|
22
22
|
timeline_enabled:,
|
23
23
|
waiting_for_gvl_threshold_ns:,
|
24
|
-
otel_context_enabled
|
24
|
+
otel_context_enabled:,
|
25
|
+
native_filenames_enabled:
|
25
26
|
)
|
26
27
|
tracer_context_key = safely_extract_context_key_from(tracer)
|
27
28
|
self.class._native_initialize(
|
@@ -33,6 +34,7 @@ module Datadog
|
|
33
34
|
timeline_enabled: timeline_enabled,
|
34
35
|
waiting_for_gvl_threshold_ns: waiting_for_gvl_threshold_ns,
|
35
36
|
otel_context_enabled: otel_context_enabled,
|
37
|
+
native_filenames_enabled: validate_native_filenames(native_filenames_enabled),
|
36
38
|
)
|
37
39
|
end
|
38
40
|
|
@@ -44,6 +46,7 @@ module Datadog
|
|
44
46
|
timeline_enabled: false,
|
45
47
|
waiting_for_gvl_threshold_ns: 10_000_000,
|
46
48
|
otel_context_enabled: false,
|
49
|
+
native_filenames_enabled: true,
|
47
50
|
**options
|
48
51
|
)
|
49
52
|
new(
|
@@ -54,6 +57,7 @@ module Datadog
|
|
54
57
|
timeline_enabled: timeline_enabled,
|
55
58
|
waiting_for_gvl_threshold_ns: waiting_for_gvl_threshold_ns,
|
56
59
|
otel_context_enabled: otel_context_enabled,
|
60
|
+
native_filenames_enabled: native_filenames_enabled,
|
57
61
|
**options,
|
58
62
|
)
|
59
63
|
end
|
@@ -81,6 +85,17 @@ module Datadog
|
|
81
85
|
context = provider.instance_variable_get(:@context)
|
82
86
|
context&.instance_variable_get(:@key)
|
83
87
|
end
|
88
|
+
|
89
|
+
def validate_native_filenames(native_filenames_enabled)
|
90
|
+
if native_filenames_enabled && !Datadog::Profiling::Collectors::Stack._native_filenames_available?
|
91
|
+
Datadog.logger.debug(
|
92
|
+
"Native filenames are enabled, but the required dladdr API was not available. Disabling native filenames."
|
93
|
+
)
|
94
|
+
false
|
95
|
+
else
|
96
|
+
native_filenames_enabled
|
97
|
+
end
|
98
|
+
end
|
84
99
|
end
|
85
100
|
end
|
86
101
|
end
|